From bcf85be86dd407fb74f084e00542ff1adc0df77c Mon Sep 17 00:00:00 2001 From: lazymio Date: Wed, 3 Nov 2021 01:46:24 +0100 Subject: [PATCH] Add a new hook type UC_HOOK_TCG_OPCODE --- include/uc_priv.h | 6 ++++ include/unicorn/unicorn.h | 39 ++++++++++++++++++++- qemu/include/tcg/tcg-op.h | 46 +++++++++++++++++++++++++ qemu/include/tcg/tcg.h | 3 ++ qemu/target/arm/helper.h | 1 + qemu/target/arm/unicorn_arm.c | 14 ++++++++ qemu/target/i386/helper.h | 1 + qemu/target/i386/translate.c | 37 +++++++++++++++++++- qemu/target/i386/unicorn.c | 27 +++++++++++++++ qemu/target/m68k/helper.h | 1 + qemu/target/mips/helper.h | 1 + qemu/target/ppc/helper.h | 1 + qemu/target/riscv/helper.h | 1 + qemu/target/sparc/helper.h | 1 + uc.c | 64 +++++++++++++++++++++++++++++++++++ 15 files changed, 241 insertions(+), 2 deletions(-) diff --git a/include/uc_priv.h b/include/uc_priv.h index 17978aaa..f43e7c5d 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -107,6 +107,8 @@ typedef uint64_t (*uc_mem_redirect_t)(uint64_t address); // validate if Unicorn supports hooking a given instruction typedef bool (*uc_insn_hook_validate)(uint32_t insn_enum); +typedef bool (*uc_opcode_hook_validate_t)(uint32_t op, uint32_t flags); + // init target page typedef void (*uc_target_page_init)(struct uc_struct *); @@ -127,6 +129,8 @@ struct hook { int type; // UC_HOOK_* int insn; // instruction for HOOK_INSN int refs; // reference count to free hook stored in multiple lists + int op; // opcode for HOOK_TCG_OPCODE + int op_flags; // opcode flags for HOOK_TCG_OPCODE bool to_delete; // set to true when the hook is deleted by the user. The // destruction of the hook is delayed. uint64_t begin, end; // only trigger if PC or memory access is in this @@ -158,6 +162,7 @@ typedef enum uc_hook_idx { UC_HOOK_MEM_READ_AFTER_IDX, UC_HOOK_INSN_INVALID_IDX, UC_HOOK_EDGE_GENERATED_IDX, + UC_HOOK_TCG_OPCODE_IDX, UC_HOOK_MAX, } uc_hook_idx; @@ -252,6 +257,7 @@ struct uc_struct { CPUState *cpu; uc_insn_hook_validate insn_hook_validate; + uc_opcode_hook_validate_t opcode_hook_invalidate; MemoryRegion *system_memory; // qemu/exec.c MemoryRegion *system_io; // qemu/exec.c diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index c30f55a6..d606ead1 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -248,6 +248,18 @@ typedef struct uc_tb { typedef void (*uc_hook_edge_gen_t)(uc_engine *uc, uc_tb *cur_tb, uc_tb *prev_tb, void *user_data); +/* + Callback function for tcg opcodes that fits in two arguments. + + @address: Current pc. + @arg1: The first argument. + @arg2: The second argument. +*/ +typedef void (*uc_hook_tcg_op_2)(uc_engine *uc, uint64_t address, uint64_t arg1, + uint64_t arg2, void *user_data); + +typedef uc_hook_tcg_op_2 uc_hook_tcg_sub; + /* Callback function for MMIO read @@ -284,6 +296,26 @@ typedef enum uc_mem_type { UC_MEM_READ_AFTER, // Memory is read from (successful access) } uc_mem_type; +// These are all op codes we support to hook for UC_HOOK_TCG_OP_CODE. +// Be cautious since it may bring much more overhead than UC_HOOK_CODE without +// proper flags. +// TODO: Tracing UC_TCG_OP_CALL should be interesting. +typedef enum uc_tcg_op_code { + UC_TCG_OP_SUB = 0, // Both sub_i32 and sub_i64 +} uc_tcg_op_code; + +// These are extra flags to be paired with uc_tcg_op_code which is helpful to +// instrument in some certain cases. +typedef enum uc_tcg_op_flag { + // Only instrument opcode if one of the arguments is an immediate value. + UC_TCG_OP_FLAG_IMM = 1 << 0, + // Only instrument opcode if it would set cc_dst, i.e. cmp instruction. + UC_TCG_OP_FLAG_CMP = 1 << 1, + // Only instrument opcode which is directly translated. i.e. x86 sub -> tcg + // sub_i32/64 + UC_TCG_OP_FLAG_DIRECT = 1 << 2 +} uc_tcg_op_flag; + // All type of hooks for uc_hook_add() API. typedef enum uc_hook_type { // Hook all interrupt/syscall events @@ -323,7 +355,10 @@ typedef enum uc_hook_type { // NOTE: This is different from UC_HOOK_BLOCK in 2 ways: // 1. The hook is called before executing code. // 2. The hook is only called when generation is triggered. - UC_HOOK_EDGE_GENERATED = 1 << 15 + UC_HOOK_EDGE_GENERATED = 1 << 15, + // Hook on specific tcg op code. The usage of this hook is similar to + // UC_HOOK_INSN. + UC_HOOK_TCG_OPCODE = 1 << 16, } uc_hook_type; // Hook type for all events of unmapped memory access @@ -771,6 +806,8 @@ uc_err uc_emu_stop(uc_engine *uc); @...: variable arguments (depending on @type) NOTE: if @type = UC_HOOK_INSN, this is the instruction ID. currently, only x86 in, out, syscall, sysenter, cpuid are supported. + NOTE: if @type = UC_HOOK_TCG_OPCODE, arguments are @opcode and @flags. See + @uc_tcg_op_code and @uc_tcg_op_flag for details. @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum for detailed error). diff --git a/qemu/include/tcg/tcg-op.h b/qemu/include/tcg/tcg-op.h index 73d1b93f..6aad2505 100644 --- a/qemu/include/tcg/tcg-op.h +++ b/qemu/include/tcg/tcg-op.h @@ -42,6 +42,24 @@ static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t t tcg_temp_free_i32(tcg_ctx, tsize); } +static inline void gen_uc_traceopcode(TCGContext *tcg_ctx, void* hook, TCGv_i64 arg1, TCGv_i64 arg2, void *uc, uint64_t pc) +{ + TCGv_ptr thook = tcg_const_ptr(tcg_ctx, hook); + TCGv_ptr tuc = tcg_const_ptr(tcg_ctx, uc); + TCGv_i64 tpc = tcg_const_i64(tcg_ctx, pc); +// #if TARGET_LONG_BITS == 32 +// TCGv_i64 targ1 = temp_tcgv_i64(tcg_ctx, tcgv_i32_temp(tcg_ctx, arg1)); +// TCGv_i64 targ2 = temp_tcgv_i64(tcg_ctx, tcgv_i32_temp(tcg_ctx, arg2)); +// #else +// TCGv_i64 targ1 = arg1; +// TCGv_i64 targ2 = arg2; +// #endif + gen_helper_uc_traceopcode(tcg_ctx, thook, arg1, arg2, tuc, tpc); + tcg_temp_free_i64(tcg_ctx, tpc); + tcg_temp_free_ptr(tcg_ctx, tuc); + tcg_temp_free_ptr(tcg_ctx, thook); +} + /* Basic output routines. Not for general consumption. */ void tcg_gen_op1(TCGContext *tcg_ctx, TCGOpcode, TCGArg); @@ -422,6 +440,20 @@ static inline void tcg_gen_add_i32(TCGContext *tcg_ctx, TCGv_i32 ret, TCGv_i32 a static inline void tcg_gen_sub_i32(TCGContext *tcg_ctx, TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) { + uc_engine *uc = tcg_ctx->uc; + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_TCG_OPCODE, tcg_ctx->pc_start)) { + struct hook *hook; + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(uc, hook, UC_HOOK_TCG_OPCODE) { + if (hook->to_delete) + continue; + if (hook->op == UC_TCG_OP_SUB && hook->op_flags == 0) { + gen_uc_traceopcode(tcg_ctx, hook, (TCGv_i64)arg1, (TCGv_i64)arg2, uc, tcg_ctx->pc_start); + } + } + } + tcg_gen_op3_i32(tcg_ctx, INDEX_op_sub_i32, ret, arg1, arg2); } @@ -641,6 +673,20 @@ static inline void tcg_gen_add_i64(TCGContext *tcg_ctx, TCGv_i64 ret, TCGv_i64 a static inline void tcg_gen_sub_i64(TCGContext *tcg_ctx, TCGv_i64 ret, TCGv_i64 arg1, TCGv_i64 arg2) { + uc_engine *uc = tcg_ctx->uc; + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_TCG_OPCODE, tcg_ctx->pc_start)) { + struct hook *hook; + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(uc, hook, UC_HOOK_TCG_OPCODE) { + if (hook->to_delete) + continue; + if (hook->op == UC_TCG_OP_SUB && hook->op_flags == 0) { + gen_uc_traceopcode(tcg_ctx, hook, arg1, arg2, uc, tcg_ctx->pc_start); + } + } + } + tcg_gen_op3_i64(tcg_ctx, INDEX_op_sub_i64, ret, arg1, arg2); } diff --git a/qemu/include/tcg/tcg.h b/qemu/include/tcg/tcg.h index f9b87452..a1566e4f 100644 --- a/qemu/include/tcg/tcg.h +++ b/qemu/include/tcg/tcg.h @@ -791,6 +791,9 @@ struct TCGContext { TCGv NULL_QREG; /* Used to distinguish stores from bad addressing modes. */ TCGv store_dummy; + + // Used to store the start of current instrution. + uint64_t pc_start; }; static inline size_t temp_idx(TCGContext *tcg_ctx, TCGTemp *ts) diff --git a/qemu/target/arm/helper.h b/qemu/target/arm/helper.h index 9782f698..f445667b 100644 --- a/qemu/target/arm/helper.h +++ b/qemu/target/arm/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_FLAGS_1(sxtb16, TCG_CALL_NO_RWG_SE, i32, i32) DEF_HELPER_FLAGS_1(uxtb16, TCG_CALL_NO_RWG_SE, i32, i32) diff --git a/qemu/target/arm/unicorn_arm.c b/qemu/target/arm/unicorn_arm.c index 2b30db80..c47b6b66 100644 --- a/qemu/target/arm/unicorn_arm.c +++ b/qemu/target/arm/unicorn_arm.c @@ -477,6 +477,19 @@ static uc_err arm_query(struct uc_struct *uc, uc_query_type type, } } +static bool arm_opcode_hook_invalidate(uint32_t op, uint32_t flags) +{ + if (op != UC_TCG_OP_SUB) { + return false; + } + + if (flags == UC_TCG_OP_FLAG_CMP && op != UC_TCG_OP_SUB) { + return false; + } + + return true; +} + static int arm_cpus_init(struct uc_struct *uc, const char *cpu_model) { ARMCPU *cpu; @@ -503,6 +516,7 @@ void arm_uc_init(struct uc_struct *uc) uc->release = arm_release; uc->query = arm_query; uc->cpus_init = arm_cpus_init; + uc->opcode_hook_invalidate = arm_opcode_hook_invalidate; uc->cpu_context_size = offsetof(CPUARMState, cpu_watchpoint); uc_common_init(uc); } diff --git a/qemu/target/i386/helper.h b/qemu/target/i386/helper.h index acdd64e8..28dadb82 100644 --- a/qemu/target/i386/helper.h +++ b/qemu/target/i386/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_FLAGS_4(cc_compute_all, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl, int) DEF_HELPER_FLAGS_4(cc_compute_c, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl, int) diff --git a/qemu/target/i386/translate.c b/qemu/target/i386/translate.c index 8616b12f..16c7f91f 100644 --- a/qemu/target/i386/translate.c +++ b/qemu/target/i386/translate.c @@ -1479,6 +1479,7 @@ static void gen_illegal_opcode(DisasContext *s) static void gen_op(DisasContext *s1, int op, MemOp ot, int d) { TCGContext *tcg_ctx = s1->uc->tcg_ctx; + uc_engine *uc = s1->uc; if (d != OR_TMP0) { if (s1->prefix & PREFIX_LOCK) { @@ -1542,6 +1543,23 @@ static void gen_op(DisasContext *s1, int op, MemOp ot, int d) tcg_gen_sub_tl(tcg_ctx, s1->T0, s1->T0, s1->T1); gen_op_st_rm_T0_A0(s1, ot, d); } + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_TCG_OPCODE, s1->pc_start)) { + struct hook *hook; + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(uc, hook, UC_HOOK_TCG_OPCODE) { + if (hook->to_delete) + continue; + if (hook->op == UC_TCG_OP_SUB && (hook->op_flags & UC_TCG_OP_FLAG_DIRECT) ) { + // TCGv is just an offset to tcg_ctx so it's safe to do so. + if ( (hook->op_flags & UC_TCG_OP_FLAG_IMM) && d != OR_EAX) { + continue; + } + gen_uc_traceopcode(tcg_ctx, hook, (TCGv_i64)s1->T0, (TCGv_i64)s1->T1, uc, s1->pc_start); + } + } + } + gen_op_update2_cc(s1); set_cc_op(s1, CC_OP_SUBB + ot); break; @@ -1583,6 +1601,23 @@ static void gen_op(DisasContext *s1, int op, MemOp ot, int d) tcg_gen_mov_tl(tcg_ctx, tcg_ctx->cpu_cc_src, s1->T1); tcg_gen_mov_tl(tcg_ctx, s1->cc_srcT, s1->T0); tcg_gen_sub_tl(tcg_ctx, tcg_ctx->cpu_cc_dst, s1->T0, s1->T1); + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_TCG_OPCODE, s1->pc_start)) { + struct hook *hook; + HOOK_FOREACH_VAR_DECLARE; + HOOK_FOREACH(uc, hook, UC_HOOK_TCG_OPCODE) { + if (hook->to_delete) + continue; + if (hook->op == UC_TCG_OP_SUB && (hook->op_flags & UC_TCG_OP_FLAG_CMP) ) { + // TCGv is just an offset to tcg_ctx so it's safe to do so. + if ( (hook->op_flags & UC_TCG_OP_FLAG_IMM) && d != OR_EAX) { + continue; + } + gen_uc_traceopcode(tcg_ctx, hook, (TCGv_i64)s1->T0, (TCGv_i64)s1->T1, uc, s1->pc_start); + } + } + } + set_cc_op(s1, CC_OP_SUBB + ot); break; } @@ -4759,7 +4794,7 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) TCGOp *tcg_op, *prev_op = NULL; bool insn_hook = false; - s->pc_start = s->pc = pc_start; + s->pc_start = tcg_ctx->pc_start = s->pc = pc_start; s->prefix = 0; s->uc = env->uc; diff --git a/qemu/target/i386/unicorn.c b/qemu/target/i386/unicorn.c index 404f8444..74a1ca0e 100644 --- a/qemu/target/i386/unicorn.c +++ b/qemu/target/i386/unicorn.c @@ -1569,6 +1569,32 @@ static bool x86_insn_hook_validate(uint32_t insn_enum) return true; } +static bool x86_opcode_hook_invalidate(uint32_t op, uint32_t flags) +{ + if (op != UC_TCG_OP_SUB) { + return false; + } + + switch (op) { + case UC_TCG_OP_SUB: + + if (flags == UC_TCG_OP_FLAG_IMM) { + return false; + } + + if ((flags & UC_TCG_OP_FLAG_CMP) && (flags & UC_TCG_OP_FLAG_DIRECT)) { + return false; + } + + break; + + default: + return false; + } + + return true; +} + static int x86_cpus_init(struct uc_struct *uc, const char *cpu_model) { @@ -1592,6 +1618,7 @@ void x86_uc_init(struct uc_struct *uc) uc->set_pc = x86_set_pc; uc->stop_interrupt = x86_stop_interrupt; uc->insn_hook_validate = x86_insn_hook_validate; + uc->opcode_hook_invalidate = x86_opcode_hook_invalidate; uc->cpus_init = x86_cpus_init; uc->cpu_context_size = offsetof(CPUX86State, retaddr); uc_common_init(uc); diff --git a/qemu/target/m68k/helper.h b/qemu/target/m68k/helper.h index 04cbbf48..0b333b0e 100644 --- a/qemu/target/m68k/helper.h +++ b/qemu/target/m68k/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_1(bitrev, i32, i32) DEF_HELPER_1(ff1, i32, i32) diff --git a/qemu/target/mips/helper.h b/qemu/target/mips/helper.h index 1ff0c475..51766471 100644 --- a/qemu/target/mips/helper.h +++ b/qemu/target/mips/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_3(raise_exception_err, noreturn, env, i32, int) DEF_HELPER_2(raise_exception, noreturn, env, i32) diff --git a/qemu/target/ppc/helper.h b/qemu/target/ppc/helper.h index 9700011b..8293ebf5 100644 --- a/qemu/target/ppc/helper.h +++ b/qemu/target/ppc/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_FLAGS_3(raise_exception_err, TCG_CALL_NO_WG, void, env, i32, i32) DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_WG, void, env, i32) diff --git a/qemu/target/riscv/helper.h b/qemu/target/riscv/helper.h index fddc79c7..8b273d5e 100644 --- a/qemu/target/riscv/helper.h +++ b/qemu/target/riscv/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) DEF_HELPER_1(uc_riscv_exit, void, env) /* Exceptions */ diff --git a/qemu/target/sparc/helper.h b/qemu/target/sparc/helper.h index 649d9fd1..e019337f 100644 --- a/qemu/target/sparc/helper.h +++ b/qemu/target/sparc/helper.h @@ -1,4 +1,5 @@ DEF_HELPER_4(uc_tracecode, void, i32, i32, ptr, i64) +DEF_HELPER_5(uc_traceopcode, void, ptr, i64, i64, ptr, i64) #ifndef TARGET_SPARC64 DEF_HELPER_1(rett, void, env) diff --git a/uc.c b/uc.c index 076c21f2..3d4cf5c6 100644 --- a/uc.c +++ b/uc.c @@ -1368,6 +1368,37 @@ uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, return UC_ERR_OK; } + if (type & UC_HOOK_TCG_OPCODE) { + va_list valist; + + va_start(valist, end); + hook->op = va_arg(valist, int); + hook->op_flags = va_arg(valist, int); + va_end(valist); + + if (uc->opcode_hook_invalidate) { + if (!uc->opcode_hook_invalidate(hook->op, hook->op_flags)) { + free(hook); + return UC_ERR_HOOK; + } + } + + if (uc->hook_insert) { + if (list_insert(&uc->hook[UC_HOOK_TCG_OPCODE_IDX], hook) == NULL) { + free(hook); + return UC_ERR_NOMEM; + } + } else { + if (list_append(&uc->hook[UC_HOOK_TCG_OPCODE_IDX], hook) == NULL) { + free(hook); + return UC_ERR_NOMEM; + } + } + + hook->refs++; + return UC_ERR_OK; + } + while ((type >> i) > 0) { if ((type >> i) & 1) { // TODO: invalid hook error? @@ -1424,6 +1455,39 @@ uc_err uc_hook_del(uc_engine *uc, uc_hook hh) } // TCG helper +// 2 arguments are enough for most opcodes. Load/Store needs 3 arguments but we +// have memory hooks already. We may exceed the maximum arguments of a tcg +// helper but that's easy to extend. +void helper_uc_traceopcode(struct hook *hook, uint64_t arg1, uint64_t arg2, + void *handle, uint64_t address); +void helper_uc_traceopcode(struct hook *hook, uint64_t arg1, uint64_t arg2, + void *handle, uint64_t address) +{ + struct uc_struct *uc = handle; + + if (unlikely(uc->stop_request)) { + return; + } + + if (unlikely(hook->to_delete)) { + return; + } + + // We did all checks in translation time. + // + // This could optimize the case that we have multiple hooks with different + // opcodes and have one callback per opcode. Note that the assumption don't + // hold in most cases for uc_tracecode. + // + // TODO: Shall we have a flag to allow users to control whether updating PC? + ((uc_hook_tcg_op_2)hook->callback)(uc, address, arg1, arg2, + hook->user_data); + + if (unlikely(uc->stop_request)) { + return; + } +} + void helper_uc_tracecode(int32_t size, uc_hook_idx index, void *handle, int64_t address); void helper_uc_tracecode(int32_t size, uc_hook_idx index, void *handle,