diff --git a/include/uc_priv.h b/include/uc_priv.h index 66b6e83e..60a9b486 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -486,6 +486,28 @@ static inline void hooked_regions_check(uc_engine *uc, uint64_t start, length); } +/* + break translation loop: + This is done in two cases: + 1. the user wants to stop the emulation. + 2. the user has set it IP. This requires to restart the internal + CPU emulation and rebuild some translation blocks +*/ +static inline uc_err break_translation_loop(uc_engine *uc) +{ + if (uc->emulation_done) { + return UC_ERR_OK; + } + + // TODO: make this atomic somehow? + if (uc->cpu) { + // exit the current TB + cpu_exit(uc->cpu); + } + + return UC_ERR_OK; +} + #ifdef UNICORN_TRACER #define UC_TRACE_START(loc) trace_start(get_tracer(), loc) #define UC_TRACE_END(loc, fmt, ...) \ diff --git a/qemu/softmmu/cpus.c b/qemu/softmmu/cpus.c index f6b242f3..22511ac7 100644 --- a/qemu/softmmu/cpus.c +++ b/qemu/softmmu/cpus.c @@ -96,7 +96,7 @@ static int tcg_cpu_exec(struct uc_struct *uc) r = cpu_exec(uc, cpu); // quit current TB but continue emulating? - if (uc->quit_request) { + if (uc->quit_request && !uc->stop_request) { // reset stop_request uc->stop_request = false; diff --git a/qemu/target/arm/unicorn_aarch64.c b/qemu/target/arm/unicorn_aarch64.c index fec0db68..fa40330f 100644 --- a/qemu/target/arm/unicorn_aarch64.c +++ b/qemu/target/arm/unicorn_aarch64.c @@ -372,7 +372,7 @@ int arm64_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_ARM64_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/arm/unicorn_arm.c b/qemu/target/arm/unicorn_arm.c index bb39b348..e706b12b 100644 --- a/qemu/target/arm/unicorn_arm.c +++ b/qemu/target/arm/unicorn_arm.c @@ -515,7 +515,7 @@ int arm_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_ARM_REG_R15) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/i386/unicorn.c b/qemu/target/i386/unicorn.c index 4541044e..3e83b0ba 100644 --- a/qemu/target/i386/unicorn.c +++ b/qemu/target/i386/unicorn.c @@ -1521,7 +1521,7 @@ int x86_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, case UC_X86_REG_IP: // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); break; } @@ -1535,7 +1535,7 @@ int x86_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, case UC_X86_REG_IP: // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); break; } #endif diff --git a/qemu/target/m68k/unicorn.c b/qemu/target/m68k/unicorn.c index d748ace7..d0a091fa 100644 --- a/qemu/target/m68k/unicorn.c +++ b/qemu/target/m68k/unicorn.c @@ -117,7 +117,7 @@ int m68k_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_M68K_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/mips/unicorn.c b/qemu/target/mips/unicorn.c index bd4d5595..792fb45e 100644 --- a/qemu/target/mips/unicorn.c +++ b/qemu/target/mips/unicorn.c @@ -170,7 +170,7 @@ int mips_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_MIPS_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/ppc/unicorn.c b/qemu/target/ppc/unicorn.c index b157ce5a..fc8e24f1 100644 --- a/qemu/target/ppc/unicorn.c +++ b/qemu/target/ppc/unicorn.c @@ -361,7 +361,7 @@ int ppc_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_PPC_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/riscv/unicorn.c b/qemu/target/riscv/unicorn.c index a440a6bd..8970051e 100644 --- a/qemu/target/riscv/unicorn.c +++ b/qemu/target/riscv/unicorn.c @@ -560,7 +560,7 @@ int riscv_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, if (regid == UC_RISCV_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/s390x/unicorn.c b/qemu/target/s390x/unicorn.c index 469cda7c..6378fe4c 100644 --- a/qemu/target/s390x/unicorn.c +++ b/qemu/target/s390x/unicorn.c @@ -130,7 +130,7 @@ static int s390_reg_write(struct uc_struct *uc, unsigned int *regs, if (regid == UC_S390X_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/qemu/target/tricore/unicorn.c b/qemu/target/tricore/unicorn.c index 88e937bb..a3eac632 100644 --- a/qemu/target/tricore/unicorn.c +++ b/qemu/target/tricore/unicorn.c @@ -229,7 +229,7 @@ int tricore_reg_write(struct uc_struct *uc, unsigned int *regs, if (regid == UC_TRICORE_REG_PC) { // force to quit execution and flush TB uc->quit_request = true; - uc_emu_stop(uc); + break_translation_loop(uc); } } diff --git a/tests/unit/test_ctl.c b/tests/unit/test_ctl.c index 05f4c54e..e1e467d3 100644 --- a/tests/unit/test_ctl.c +++ b/tests/unit/test_ctl.c @@ -304,6 +304,40 @@ static void test_uc_hook_cached_uaf(void) #endif } +static void test_uc_emu_stop_set_ip_callback(uc_engine *uc, uint64_t address, uint32_t size, void *userdata) +{ + uint64_t rip = code_start + 0xb; + + if (address == code_start + 0x7) { + uc_emu_stop(uc); + uc_reg_write(uc, UC_X86_REG_RIP, &rip); + } +} + +static void test_uc_emu_stop_set_ip(void) +{ + uc_engine *uc; + uc_hook h; + uint64_t rip; + + char code[] = "\x48\x31\xc0" // 0x0 xor rax, rax : rax = 0 + "\x90" // 0x3 nop : + "\x48\xff\xc0" // 0x4 inc rax : rax++ + "\x90" // 0x7 nop : <-- going to stop here + "\x48\xff\xc0" // 0x8 inc rax : rax++ + "\x90" // 0xb nop : + "\x0f\x0b" // 0xc ud2 : <-- will raise UC_ERR_INSN_INVALID, but should not never be reached + "\x90" // 0xe nop : + "\x90"; // 0xf nop : + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code) - 1); + OK(uc_hook_add(uc, &h, UC_HOOK_CODE, test_uc_emu_stop_set_ip_callback, NULL, 1, 0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &rip)); + TEST_CHECK(rip == code_start + 0xb); + OK(uc_close(uc)); +} + TEST_LIST = {{"test_uc_ctl_mode", test_uc_ctl_mode}, {"test_uc_ctl_page_size", test_uc_ctl_page_size}, {"test_uc_ctl_arch", test_uc_ctl_arch}, @@ -315,4 +349,5 @@ TEST_LIST = {{"test_uc_ctl_mode", test_uc_ctl_mode}, {"test_uc_ctl_arm_cpu", test_uc_ctl_arm_cpu}, #endif {"test_uc_hook_cached_uaf", test_uc_hook_cached_uaf}, + {"test_uc_emu_stop_set_ip", test_uc_emu_stop_set_ip}, {NULL, NULL}}; diff --git a/uc.c b/uc.c index 829dda95..76c4eb28 100644 --- a/uc.c +++ b/uc.c @@ -907,19 +907,8 @@ UNICORN_EXPORT uc_err uc_emu_stop(uc_engine *uc) { UC_INIT(uc); - - if (uc->emulation_done) { - return UC_ERR_OK; - } - uc->stop_request = true; - // TODO: make this atomic somehow? - if (uc->cpu) { - // exit the current TB - cpu_exit(uc->cpu); - } - - return UC_ERR_OK; + return break_translation_loop(uc); } // return target index where a memory region at the address exists, or could be