diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 98a21a3418..801f04cea1 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -1093,6 +1093,27 @@ UNICORN_EXPORT uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count); +/* + Emulate machine code starting from the current instruction pointer + + @uc: handle returned by uc_open() + @until: address where emulation stops (i.e. when this address is hit) + @timeout: duration to emulate the code (in microseconds). When this value is 0, + we will emulate the code in infinite time, until the code is finished. + @count: the number of instructions to be emulated. When this value is 0, + we will emulate all the code available, until the code is finished. + + NOTE: The internal states of the engine is guranteed to be correct if and only + if uc_emu_start returns without any errors or errors have been handled in + the callbacks. + + @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum + for detailed error). +*/ +UNICORN_EXPORT +uc_err uc_emu_run(uc_engine *uc, uint64_t until, uint64_t timeout, + size_t count); + /* Stop emulation (which was started by uc_emu_start() API. This is typically called from callback functions registered via tracing APIs. diff --git a/uc.c b/uc.c index 1de8a5b98f..72954108c5 100644 --- a/uc.c +++ b/uc.c @@ -1073,8 +1073,7 @@ static void clear_deleted_hooks(uc_engine *uc) } UNICORN_EXPORT -uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, - uint64_t timeout, size_t count) +uc_err uc_emu_run(uc_engine *uc, uint64_t until, uint64_t timeout, size_t count) { uc_err err; @@ -1099,6 +1098,82 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, } uc->nested_level++; + uc->skip_sync_pc_on_exit = false; + uc->stop_request = false; + + uc->emu_count = count; + // remove count hook if counting isn't necessary + if (count <= 0 && uc->count_hook != 0) { + uc_hook_del(uc, uc->count_hook); + uc->count_hook = 0; + + // In this case, we have to drop all translated blocks. + uc->tb_flush(uc); + } + // set up count hook to count instructions. + if (count > 0 && uc->count_hook == 0) { + uc_err err; + // callback to count instructions must be run before everything else, + // so instead of appending, we must insert the hook at the begin + // of the hook list + uc->hook_insert = 1; + err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, + NULL, 1, 0); + // restore to append mode for uc_hook_add() + uc->hook_insert = 0; + if (err != UC_ERR_OK) { + uc->nested_level--; + return err; + } + } + + // If UC_CTL_UC_USE_EXITS is set, then the @until param won't have any + // effect. This is designed for the backward compatibility. + if (!uc->use_exits) { + uc->exits[uc->nested_level - 1] = until; + } + + if (timeout) { + enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds + } + + uc->vm_start(uc); + + uc->nested_level--; + + // emulation is done if and only if we exit the outer uc_emu_start + // or we may lost uc_emu_stop + if (uc->nested_level == 0) { + uc->emulation_done = true; + + // remove hooks to delete + // make sure we delete all hooks at the first level. + clear_deleted_hooks(uc); + + restore_jit_state(uc); + } + + if (timeout) { + // wait for the timer to finish + qemu_thread_join(&uc->timer); + } + + // We may be in a nested uc_emu_start and thus clear invalid_error + // once we are done. + err = uc->invalid_error; + uc->invalid_error = 0; + return err; +} + +UNICORN_EXPORT +uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, + uint64_t timeout, size_t count) +{ + // Avoid nested uc_emu_start saves wrong jit states. + if (uc->nested_level == 0) { + UC_INIT(uc); + } + uint32_t begin_pc32 = READ_DWORD(begin); switch (uc->arch) { default: @@ -1186,71 +1261,7 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, break; #endif } - uc->skip_sync_pc_on_exit = false; - uc->stop_request = false; - - uc->emu_count = count; - // remove count hook if counting isn't necessary - if (count <= 0 && uc->count_hook != 0) { - uc_hook_del(uc, uc->count_hook); - uc->count_hook = 0; - - // In this case, we have to drop all translated blocks. - uc->tb_flush(uc); - } - // set up count hook to count instructions. - if (count > 0 && uc->count_hook == 0) { - uc_err err; - // callback to count instructions must be run before everything else, - // so instead of appending, we must insert the hook at the begin - // of the hook list - uc->hook_insert = 1; - err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, - NULL, 1, 0); - // restore to append mode for uc_hook_add() - uc->hook_insert = 0; - if (err != UC_ERR_OK) { - uc->nested_level--; - return err; - } - } - - // If UC_CTL_UC_USE_EXITS is set, then the @until param won't have any - // effect. This is designed for the backward compatibility. - if (!uc->use_exits) { - uc->exits[uc->nested_level - 1] = until; - } - - if (timeout) { - enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds - } - - uc->vm_start(uc); - - uc->nested_level--; - - // emulation is done if and only if we exit the outer uc_emu_start - // or we may lost uc_emu_stop - if (uc->nested_level == 0) { - uc->emulation_done = true; - - // remove hooks to delete - // make sure we delete all hooks at the first level. - clear_deleted_hooks(uc); - - restore_jit_state(uc); - } - - if (timeout) { - // wait for the timer to finish - qemu_thread_join(&uc->timer); - } - - // We may be in a nested uc_emu_start and thus clear invalid_error - // once we are done. - err = uc->invalid_error; - uc->invalid_error = 0; - return err; + return uc_emu_run(uc, until, timeout, count); } UNICORN_EXPORT