Add a new hook type UC_HOOK_TCG_OPCODE
This commit is contained in:
parent
eb75d459f0
commit
bcf85be86d
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
64
uc.c
64
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user