From 7737e7b4361cd72f7c8aceea37791818fd479e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20T=C3=B4rres?= Date: Fri, 20 Dec 2024 01:17:23 -0800 Subject: [PATCH] make i386 instructions RDTSC and RDTSCP hookable (#2066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * instruction hooks for RDTSC and RDTSCP Signed-off-by: Pedro Tôrres * update hookable instruction list Signed-off-by: Pedro Tôrres * test RDTSC and RDTSCP instruction hooks Signed-off-by: Pedro Tôrres --------- Signed-off-by: Pedro Tôrres Co-authored-by: mio --- bindings/go/unicorn/hook.c | 4 ++ bindings/go/unicorn/hook.go | 8 ++++ bindings/go/unicorn/hook.h | 1 + qemu/target/i386/misc_helper.c | 68 ++++++++++++++++++++++++-- qemu/target/i386/unicorn.c | 5 +- tests/unit/test_x86.c | 88 ++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/bindings/go/unicorn/hook.c b/bindings/go/unicorn/hook.c index a2b7dc93..9018425a 100644 --- a/bindings/go/unicorn/hook.c +++ b/bindings/go/unicorn/hook.c @@ -40,3 +40,7 @@ void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t va void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user) { hookX86Syscall(handle, (void *)user); } + +int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user) { + return hookX86Cpuid(handle, (void *)user); +} diff --git a/bindings/go/unicorn/hook.go b/bindings/go/unicorn/hook.go index 0d7fd681..ecd958f9 100644 --- a/bindings/go/unicorn/hook.go +++ b/bindings/go/unicorn/hook.go @@ -98,6 +98,12 @@ func hookX86Syscall(handle unsafe.Pointer, user unsafe.Pointer) { hook.Callback.(func(Unicorn))(hook.Uc) } +//export hookX86Cpuid +func hookX86Cpuid(handle unsafe.Pointer, user unsafe.Pointer) bool { + hook := hookMap.get(user) + return hook.Callback.(func(Unicorn) bool)(hook.Uc) +} + func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int) (Hook, error) { var callback unsafe.Pointer var insn C.int @@ -119,6 +125,8 @@ func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int) callback = C.hookX86Out_cgo case X86_INS_SYSCALL, X86_INS_SYSENTER: callback = C.hookX86Syscall_cgo + case X86_INS_CPUID, X86_INS_RDTSC, X86_INS_RDTSCP: + callback = C.hookX86Cpuid_cgo default: return 0, errors.New("Unknown instruction type.") } diff --git a/bindings/go/unicorn/hook.h b/bindings/go/unicorn/hook.h index 35813a0a..952a1d9e 100644 --- a/bindings/go/unicorn/hook.h +++ b/bindings/go/unicorn/hook.h @@ -7,3 +7,4 @@ void hookInterrupt_cgo(uc_engine *handle, uint32_t intno, uintptr_t user); uint32_t hookX86In_cgo(uc_engine *handle, uint32_t port, uint32_t size, uintptr_t user); void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t value, uintptr_t user); void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user); +int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user); diff --git a/qemu/target/i386/misc_helper.c b/qemu/target/i386/misc_helper.c index 25933212..a8aec2bf 100644 --- a/qemu/target/i386/misc_helper.c +++ b/qemu/target/i386/misc_helper.c @@ -209,21 +209,79 @@ void helper_invlpg(CPUX86State *env, target_ulong addr) void helper_rdtsc(CPUX86State *env) { uint64_t val; + uc_engine *uc = env->uc; + struct hook *hook; + int skip_rdtsc = 0; if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) { raise_exception_ra(env, EXCP0D_GPF, GETPC()); } cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC()); - val = cpu_get_tsc(env) + env->tsc_offset; - env->regs[R_EAX] = (uint32_t)(val); - env->regs[R_EDX] = (uint32_t)(val >> 32); + // Unicorn: call registered RDTSC hooks + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { + if (hook->to_delete) + continue; + if (!HOOK_BOUND_CHECK(hook, env->eip)) + continue; + + // Multiple rdtsc callbacks returning different values is undefined. + // true -> skip the rdtsc instruction + if (hook->insn == UC_X86_INS_RDTSC) { + JIT_CALLBACK_GUARD_VAR(skip_rdtsc, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data)); + } + + // the last callback may already asked to stop emulation + if (env->uc->stop_request) + break; + } + + if (!skip_rdtsc) { + val = cpu_get_tsc(env) + env->tsc_offset; + env->regs[R_EAX] = (uint32_t)(val); + env->regs[R_EDX] = (uint32_t)(val >> 32); + } } void helper_rdtscp(CPUX86State *env) { - helper_rdtsc(env); - env->regs[R_ECX] = (uint32_t)(env->tsc_aux); + uint64_t val; + uc_engine *uc = env->uc; + struct hook *hook; + int skip_rdtscp = 0; + + if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) { + raise_exception_ra(env, EXCP0D_GPF, GETPC()); + } + cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC()); + + // Unicorn: call registered RDTSCP hooks + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { + if (hook->to_delete) + continue; + if (!HOOK_BOUND_CHECK(hook, env->eip)) + continue; + + // Multiple rdtscp callbacks returning different values is undefined. + // true -> skip the rdtscp instruction + if (hook->insn == UC_X86_INS_RDTSCP) { + JIT_CALLBACK_GUARD_VAR(skip_rdtscp, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data)); + } + + // the last callback may already asked to stop emulation + if (env->uc->stop_request) + break; + } + + if (!skip_rdtscp) { + val = cpu_get_tsc(env) + env->tsc_offset; + env->regs[R_EAX] = (uint32_t)(val); + env->regs[R_EDX] = (uint32_t)(val >> 32); + + env->regs[R_ECX] = (uint32_t)(env->tsc_aux); + } } void helper_rdpmc(CPUX86State *env) diff --git a/qemu/target/i386/unicorn.c b/qemu/target/i386/unicorn.c index 3d6968b9..a694af46 100644 --- a/qemu/target/i386/unicorn.c +++ b/qemu/target/i386/unicorn.c @@ -2000,10 +2000,11 @@ static bool x86_stop_interrupt(struct uc_struct *uc, int intno) static bool x86_insn_hook_validate(uint32_t insn_enum) { - // for x86 we can only hook IN, OUT, and SYSCALL + // for x86 we can only hook IN, OUT, SYSCALL, SYSENTER, CPUID, RDTSC, and RDTSCP if (insn_enum != UC_X86_INS_IN && insn_enum != UC_X86_INS_OUT && insn_enum != UC_X86_INS_SYSCALL && insn_enum != UC_X86_INS_SYSENTER && - insn_enum != UC_X86_INS_CPUID) { + insn_enum != UC_X86_INS_CPUID && insn_enum != UC_X86_INS_RDTSC && + insn_enum != UC_X86_INS_RDTSCP) { return false; } return true; diff --git a/tests/unit/test_x86.c b/tests/unit/test_x86.c index 3eee30e8..079e9863 100644 --- a/tests/unit/test_x86.c +++ b/tests/unit/test_x86.c @@ -1891,6 +1891,92 @@ static void test_x86_ro_segfault(void) OK(uc_close(uc)); } +static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data) +{ + uint64_t h = 0x00000000FEDCBA98; + OK(uc_reg_write(uc, UC_X86_REG_RDX, &h)); + + uint64_t l = 0x0000000076543210; + OK(uc_reg_write(uc, UC_X86_REG_RAX, &l)); + + return true; +} + +static void test_x86_hook_insn_rdtsc(void) +{ + char code[] = "\x0F\x31"; // RDTSC + + uc_engine *uc; + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof code - 1); + + uc_hook hook; + OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtsc_cb, NULL, + 1, 0, UC_X86_INS_RDTSC)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0)); + + OK(uc_hook_del(uc, hook)); + + uint64_t h = 0; + OK(uc_reg_read(uc, UC_X86_REG_RDX, &h)); + TEST_CHECK(h == 0x00000000FEDCBA98); + + uint64_t l = 0; + OK(uc_reg_read(uc, UC_X86_REG_RAX, &l)); + TEST_CHECK(l == 0x0000000076543210); + + OK(uc_close(uc)); +} + +static bool test_x86_hook_insn_rdtscp_cb(uc_engine *uc, void *user_data) +{ + uint64_t h = 0x0000000001234567; + OK(uc_reg_write(uc, UC_X86_REG_RDX, &h)); + + uint64_t l = 0x0000000089ABCDEF; + OK(uc_reg_write(uc, UC_X86_REG_RAX, &l)); + + uint64_t i = 0x00000000DEADBEEF; + OK(uc_reg_write(uc, UC_X86_REG_RCX, &i)); + + return true; +} + +static void test_x86_hook_insn_rdtscp(void) +{ + uc_engine *uc; + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + + OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL)); + + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + + char code[] = "\x0F\x01\xF9"; // RDTSCP + OK(uc_mem_write(uc, code_start, code, sizeof code - 1)); + + uc_hook hook; + OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtscp_cb, NULL, + 1, 0, UC_X86_INS_RDTSCP)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0)); + + OK(uc_hook_del(uc, hook)); + + uint64_t h = 0; + OK(uc_reg_read(uc, UC_X86_REG_RDX, &h)); + TEST_CHECK(h == 0x0000000001234567); + + uint64_t l = 0; + OK(uc_reg_read(uc, UC_X86_REG_RAX, &l)); + TEST_CHECK(l == 0x0000000089ABCDEF); + + uint64_t i = 0; + OK(uc_reg_read(uc, UC_X86_REG_RCX, &i)); + TEST_CHECK(i == 0x00000000DEADBEEF); + + OK(uc_close(uc)); +} + TEST_LIST = { {"test_x86_in", test_x86_in}, {"test_x86_out", test_x86_out}, @@ -1947,4 +2033,6 @@ TEST_LIST = { {"test_bswap_x64", test_bswap_ax}, {"test_rex_x64", test_rex_x64}, {"test_x86_ro_segfault", test_x86_ro_segfault}, + {"test_x86_hook_insn_rdtsc", test_x86_hook_insn_rdtsc}, + {"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp}, {NULL, NULL}};