From e5207a13634e2d1e80b3a6ac9061d9293784f4dc Mon Sep 17 00:00:00 2001 From: lazymio Date: Sun, 27 Feb 2022 14:59:52 +0100 Subject: [PATCH] Implement UC_HOOK_INSN for aarch64 MRS/MSR/SYS/SYSL --- include/unicorn/arm64.h | 22 ++++++++ qemu/aarch64.h | 1 + qemu/target/arm/helper.h | 1 + qemu/target/arm/op_helper.c | 35 +++++++++++++ qemu/target/arm/translate-a64.c | 89 +++++++++++++++++++++++++++++++++ symbols.sh | 1 + tests/unit/test_arm64.c | 37 ++++++++++++++ 7 files changed, 186 insertions(+) diff --git a/include/unicorn/arm64.h b/include/unicorn/arm64.h index db07082e..5597ab19 100644 --- a/include/unicorn/arm64.h +++ b/include/unicorn/arm64.h @@ -358,6 +358,28 @@ typedef enum uc_arm64_reg { UC_ARM64_REG_LR = UC_ARM64_REG_X30, } uc_arm64_reg; +// Callback function for tracing MRS/MSR/SYS/SYSL. If this callback returns +// true, the read/write to system registers would be skipped. Note one callback +// per instruction is allowed. +// @reg: The source/destination register. +// @cp_reg: The source/destincation system register. +// @user_data: The user data. +typedef uint32_t (*uc_cb_insn_sys_t)(uc_engine *uc, uc_arm64_reg reg, + const uc_arm64_cp_reg *cp_reg, + void *user_data); + +//> ARM64 instructions +typedef enum uc_arm64_insn { + UC_ARM64_INS_INVALID = 0, + + UC_ARM64_INS_MRS, + UC_ARM64_INS_MSR, + UC_ARM64_INS_SYS, + UC_ARM64_INS_SYSL, + + UC_ARM64_INS_ENDING +} uc_arm64_insn; + #ifdef __cplusplus } #endif diff --git a/qemu/aarch64.h b/qemu/aarch64.h index 12f6833c..7b8b40df 100644 --- a/qemu/aarch64.h +++ b/qemu/aarch64.h @@ -2976,4 +2976,5 @@ #define ssra_op ssra_op_aarch64 #define aarch64_translator_ops aarch64_translator_ops_aarch64 #define pred_esz_masks pred_esz_masks_aarch64 +#define helper_uc_hooksys64 helper_uc_hooksys64_aarch64 #endif diff --git a/qemu/target/arm/helper.h b/qemu/target/arm/helper.h index a9f30ad7..616d032c 100644 --- a/qemu/target/arm/helper.h +++ b/qemu/target/arm/helper.h @@ -83,6 +83,7 @@ DEF_HELPER_3(set_cp_reg, void, env, ptr, i32) DEF_HELPER_2(get_cp_reg, i32, env, ptr) DEF_HELPER_3(set_cp_reg64, void, env, ptr, i64) DEF_HELPER_2(get_cp_reg64, i64, env, ptr) +DEF_HELPER_3(uc_hooksys64, i32, env, i32, ptr) DEF_HELPER_2(get_r13_banked, i32, env, i32) DEF_HELPER_3(set_r13_banked, void, env, i32, i32) diff --git a/qemu/target/arm/op_helper.c b/qemu/target/arm/op_helper.c index 7a1923c7..69eac122 100644 --- a/qemu/target/arm/op_helper.c +++ b/qemu/target/arm/op_helper.c @@ -932,3 +932,38 @@ uint32_t HELPER(ror_cc)(CPUARMState *env, uint32_t x, uint32_t i) return ((uint32_t)x >> shift) | (x << (32 - shift)); } } + +uint32_t HELPER(uc_hooksys64)(CPUARMState *env, uint32_t insn, void *hk) +{ + uc_arm64_reg uc_rt; + struct hook *hook = (struct hook*)hk; + uc_arm64_cp_reg cp_reg; + uint32_t rt; + + if (hook->to_delete) { + return 0; + } + + rt = extract32(insn, 0, 5); + cp_reg.op0 = extract32(insn, 19, 2); + cp_reg.op1 = extract32(insn, 16, 3); + cp_reg.crn = extract32(insn, 12, 4); + cp_reg.crm = extract32(insn, 8, 4); + cp_reg.op2 = extract32(insn, 5, 3); + + if (rt <= 28 && rt >= 0) { + uc_rt = UC_ARM64_REG_X0 + rt; + cp_reg.val = env->xregs[rt]; + } else if (rt == 29 ) { + uc_rt = UC_ARM64_REG_X29; + cp_reg.val = env->xregs[29]; + } else if (rt == 30) { + uc_rt = UC_ARM64_REG_X30; + cp_reg.val = env->xregs[30]; + } else { + uc_rt = UC_ARM64_REG_XZR; + cp_reg.val = 0; + } + + return ((uc_cb_insn_sys_t)(hook->callback))(env->uc, uc_rt, &cp_reg, hook->user_data); +} \ No newline at end of file diff --git a/qemu/target/arm/translate-a64.c b/qemu/target/arm/translate-a64.c index a42a12e0..62f462ec 100644 --- a/qemu/target/arm/translate-a64.c +++ b/qemu/target/arm/translate-a64.c @@ -1748,6 +1748,38 @@ static void gen_set_nzcv(TCGContext *tcg_ctx, TCGv_i64 tcg_rt) tcg_temp_free_i32(tcg_ctx, nzcv); } + +static TCGLabel *gen_hook_sys(DisasContext *s, uint32_t insn, struct hook *hk) +{ + uc_engine *uc = s->uc; + TCGContext *tcg_ctx = uc->tcg_ctx; + TCGLabel *label = gen_new_label(tcg_ctx); + TCGv_i32 tcg_skip, tcg_insn; + TCGv_ptr tcg_hk; + + tcg_skip = tcg_temp_new_i32(tcg_ctx); + tcg_insn = tcg_const_i32(tcg_ctx, insn); + tcg_hk = tcg_const_ptr(tcg_ctx, (void*)hk); + + // Only one hook per instruction for SYS/SYSL/MRS/MSR is allowed. + // This is intended and may be extended if it's really necessary. + gen_helper_uc_hooksys64(tcg_ctx, tcg_skip, tcg_ctx->cpu_env, tcg_insn, tcg_hk); + + tcg_gen_brcondi_i32(tcg_ctx, TCG_COND_NE, tcg_skip, 0, label); + + tcg_temp_free_i32(tcg_ctx, tcg_skip); + tcg_temp_free_i32(tcg_ctx, tcg_insn); + tcg_temp_free_ptr(tcg_ctx, tcg_hk); + + return label; +} + +static void may_gen_set_label(DisasContext *s, TCGLabel *label) { + if (label) { + gen_set_label(s->uc->tcg_ctx, label); + } +} + /* MRS - move from system register * MSR (register) - move to system register * SYS @@ -1762,6 +1794,52 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, TCGContext *tcg_ctx = s->uc->tcg_ctx; const ARMCPRegInfo *ri; TCGv_i64 tcg_rt; + uc_engine *uc = s->uc; + TCGLabel *label = NULL; + struct hook *hook; + HOOK_FOREACH_VAR_DECLARE; + + HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { + if (hook->to_delete) + continue; + + if (!HOOK_BOUND_CHECK(hook, s->pc_curr)) { + continue; + } + + switch (hook->insn) { + case UC_ARM64_INS_MRS: { + if (isread && (op0 == 2 || op0 == 3)) { + label = gen_hook_sys(s, insn, hook); + } + break; + } + case UC_ARM64_INS_MSR: { + if (!isread && (op0 == 2 || op0 == 3)) { + label = gen_hook_sys(s, insn, hook); + } + break; + } + case UC_ARM64_INS_SYSL: { + if (isread && op0 == 1) { + label = gen_hook_sys(s, insn, hook); + } + break; + } + case UC_ARM64_INS_SYS: { + if (!isread && op0 == 1) { + label = gen_hook_sys(s, insn, hook); + } + break; + } + default: + break; + } + + if (label) { + break; + } + } ri = get_arm_cp_reginfo(s->cp_regs, ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP, @@ -1775,12 +1853,14 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, "system register op0:%d op1:%d crn:%d crm:%d op2:%d\n", isread ? "read" : "write", op0, op1, crn, crm, op2); unallocated_encoding(s); + may_gen_set_label(s, label); return; } /* Check access permissions */ if (!cp_access_ok(s->current_el, ri, isread)) { unallocated_encoding(s); + may_gen_set_label(s, label); return; } @@ -1812,6 +1892,7 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, /* Handle special cases first */ switch (ri->type & ~(ARM_CP_FLAG_MASK & ~ARM_CP_SPECIAL)) { case ARM_CP_NOP: + may_gen_set_label(s, label); return; case ARM_CP_NZCV: tcg_rt = cpu_reg(s, rt); @@ -1820,6 +1901,7 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, } else { gen_set_nzcv(tcg_ctx, tcg_rt); } + may_gen_set_label(s, label); return; case ARM_CP_CURRENTEL: /* Reads as current EL value from pstate, which is @@ -1827,18 +1909,22 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, */ tcg_rt = cpu_reg(s, rt); tcg_gen_movi_i64(tcg_ctx, tcg_rt, s->current_el << 2); + may_gen_set_label(s, label); return; case ARM_CP_DC_ZVA: /* Writes clear the aligned block of memory which rt points into. */ tcg_rt = clean_data_tbi(s, cpu_reg(s, rt)); gen_helper_dc_zva(tcg_ctx, tcg_ctx->cpu_env, tcg_rt); + may_gen_set_label(s, label); return; default: break; } if ((ri->type & ARM_CP_FPU) && !fp_access_check(s)) { + may_gen_set_label(s, label); return; } else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) { + may_gen_set_label(s, label); return; } @@ -1858,6 +1944,7 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, } else { if (ri->type & ARM_CP_CONST) { /* If not forbidden by access permissions, treat as WI */ + may_gen_set_label(s, label); return; } else if (ri->writefn) { TCGv_ptr tmpptr; @@ -1888,6 +1975,8 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread, */ s->base.is_jmp = DISAS_UPDATE; } + + may_gen_set_label(s, label); } /* System diff --git a/symbols.sh b/symbols.sh index 5b7bcbd0..bddb11da 100755 --- a/symbols.sh +++ b/symbols.sh @@ -4297,6 +4297,7 @@ usra_op \ ssra_op \ aarch64_translator_ops \ pred_esz_masks \ +helper_uc_hooksys64 \ " riscv32_SYMBOLS=" diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index ac75d34f..5926c84b 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -159,9 +159,46 @@ static void test_arm64_read_sctlr() OK(uc_close(uc)); } +static uint32_t test_arm64_mrs_hook_cb(uc_engine *uc, uc_arm64_reg reg, + const uc_arm64_cp_reg *cp_reg) +{ + uint64_t r_x2 = 0x114514; + + OK(uc_reg_write(uc, reg, &r_x2)); + + // Skip + return 1; +} + +static void test_arm64_mrs_hook() +{ + uc_engine *uc; + uc_hook hk; + uint64_t r_x2; + // mrs x2, tpidrro_el0 + char code[] = "\x62\xd0\x3b\xd5"; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_LITTLE_ENDIAN | UC_MODE_ARM, + code, sizeof(code) - 1, UC_CPU_AARCH64_A72); + + OK(uc_hook_add(uc, &hk, UC_HOOK_INSN, (void *)test_arm64_mrs_hook_cb, NULL, + 1, 0, UC_ARM64_INS_MRS)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + + OK(uc_reg_read(uc, UC_ARM64_REG_X2, &r_x2)); + + TEST_CHECK(r_x2 == 0x114514); + + OK(uc_hook_del(uc, hk)); + + OK(uc_close(uc)); +} + TEST_LIST = {{"test_arm64_until", test_arm64_until}, {"test_arm64_code_patching", test_arm64_code_patching}, {"test_arm64_code_patching_count", test_arm64_code_patching_count}, {"test_arm64_v8_pac", test_arm64_v8_pac}, {"test_arm64_read_sctlr", test_arm64_read_sctlr}, + {"test_arm64_mrs_hook", test_arm64_mrs_hook}, {NULL, NULL}};