/* insert inline code to add a memory reference info entry into the buffer */ static void instrument_mem(void *drcontext, instrlist_t *ilist, instr_t *where, opnd_t ref, bool write) { /* We need two scratch registers */ reg_id_t reg_ptr, reg_tmp; if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_ptr) != DRREG_SUCCESS || drreg_reserve_register(drcontext, ilist, where, NULL, ®_tmp) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ return; } /* save_addr should be called first as reg_ptr or reg_tmp maybe used in ref */ insert_save_addr(drcontext, ilist, where, ref, reg_ptr, reg_tmp); insert_save_type(drcontext, ilist, where, reg_ptr, reg_tmp, write ? REF_TYPE_WRITE : REF_TYPE_READ); insert_save_size(drcontext, ilist, where, reg_ptr, reg_tmp, (ushort)drutil_opnd_mem_size_in_bytes(ref, where)); insert_update_buf_ptr(drcontext, ilist, where, reg_ptr, sizeof(mem_ref_t)); /* Restore scratch registers */ if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS || drreg_unreserve_register(drcontext, ilist, where, reg_tmp) != DRREG_SUCCESS) DR_ASSERT(false); }
static void instrument_post_write(void *drcontext, instrlist_t *ilist, instr_t *where, opnd_t memref, instr_t *write, reg_id_t reg_addr) { reg_id_t reg_ptr; ushort stride = (ushort)drutil_opnd_mem_size_in_bytes(memref, write); /* We want to use the same predicate as write when inserting the following * instrumentation. */ instrlist_set_auto_predicate(ilist, instr_get_predicate(write)); if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_ptr) != DRREG_SUCCESS) { DR_ASSERT(false); return; } drx_buf_insert_load_buf_ptr(drcontext, write_buffer, ilist, where, reg_ptr); /* drx_buf_insert_buf_memcpy() internally updates the buffer pointer */ drx_buf_insert_buf_memcpy(drcontext, write_buffer, ilist, where, reg_ptr, reg_addr, stride); if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS) DR_ASSERT(false); if (drreg_unreserve_register(drcontext, ilist, where, reg_addr) != DRREG_SUCCESS) DR_ASSERT(false); /* Set the predicate back to the default */ instrlist_set_auto_predicate(ilist, instr_get_predicate(where)); }
/* insert inline code to add an instruction entry into the buffer */ static void instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where) { /* We need two scratch registers */ reg_id_t reg_ptr, reg_tmp; /* we don't want to predicate this, because an instruction fetch always occurs */ instrlist_set_auto_predicate(ilist, DR_PRED_NONE); if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_ptr) != DRREG_SUCCESS || drreg_reserve_register(drcontext, ilist, where, NULL, ®_tmp) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ return; } insert_load_buf_ptr(drcontext, ilist, where, reg_ptr); insert_save_type(drcontext, ilist, where, reg_ptr, reg_tmp, (ushort)instr_get_opcode(where)); insert_save_size(drcontext, ilist, where, reg_ptr, reg_tmp, (ushort)instr_length(drcontext, where)); insert_save_pc(drcontext, ilist, where, reg_ptr, reg_tmp, instr_get_app_pc(where)); insert_update_buf_ptr(drcontext, ilist, where, reg_ptr, sizeof(mem_ref_t)); /* Restore scratch registers */ if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS || drreg_unreserve_register(drcontext, ilist, where, reg_tmp) != DRREG_SUCCESS) DR_ASSERT(false); instrlist_set_auto_predicate(ilist, instr_get_predicate(where)); }
/* * Function that tests a single operand of an instruction to see if * it's a memory reference, and if so, adds a call to log_mem. */ static void try_mem_opnd( void *drcontext, instrlist_t *bb, instr_t *instr, char **loc, opnd_t opnd, bool write) { if (!opnd_is_memory_reference(opnd)) return; instr_format_location(instr, loc); reg_id_t r0, r1; drreg_status_t st; st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); DR_ASSERT(st == DRREG_SUCCESS); st = drreg_reserve_register(drcontext, bb, instr, NULL, &r1); DR_ASSERT(st == DRREG_SUCCESS); bool ok = drutil_insert_get_mem_addr(drcontext, bb, instr, opnd, r0, r1); DR_ASSERT(ok); uint size = drutil_opnd_mem_size_in_bytes(opnd, instr); dr_insert_clean_call( drcontext, bb, instr, (void *)log_mem, false, 4, opnd_create_reg(r0), OPND_CREATE_INT32(size), OPND_CREATE_INT32(write), OPND_CREATE_INTPTR(*loc)); st = drreg_unreserve_register(drcontext, bb, instr, r1); DR_ASSERT(st == DRREG_SUCCESS); st = drreg_unreserve_register(drcontext, bb, instr, r0); DR_ASSERT(st == DRREG_SUCCESS); }
static dr_emit_flags_t event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst, bool for_trace, bool translating, void *user_data) { app_pc pc = dr_fragment_app_pc(tag); reg_id_t reg; /* We need a 2nd scratch reg for several operations on AArch32 and AArch64 only. */ reg_id_t reg2 = DR_REG_NULL; /* We do all our work at the start of the block prior to the first instr */ if (!drmgr_is_first_instr(drcontext, inst)) return DR_EMIT_DEFAULT; /* We need a scratch register */ if (drreg_reserve_register(drcontext, bb, inst, NULL, ®) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ return DR_EMIT_DEFAULT; } #ifdef AARCHXX /* We need a second register here, because the drx_buf routines need a scratch reg * for AArch32 and AArch64. */ if (drreg_reserve_register(drcontext, bb, inst, NULL, ®2) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ return DR_EMIT_DEFAULT; } #endif /* load buffer pointer from TLS field */ drx_buf_insert_load_buf_ptr(drcontext, buf, bb, inst, reg); /* store bb's start pc into the buffer */ drx_buf_insert_buf_store(drcontext, buf, bb, inst, reg, reg2, OPND_CREATE_INTPTR(pc), OPSZ_PTR, 0); /* Internally this will update the TLS buffer pointer by incrementing just the bottom * 16 bits of the pointer. */ drx_buf_insert_update_buf_ptr(drcontext, buf, bb, inst, reg, reg2, sizeof(app_pc)); if (drreg_unreserve_register(drcontext, bb, inst, reg) != DRREG_SUCCESS) DR_ASSERT(false); #ifdef AARCHXX if (drreg_unreserve_register(drcontext, bb, inst, reg2) != DRREG_SUCCESS) DR_ASSERT(false); #endif return DR_EMIT_DEFAULT; }
static dr_emit_flags_t event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, bool for_trace, bool translating, void *user_data) { /* We test reserving across app instrs by reserving on each store and * unreserving on the subsequent instr. */ drvector_t allowed; if (reg != DR_REG_NULL) { if (drreg_unreserve_register(drcontext, bb, instr, reg) != DRREG_SUCCESS) CHECK(false, "failed to unreserve"); reg = DR_REG_NULL; } if (!instr_is_app(instr)) return DR_EMIT_DEFAULT; if (!instr_writes_memory(instr)) return DR_EMIT_DEFAULT; if (!drmgr_is_last_instr(drcontext, instr)) { drreg_init_and_fill_vector(&allowed, true); /* Limit the registers for more of a stress test: */ drreg_set_vector_entry(&allowed, IF_X86_ELSE(DR_REG_XCX, DR_REG_R0), false); drreg_set_vector_entry(&allowed, IF_X86_ELSE(DR_REG_XDX, DR_REG_R1), false); if (drreg_reserve_register(drcontext, bb, instr, &allowed, ®) != DRREG_SUCCESS) DR_ASSERT(false); drvector_delete(&allowed); } return DR_EMIT_DEFAULT; }
static void insert_counter_update(void *drcontext, instrlist_t *bb, instr_t *where, int offset) { /* Since the inc instruction clobbers 5 of the arithmetic eflags, * we have to save them around the inc. We could be more efficient * by not bothering to save the overflow flag and constructing our * own sequence of instructions to save the other 5 flags (using * lahf). */ if (drreg_reserve_aflags(drcontext, bb, where) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ return; } /* Increment the global counter using the lock prefix to make it atomic * across threads. It would be cheaper to aggregate the thread counters * in the exit events, but this sample is intended to illustrate inserted * instrumentation. */ instrlist_meta_preinsert( bb, where, LOCK(INSTR_CREATE_inc( drcontext, OPND_CREATE_ABSMEM(((byte *)&global_count) + offset, OPSZ_4)))); /* Increment the thread private counter. */ if (dr_using_all_private_caches()) { per_thread_t *data = (per_thread_t *)drmgr_get_tls_field(drcontext, tls_idx); /* private caches - we can use an absolute address */ instrlist_meta_preinsert( bb, where, INSTR_CREATE_inc(drcontext, OPND_CREATE_ABSMEM(((byte *)&data) + offset, OPSZ_4))); } else { /* shared caches - we must indirect via thread local storage */ reg_id_t scratch; if (drreg_reserve_register(drcontext, bb, where, NULL, &scratch) != DRREG_SUCCESS) DR_ASSERT(false); drmgr_insert_read_tls_field(drcontext, tls_idx, bb, where, scratch); instrlist_meta_preinsert( bb, where, INSTR_CREATE_inc(drcontext, OPND_CREATE_MEM32(scratch, offset))); if (drreg_unreserve_register(drcontext, bb, where, scratch) != DRREG_SUCCESS) DR_ASSERT(false); } if (drreg_unreserve_aflags(drcontext, bb, where) != DRREG_SUCCESS) DR_ASSERT(false); /* cannot recover */ }
/* instrument_instr is called whenever a memory reference is identified. * It inserts code before the memory reference to to fill the memory buffer * and jump to our own code cache to call the clean_call when the buffer is full. */ static void instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where) { instr_t *instr, *call, *restore; opnd_t opnd1, opnd2; reg_id_t reg1, reg2; drvector_t allowed; per_thread_t *data; app_pc pc; data = drmgr_get_tls_field(drcontext, tls_index); /* Steal two scratch registers. * reg2 must be ECX or RCX for jecxz. */ drreg_init_and_fill_vector(&allowed, false); drreg_set_vector_entry(&allowed, DR_REG_XCX, true); if (drreg_reserve_register(drcontext, ilist, where, &allowed, ®2) != DRREG_SUCCESS || drreg_reserve_register(drcontext, ilist, where, NULL, ®1) != DRREG_SUCCESS) { DR_ASSERT(false); /* cannot recover */ drvector_delete(&allowed); return; } drvector_delete(&allowed); /* The following assembly performs the following instructions * buf_ptr->pc = pc; * buf_ptr->opcode = opcode; * buf_ptr++; * if (buf_ptr >= buf_end_ptr) * clean_call(); */ drmgr_insert_read_tls_field(drcontext, tls_index, ilist, where, reg2); /* Load data->buf_ptr into reg2 */ opnd1 = opnd_create_reg(reg2); opnd2 = OPND_CREATE_MEMPTR(reg2, offsetof(per_thread_t, buf_ptr)); instr = INSTR_CREATE_mov_ld(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* Store pc */ pc = instr_get_app_pc(where); /* For 64-bit, we can't use a 64-bit immediate so we split pc into two halves. * We could alternatively load it into reg1 and then store reg1. * We use a convenience routine that does the two-step store for us. */ opnd1 = OPND_CREATE_MEMPTR(reg2, offsetof(ins_ref_t, pc)); instrlist_insert_mov_immed_ptrsz(drcontext, (ptr_int_t) pc, opnd1, ilist, where, NULL, NULL); /* Store opcode */ opnd1 = OPND_CREATE_MEMPTR(reg2, offsetof(ins_ref_t, opcode)); opnd2 = OPND_CREATE_INT32(instr_get_opcode(where)); instr = INSTR_CREATE_mov_st(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* Increment reg value by pointer size using lea instr */ opnd1 = opnd_create_reg(reg2); opnd2 = opnd_create_base_disp(reg2, DR_REG_NULL, 0, sizeof(ins_ref_t), OPSZ_lea); instr = INSTR_CREATE_lea(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* Update the data->buf_ptr */ drmgr_insert_read_tls_field(drcontext, tls_index, ilist, where, reg1); opnd1 = OPND_CREATE_MEMPTR(reg1, offsetof(per_thread_t, buf_ptr)); opnd2 = opnd_create_reg(reg2); instr = INSTR_CREATE_mov_st(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* We use the lea + jecxz trick for better performance. * lea and jecxz won't disturb the eflags, so we won't need * code to save and restore the application's eflags. */ /* lea [reg2 - buf_end] => reg2 */ opnd1 = opnd_create_reg(reg1); opnd2 = OPND_CREATE_MEMPTR(reg1, offsetof(per_thread_t, buf_end)); instr = INSTR_CREATE_mov_ld(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); opnd1 = opnd_create_reg(reg2); opnd2 = opnd_create_base_disp(reg1, reg2, 1, 0, OPSZ_lea); instr = INSTR_CREATE_lea(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* jecxz call */ call = INSTR_CREATE_label(drcontext); opnd1 = opnd_create_instr(call); instr = INSTR_CREATE_jecxz(drcontext, opnd1); instrlist_meta_preinsert(ilist, where, instr); /* jump restore to skip clean call */ restore = INSTR_CREATE_label(drcontext); opnd1 = opnd_create_instr(restore); instr = INSTR_CREATE_jmp(drcontext, opnd1); instrlist_meta_preinsert(ilist, where, instr); /* clean call */ /* We jump to our generated lean procedure which performs a full context * switch and clean call invocation. This is to reduce the code cache size. */ instrlist_meta_preinsert(ilist, where, call); /* mov restore DR_REG_XCX */ opnd1 = opnd_create_reg(reg2); /* This is the return address for jumping back from the lean procedure. */ opnd2 = opnd_create_instr(restore); /* We could use instrlist_insert_mov_instr_addr(), but with a register * destination we know we can use a 64-bit immediate. */ instr = INSTR_CREATE_mov_imm(drcontext, opnd1, opnd2); instrlist_meta_preinsert(ilist, where, instr); /* jmp code_cache */ opnd1 = opnd_create_pc(code_cache); instr = INSTR_CREATE_jmp(drcontext, opnd1); instrlist_meta_preinsert(ilist, where, instr); /* Restore scratch registers */ instrlist_meta_preinsert(ilist, where, restore); if (drreg_unreserve_register(drcontext, ilist, where, reg1) != DRREG_SUCCESS || drreg_unreserve_register(drcontext, ilist, where, reg2) != DRREG_SUCCESS) DR_ASSERT(false); }
static reg_id_t instrument_mem(void *drcontext, instrlist_t *ilist, instr_t *where, opnd_t ref) { reg_id_t reg_ptr, reg_tmp, reg_addr; ushort type, size; bool ok; if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_tmp) != DRREG_SUCCESS) { DR_ASSERT(false); return DR_REG_NULL; } if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_ptr) != DRREG_SUCCESS) { DR_ASSERT(false); return DR_REG_NULL; } /* i#2449: In the situation that instrument_post_write, instrument_mem and ref all * have the same register reserved, drutil_insert_get_mem_addr will compute the * address of an operand using an incorrect register value, as drreg will elide the * save/restore. */ if (opnd_uses_reg(ref, reg_tmp) && drreg_get_app_value(drcontext, ilist, where, reg_tmp, reg_tmp) != DRREG_SUCCESS) { DR_ASSERT(false); return DR_REG_NULL; } if (opnd_uses_reg(ref, reg_ptr) && drreg_get_app_value(drcontext, ilist, where, reg_ptr, reg_ptr) != DRREG_SUCCESS) { DR_ASSERT(false); return DR_REG_NULL; } /* We use reg_ptr as scratch to get addr. Note we do this first as reg_ptr or reg_tmp * may be used in ref. */ ok = drutil_insert_get_mem_addr(drcontext, ilist, where, ref, reg_tmp, reg_ptr); DR_ASSERT(ok); drx_buf_insert_load_buf_ptr(drcontext, trace_buffer, ilist, where, reg_ptr); /* inserts memref addr */ drx_buf_insert_buf_store(drcontext, trace_buffer, ilist, where, reg_ptr, DR_REG_NULL, opnd_create_reg(reg_tmp), OPSZ_PTR, offsetof(mem_ref_t, addr)); if (IF_AARCHXX_ELSE(true, false)) { /* At this point we save the write address for later, because reg_tmp's value * will get clobbered on ARM. */ if (drreg_reserve_register(drcontext, ilist, where, NULL, ®_addr) != DRREG_SUCCESS) { DR_ASSERT(false); return DR_REG_NULL; } MINSERT(ilist, where, XINST_CREATE_move(drcontext, opnd_create_reg(reg_addr), opnd_create_reg(reg_tmp))); } /* inserts type */ type = (ushort)instr_get_opcode(where); drx_buf_insert_buf_store(drcontext, trace_buffer, ilist, where, reg_ptr, reg_tmp, OPND_CREATE_INT16(type), OPSZ_2, offsetof(mem_ref_t, type)); /* inserts size */ size = (ushort)drutil_opnd_mem_size_in_bytes(ref, where); drx_buf_insert_buf_store(drcontext, trace_buffer, ilist, where, reg_ptr, reg_tmp, OPND_CREATE_INT16(size), OPSZ_2, offsetof(mem_ref_t, size)); drx_buf_insert_update_buf_ptr(drcontext, trace_buffer, ilist, where, reg_ptr, DR_REG_NULL, sizeof(mem_ref_t)); if (instr_is_call(where)) { app_pc pc; /* Note that on ARM the call instruction writes only to the link register, so * we would never even get into instrument_mem() on ARM if this was a call. */ IF_AARCHXX(DR_ASSERT(false)); /* We simulate the call instruction's written memory by writing the next app_pc * to the written buffer, since we can't do this after the call has happened. */ drx_buf_insert_load_buf_ptr(drcontext, write_buffer, ilist, where, reg_ptr); pc = decode_next_pc(drcontext, instr_get_app_pc(where)); /* note that for a circular buffer, we don't need to specify a scratch register */ drx_buf_insert_buf_store(drcontext, trace_buffer, ilist, where, reg_ptr, DR_REG_NULL, OPND_CREATE_INTPTR((ptr_int_t)pc), OPSZ_PTR, 0); drx_buf_insert_update_buf_ptr(drcontext, write_buffer, ilist, where, reg_ptr, reg_tmp, sizeof(app_pc)); /* we don't need to persist reg_tmp to the next instruction */ if (drreg_unreserve_register(drcontext, ilist, where, reg_tmp) != DRREG_SUCCESS) DR_ASSERT(false); reg_tmp = DR_REG_NULL; } else if (IF_AARCHXX_ELSE(true, false)) { /* Now reg_tmp has the address of the write again. */ MINSERT(ilist, where, XINST_CREATE_move(drcontext, opnd_create_reg(reg_tmp), opnd_create_reg(reg_addr))); if (drreg_unreserve_register(drcontext, ilist, where, reg_addr) != DRREG_SUCCESS) DR_ASSERT(false); } if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS) DR_ASSERT(false); return reg_tmp; }
/* * The main function called to instrument each machine instruction. */ static dr_emit_flags_t instrument_instr( void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, bool for_trace, bool translating, void *user_data) { char *loc = NULL; /* * If this instruction is the first in its basic block, call * log_pc to record that we're executing this block at all. */ if (drmgr_is_first_instr(drcontext, instr)) { instr_format_location(instr, &loc); dr_insert_clean_call( drcontext, bb, instr, (void *)log_pc, false, 1, OPND_CREATE_INTPTR(loc)); } /* * If the instruction reads or writes memory, log its access. */ if (instr_reads_memory(instr) || instr_writes_memory(instr)) { for (int i = 0, limit = instr_num_srcs(instr); i < limit; i++) try_mem_opnd(drcontext, bb, instr, &loc, instr_get_src(instr, i), false); for (int i = 0, limit = instr_num_dsts(instr); i < limit; i++) try_mem_opnd(drcontext, bb, instr, &loc, instr_get_dst(instr, i), false); } /* * Now do opcode-specific checks. */ int opcode = instr_get_opcode(instr); switch (opcode) { case OP_div: case OP_idiv: /* * x86 hardware divisions. The operand order for DR's * representation of these seem to be: 0 = denominator, 1 = * numerator MSW, 2 = numerator LSW. */ instr_format_location(instr, &loc); dr_insert_clean_call( drcontext, bb, instr, (void *)log_div, false, 3, instr_get_src(instr, 2), instr_get_src(instr, 0), OPND_CREATE_INTPTR(loc)); break; case OP_shl: case OP_shr: case OP_sar: case OP_shlx: case OP_shrx: case OP_sarx: case OP_rol: case OP_ror: case OP_rcl: case OP_rcr: /* * Shift instructions. If they're register-controlled, log the * shift count. */ { opnd_t shiftcount = instr_get_src(instr, 0); if (!opnd_is_immed(shiftcount)) { reg_id_t r0; drreg_status_t st; st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); DR_ASSERT(st == DRREG_SUCCESS); opnd_t op_r0 = opnd_create_reg(r0); instrlist_preinsert(bb, instr, INSTR_CREATE_movzx( drcontext, op_r0, shiftcount)); instr_format_location(instr, &loc); dr_insert_clean_call( drcontext, bb, instr, (void *)log_var_shift, false, 2, op_r0, OPND_CREATE_INTPTR(loc)); st = drreg_unreserve_register(drcontext, bb, instr, r0); DR_ASSERT(st == DRREG_SUCCESS); } } break; } return DR_EMIT_DEFAULT; }