Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/unicorn/unicorn.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea but I feel that the state after uc_emu_start is not always safe to start immediately, like those after errors.

uc_emu_start with a pc write shall reset states in most cases, but uc_emu_run does not. Do you think that introduces potential issues?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be, I need to think about it a bit. But I have a few questions:

  • Which error states are not save to start immediately from?
  • does a pc write restore from a context does have the same effect?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which error states are not save to start immediately from?

I can not really come up with one so I think this PR is mostly safe to go but let me review others firstly.

does a pc write restore from a context does have the same effect?

Writing a pc is always making the emulator to a clean state, at least I design it in this way.

size_t count);

/*
Stop emulation (which was started by uc_emu_start() API.
This is typically called from callback functions registered via tracing APIs.
Expand Down
145 changes: 78 additions & 67 deletions uc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
Loading