| /* ********************************************************** |
| * Copyright (c) 2012-2016 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* Dr. Memory: the memory debugger |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; |
| * version 2.1 of the License, and no later version. |
| |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| /*************************************************************************** |
| * pattern.c: Dr. Memory pattern mode implementation |
| */ |
| |
| #include "dr_api.h" |
| #include "drreg.h" |
| #include "drutil.h" |
| #include "drmemory.h" |
| #include "slowpath.h" |
| #include "spill.h" |
| #include "pattern.h" |
| #include "shadow.h" |
| #include "stack.h" |
| #include "fastpath.h" |
| #include "alloc.h" |
| #include "redblack.h" |
| #include "report.h" |
| #include "alloc_drmem.h" |
| |
| #ifdef UNIX |
| # include <signal.h> /* for SIGSEGV */ |
| #endif |
| |
| /*************************************************************************** |
| * Pattern mode instrumentation functions |
| */ |
| |
| #define MAX_NUM_CHECKS_PER_REF 4 |
| #define MAX_REFS_PER_INSTR 3 |
| #define SWAP_BYTE(x) ((0x0ff & ((x) >> 8)) | ((0x0ff & (x)) << 8)) |
| #define PATTERN_REVERSE(x) (SWAP_BYTE(x) | (SWAP_BYTE(x) << 16)) |
| |
| /* we can use a redblack tree to keep malloc info */ |
| static rb_tree_t *pattern_malloc_tree; |
| static void *pattern_malloc_tree_rwlock; |
| static uint pattern_reverse; |
| static bool pattern_4byte_check_only = false; |
| static void *flush_lock; |
| |
| static int num_2byte_faults = 0; |
| |
| /* check if the opnd should be instrumented for checks */ |
| bool |
| pattern_opnd_needs_check(opnd_t opnd) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| ASSERT(opnd_is_memory_reference(opnd), "not a memory reference"); |
| ASSERT(!options.check_stack_access, "no stack check"); |
| /* We are only interested in heap objects in pattern mode, |
| * so no absolute address or pc relative address. |
| */ |
| if (opnd_is_abs_addr(opnd)) |
| return false; |
| #if defined(X64) || defined(ARM) |
| if (opnd_is_rel_addr(opnd)) |
| return false; |
| #endif |
| #if defined(UNIX) && defined(X86) |
| /* FIXME i#1812: check app TLS accesses. |
| * DynamoRIO steals both TLS segment registers and mangles all app TLS accesses. |
| * To check an app TLS access, we need extra instrumentation to steal a |
| * register for holding app TLS segment base. |
| */ |
| /* Assuming all non-TLS segment bases are 0, we can check those memory |
| * accesses without special handling. |
| */ |
| if (opnd_is_far_base_disp(opnd) && |
| (opnd_get_segment(opnd) == SEG_GS || opnd_get_segment(opnd) == SEG_FS)) |
| return false; |
| #endif |
| ASSERT(opnd_is_base_disp(opnd), "not a base disp opnd"); |
| return true; |
| } |
| |
| #ifdef X86 |
| static void |
| pattern_handle_xlat(void *drcontext, instrlist_t *ilist, instr_t *app, bool pre) |
| { |
| /* xlat accesses memory (xbx, al), which is not a legal memory operand, |
| * and we use (xbx, xax) to emulate (xbx, al) instead: |
| * save xax; movzx xax, al; ...; restore xax; ...; xlat |
| */ |
| IF_DEBUG(drreg_status_t res); |
| if (pre) { |
| drvector_t allowed; |
| reg_id_t scratch; |
| drreg_init_and_fill_vector(&allowed, false); |
| drreg_set_vector_entry(&allowed, DR_REG_XAX, true); |
| IF_DEBUG(res =) |
| drreg_reserve_register(drcontext, ilist, app, &allowed, &scratch); |
| ASSERT(res == DRREG_SUCCESS && scratch == DR_REG_XAX, "failed to reserve eax"); |
| drvector_delete(&allowed); |
| PRE(ilist, app, INSTR_CREATE_movzx(drcontext, |
| opnd_create_reg(DR_REG_XAX), |
| opnd_create_reg(DR_REG_AL))); |
| } else { |
| /* restore xax */ |
| IF_DEBUG(res =) |
| drreg_unreserve_register(drcontext, ilist, app, DR_REG_XAX); |
| ASSERT(res == DRREG_SUCCESS, "reg unreserve should work"); |
| } |
| } |
| #endif |
| |
| /* Insert the code for pattern check on operand refs. |
| * The instr sequence instrumented here is used in fault handling for |
| * checking if it is the instrumented code. So if it is changed here, |
| * the checking code in |
| * - ill_instr_is_instrumented and |
| * - segv_instr_is_instrumented |
| * must be updated too. |
| * The caller is responsible for preserving aflags. |
| */ |
| static void |
| pattern_insert_cmp_jne_ud2a(void *drcontext, instrlist_t *ilist, instr_t *app, |
| opnd_t ref, opnd_t pattern) |
| { |
| instr_t *label; |
| app_pc pc = instr_get_app_pc(app); |
| IF_DEBUG(drreg_status_t res;) |
| #ifdef ARM |
| uint i; |
| reg_id_t scratch, scratch2; |
| dr_pred_type_t pred = instr_get_predicate(app); |
| instr_t *in; |
| uint val = opnd_get_immed_int(pattern); |
| #endif |
| |
| label = INSTR_CREATE_label(drcontext); |
| /* cmp ref, pattern */ |
| #ifdef X86 |
| IF_DEBUG(res =) |
| drreg_restore_app_values(drcontext, ilist, app, ref, NULL); |
| ASSERT(res == DRREG_SUCCESS, "should restore memref regs"); |
| PREXL8M(ilist, app, |
| INSTR_XL8(INSTR_CREATE_cmp(drcontext, ref, pattern), pc)); |
| #elif defined(ARM) |
| /* We use an inefficient but simple 2-scratch-reg scheme for now. |
| * XXX: if we limit the pattern value to a 1-byte value we can do a direct |
| * cmp in thumb mode via a repeated-4-times immed and avoid scratch2. |
| * XXX: we could try sub;cbnz in an IT block to avoid clobbering the |
| * flags, though cbnz requires r0-r7. |
| * XXX: for ARM, there are no multi-byte immediates, but subX4 is faster |
| * than movw,movt if there's no dead scratch2. |
| */ |
| IF_DEBUG(res =) |
| drreg_reserve_register(drcontext, ilist, app, NULL, &scratch); |
| ASSERT(res == DRREG_SUCCESS, "should always find scratch reg"); |
| IF_DEBUG(res =) |
| drreg_reserve_register(drcontext, ilist, app, NULL, &scratch2); |
| ASSERT(res == DRREG_SUCCESS, "should always find 2nd scratch reg"); |
| /* To handle a predicated memref, because we can't predicate a conditional |
| * branch nor OP_udf, we need an explicit branch to skip the OP_udf if the |
| * app pred doesn't match. We just skip all the instru and don't predicate. |
| */ |
| if (pred != DR_PRED_NONE && pred != DR_PRED_AL) { |
| PRE(ilist, app, INSTR_PRED |
| (XINST_CREATE_jump_short(drcontext, opnd_create_instr(label)), |
| instr_invert_predicate(pred))); |
| } |
| /* ldr scratch, ref */ |
| for (i = 0; i < opnd_num_regs_used(ref); i++) { |
| reg_id_t reg = opnd_get_reg_used(ref, i); |
| if (reg != dr_get_stolen_reg()) { /* stolen handled below */ |
| IF_DEBUG(res =) |
| drreg_get_app_value(drcontext, ilist, app, reg, reg); |
| ASSERT(res == DRREG_SUCCESS, "should get app value"); |
| } |
| } |
| /* XXX DRi#1834: sthg like drx_load_from_app_mem() would make this simpler if |
| * it handled the stolen reg plus pc-relative (and far refs on x86). |
| */ |
| if (opnd_uses_reg(ref, dr_get_stolen_reg())) { |
| reg_id_t swap = opnd_uses_reg(ref, scratch) ? scratch2 : scratch; |
| IF_DEBUG(bool ok;) |
| ASSERT(!opnd_uses_reg(ref, swap), "opnd uses 3 different regs?!"); |
| IF_DEBUG(ok =) |
| dr_insert_get_stolen_reg_value(drcontext, ilist, app, swap); |
| ASSERT(ok, "failed to handle stolen register"); |
| IF_DEBUG(ok =) |
| opnd_replace_reg(&ref, dr_get_stolen_reg(), swap); |
| ASSERT(ok, "failed to replace reg"); |
| } |
| if (opnd_get_size(ref) == OPSZ_1) { |
| in = INSTR_CREATE_ldrb(drcontext, opnd_create_reg(scratch), ref); |
| } else if (opnd_get_size(ref) == OPSZ_2) { |
| in = INSTR_CREATE_ldrh(drcontext, opnd_create_reg(scratch), ref); |
| if (!instr_is_encoding_possible(in)) { |
| /* i#1879: the ISA is not symmetric and there is not a 2-byte |
| * equivalent for every 1-byte load or store. |
| */ |
| IF_DEBUG(bool ok = ) |
| drutil_insert_get_mem_addr(drcontext, ilist, app, ref, scratch, |
| scratch2); |
| ASSERT(ok, "failed to handle unusual sub-word memref"); |
| instr_destroy(drcontext, in); |
| in = INSTR_CREATE_ldrh(drcontext, opnd_create_reg(scratch), |
| OPND_CREATE_MEM16(scratch, 0)); |
| } |
| } else { |
| ASSERT(opnd_get_size(ref) == OPSZ_4, "unsupported ARM memref size"); |
| in = INSTR_CREATE_ldr(drcontext, opnd_create_reg(scratch), ref); |
| } |
| PREXL8M(ilist, app, INSTR_XL8(in, pc)); |
| /* movw+movt pattern to scratch2 */ |
| in = INSTR_CREATE_movw(drcontext, opnd_create_reg(scratch2), |
| OPND_CREATE_INT(val & 0xffff)); |
| PREXL8M(ilist, app, INSTR_XL8(in, pc)); |
| if (opnd_get_size(ref) == OPSZ_4) { |
| in = INSTR_CREATE_movt(drcontext, opnd_create_reg(scratch2), |
| OPND_CREATE_INT(val >> 16)); |
| PREXL8M(ilist, app, INSTR_XL8(in, pc)); |
| } |
| /* cmp scratch to scratch2 */ |
| /* XXX: we should switch to OP_teq to avoid clobbering the V flag, but |
| * we'd need drreg to support granular flag reservations (DRi#1886) to |
| * avoid an aflags spill. Xref DRi#1877. |
| */ |
| in = INSTR_CREATE_cmp(drcontext, opnd_create_reg(scratch), |
| opnd_create_reg(scratch2)); |
| PREXL8M(ilist, app, INSTR_XL8(in, pc)); |
| IF_DEBUG(res =) |
| drreg_unreserve_register(drcontext, ilist, app, scratch2); |
| ASSERT(res == DRREG_SUCCESS, "should always succeed"); |
| IF_DEBUG(res =) |
| drreg_unreserve_register(drcontext, ilist, app, scratch); |
| ASSERT(res == DRREG_SUCCESS, "should always succeed"); |
| #endif |
| |
| /* jne label */ |
| /* XXX: add XINST_CREATE_xxxx cross-platform cbr macro to DR */ |
| #ifdef X86 |
| PRE(ilist, app, INSTR_CREATE_jcc_short(drcontext, OP_jne_short, |
| opnd_create_instr(label))); |
| #elif defined(ARM) |
| PRE(ilist, app, INSTR_PRED |
| (XINST_CREATE_jump_short(drcontext, opnd_create_instr(label)), DR_PRED_NE)); |
| #endif |
| /* we assume that the pattern seen is rare enough, so we use ud2a or udf to |
| * cause an illegal exception on a match. |
| */ |
| #ifdef X86 |
| PREXL8M(ilist, app, INSTR_XL8(INSTR_CREATE_ud2a(drcontext), pc)); |
| #elif defined(ARM) |
| PREXL8M(ilist, app, INSTR_XL8(INSTR_CREATE_udf(drcontext, OPND_CREATE_INT32(0)), pc)); |
| #endif |
| /* label */ |
| PRE(ilist, app, label); |
| } |
| |
| static int |
| pattern_create_check_opnds(bb_info_t *bi, opnd_t *refs, opnd_t *opnds |
| _IF_DEBUG(int max_refs /* size for both arrays */)) |
| { |
| opnd_size_t ref_size; |
| |
| ASSERT(max_refs >= 4 /* the max refs we might fill in */, |
| "refs/opnds array is too small"); |
| /* refs[0] holds the actual app's memory reference opnd */ |
| ASSERT(opnd_is_base_disp(refs[0]), "wrong app's memory reference"); |
| /* XXX i#774: we perform 2-byte check for 1-byte or 2-byte reference and |
| * 4-byte check for others. |
| * Should we do something like 2 4-byte checks for 8-byte reference? |
| */ |
| /* XXX i#881: detect access boundary of the redzone in pattern mode */ |
| ref_size = opnd_get_size(refs[0]); |
| if (ref_size > OPSZ_2) { |
| /* cmp [ref], pattern */ |
| opnd_set_size(&refs[0], OPSZ_4); |
| opnds[0] = OPND_CREATE_INT32((int)options.pattern); |
| return 1; |
| } |
| |
| if (ref_size == OPSZ_2) { |
| if (!bi->pattern_4byte_check_only) { |
| /* cmp [ref], pattern */ |
| opnds[0] = OPND_CREATE_INT16((short)options.pattern); |
| return 1; |
| } else { /* 4byte_check_only mode */ |
| /* for a 2-byte ref, we assume it is aligned and |
| * insert two 4-byte checks on [mem] and [mem-2]. |
| */ |
| /* cmp [ref], pattern */ |
| opnd_set_size(&refs[0], OPSZ_4); |
| opnds[0] = OPND_CREATE_INT32((int)options.pattern); |
| if (opnd_get_disp(refs[0]) < INT_MIN + 2) { |
| /* In the case of wraparound, we give up the check on [mem-2], |
| * so there are possible false negatives on accessing the last |
| * 2 bytes of the redzone. |
| */ |
| return 1; |
| } |
| /* cmp [ref-2], pattern */ |
| refs[1] = refs[0]; |
| opnd_set_disp(&refs[1], opnd_get_disp(refs[0]) - 2); |
| opnds[1] = opnds[0]; |
| return 2; |
| } |
| } |
| |
| /* XXX i#811: because of using 2-byte pattern for checking, |
| * we currently do not detect some boundary refs, e.g. |
| * a single byte reference at the end of the redzone. |
| */ |
| if (ref_size == OPSZ_1) { |
| if (!bi->pattern_4byte_check_only) { |
| /* cmp [ref], pattern */ |
| opnd_set_size(&refs[0], OPSZ_2); |
| opnds[0] = OPND_CREATE_INT16((short)options.pattern); |
| /* cmp [ref], pattern_reverse */ |
| refs[1] = refs[0]; |
| opnds[1] = OPND_CREATE_INT16((short)pattern_reverse); |
| return 2; |
| } else { /* 4byte_check_only mode */ |
| /* for a 1-byte ref, we do not assume it is aligned and |
| * insert 4 4-byte checks on [mem] and [mem-2] with |
| * pattern and pattern_reverse. |
| * It is possible to insert 3 4-byte checks on [mem], [mem-1], |
| * and [mem-2] with pattern, but it will have false negative |
| * in the malloc test case. |
| */ |
| /* cmp [ref], pattern */ |
| opnd_set_size(&refs[0], OPSZ_4); |
| opnds[0] = OPND_CREATE_INT32((int)options.pattern); |
| /* cmp [ref], pattern_reverse */ |
| refs[1] = refs[0]; |
| opnds[1] = OPND_CREATE_INT32((int)pattern_reverse); |
| if (opnd_get_disp(refs[0]) < INT_MIN + 2) { |
| /* In the case of wraparound, we give up the check on [mem-2] |
| * possible false negative on accessing the last 2-byte of |
| * the redzone. |
| */ |
| return 2; |
| } |
| /* cmp [ref - 2], pattern */ |
| refs[2] = refs[0]; |
| opnd_set_disp(&refs[1], opnd_get_disp(refs[0]) - 2); |
| opnds[2] = opnds[0]; |
| /* cmp [ref - 2], pattern_reverse */ |
| refs[3] = refs[2]; |
| opnds[3] = opnds[1]; |
| return 4; |
| } |
| } |
| return -1; |
| } |
| |
| /* If not called during drmgr's insert phase, the caller must preserve aflags */ |
| static void |
| pattern_insert_check_code(void *drcontext, instrlist_t *ilist, |
| instr_t *app, opnd_t ref, bb_info_t *bi) |
| { |
| opnd_t refs[MAX_NUM_CHECKS_PER_REF], opnds[MAX_NUM_CHECKS_PER_REF]; |
| int num_checks, i; |
| IF_DEBUG(drreg_status_t res); |
| |
| if (drmgr_current_bb_phase(drcontext) == DRMGR_PHASE_INSERTION) { |
| IF_DEBUG(res =) |
| drreg_reserve_aflags(drcontext, ilist, app); |
| ASSERT(res == DRREG_SUCCESS, "reserve of aflags should work"); |
| } |
| ASSERT(opnd_uses_nonignorable_memory(ref), |
| "non-memory-ref opnd is instrumented"); |
| #ifdef X86 |
| /* special handling for xlat instr */ |
| if (instr_get_opcode(app) == OP_xlat) |
| pattern_handle_xlat(drcontext, ilist, app, true /* pre */); |
| #endif |
| |
| refs[0] = ref; |
| num_checks = pattern_create_check_opnds(bi, refs, opnds |
| _IF_DEBUG(MAX_NUM_CHECKS_PER_REF)); |
| ASSERT(num_checks > 0 && num_checks <= MAX_NUM_CHECKS_PER_REF, |
| "Wrong number of checks created"); |
| for (i = 0; i < num_checks; i++) |
| pattern_insert_cmp_jne_ud2a(drcontext, ilist, app, refs[i], opnds[i]); |
| |
| #ifdef X86 |
| if (instr_get_opcode(app) == OP_xlat) |
| pattern_handle_xlat(drcontext, ilist, app, false /* post */); |
| #endif |
| |
| if (drmgr_current_bb_phase(drcontext) == DRMGR_PHASE_INSERTION) { |
| IF_DEBUG(res =) |
| drreg_unreserve_aflags(drcontext, ilist, app); |
| ASSERT(res == DRREG_SUCCESS, "unreserve of aflags should work"); |
| } |
| } |
| |
| static int |
| pattern_extract_refs(instr_t *app, opnd_t *refs _IF_DEBUG(int max_num_refs)) |
| { |
| int i, j, num_refs = 0; |
| opnd_t opnd; |
| |
| #ifdef X86 |
| if (instr_get_opcode(app) == OP_xlat) { |
| /* we use (%xbx, %xax) to emulate xlat's (%xbx, %al) */ |
| refs[0] = opnd_create_base_disp(DR_REG_XBX, DR_REG_XAX, 1, 0, OPSZ_1); |
| return 1; |
| } |
| #endif |
| /* we do not handle stack access including OP_enter/OP_leave. */ |
| ASSERT(!options.check_stack_access, "no stack check"); |
| for (i = 0; i < instr_num_srcs(app); i++) { |
| opnd = instr_get_src(app, i); |
| if (opnd_uses_nonignorable_memory(opnd)) { |
| ASSERT(num_refs < max_num_refs, "too many refs per instr"); |
| refs[num_refs] = opnd; |
| num_refs++; |
| } |
| } |
| for (i = 0; i < instr_num_dsts(app); i++) { |
| opnd = instr_get_dst(app, i); |
| if (opnd_uses_nonignorable_memory(opnd)) { |
| for (j = 0; j < num_refs; j++) { |
| /* skip case like ADD [mem], val => [mem] */ |
| if (opnd_same(refs[j], opnd)) |
| break; |
| } |
| if (j < num_refs) |
| continue; |
| ASSERT(num_refs < max_num_refs, "too many refs per instr"); |
| refs[num_refs] = opnd; |
| num_refs++; |
| } |
| } |
| return num_refs; |
| } |
| |
| /* remove the instrumented check instructions between start and end */ |
| static void |
| pattern_remove_check(void *drcontext, instrlist_t *ilist, |
| instr_t *start, instr_t *end) |
| { |
| instr_t *instr, *next; |
| ASSERT(start != NULL && instr_is_label(start) && |
| end != NULL && instr_is_label(end), |
| "missing start/end label"); |
| for (instr = start; instr != end; instr = next) { |
| next = instr_get_next(instr); |
| instrlist_remove(ilist, instr); |
| instr_destroy(drcontext, instr); |
| } |
| ASSERT(instr == end, "wrong end label"); |
| instrlist_remove(ilist, end); |
| instr_destroy(drcontext, end); |
| } |
| |
| /* invalidate stored check info that its base reg is overwritten by the app */ |
| static void |
| pattern_opt_elide_overlap_update_regs(instr_t *app, bb_info_t *bi) |
| { |
| int i; |
| elide_reg_cover_info_t *reg_cover = bi->reg_cover; |
| for (i = 0; i < NUM_LIVENESS_REGS; i++) { |
| if (reg_cover[i].status != ELIDE_REG_COVER_STATUS_NONE && |
| instr_writes_to_reg(app, REG_START + i, DR_QUERY_INCLUDE_ALL)) { |
| /* the base reg is overwritten, invalidate it */ |
| reg_cover[i].status = ELIDE_REG_COVER_STATUS_NONE; |
| } |
| } |
| } |
| |
| /* check if we can skip instrumenting the checks for refs */ |
| static bool |
| pattern_opt_elide_overlap_ignore_refs(bb_info_t *bi, int num_refs, opnd_t *refs) |
| { |
| elide_reg_cover_info_t *reg_cover; |
| reg_id_t base; |
| int disp; |
| |
| ASSERT(options.pattern_opt_elide_overlap, "should not be called"); |
| if (num_refs == 0) |
| return true; |
| if (num_refs > 1) |
| return false; |
| /* only care about [base + disp] refs */ |
| if (!opnd_is_near_base_disp(refs[0]) || |
| opnd_get_index(refs[0]) != DR_REG_NULL) |
| return false; |
| |
| base = opnd_get_base(refs[0]); |
| disp = opnd_get_disp(refs[0]); |
| if (!IF_X64_ELSE(reg_is_64bit(base), reg_is_32bit(base))) { |
| ASSERT(false, "wrong base register"); |
| return false; |
| } |
| reg_cover = &bi->reg_cover[base - REG_START]; |
| if (reg_cover->status == ELIDE_REG_COVER_STATUS_NONE) { |
| /* no previous check */ |
| return false; |
| } else if (reg_cover->status == ELIDE_REG_COVER_STATUS_LEFT && |
| reg_cover->left.disp == disp) { |
| /* the same ref as the left. */ |
| /* XXX: we ignore the size difference. */ |
| return true; |
| } else if (reg_cover->status == ELIDE_REG_COVER_STATUS_BOTH && |
| (reg_cover->left.disp == disp || |
| reg_cover->right.disp == disp)) { |
| /* the same ref as the right. */ |
| /* XXX: we ignore the size difference. */ |
| return true; |
| } else if (reg_cover->status == ELIDE_REG_COVER_STATUS_BOTH && |
| (reg_cover->right.disp - reg_cover->left.disp) <= |
| options.redzone_size && |
| disp > reg_cover->left.disp && disp < reg_cover->right.disp) { |
| /* check if in between */ |
| return true; |
| } |
| return false; |
| } |
| |
| /* optimize the checks by removing un-necessary checks, |
| * The heuristic is that if there are two checks closer to each other |
| * than redzone size, there are no checks needed in between. |
| */ |
| /* it returns the check info, and the caller stores the instr bounds so |
| * it can be removed later |
| */ |
| static elide_ref_check_info_t * |
| pattern_opt_elide_overlap_update_checks(void *drcontext, bb_info_t *bi, |
| instrlist_t *ilist, int num_refs, |
| opnd_t *refs) |
| { |
| elide_reg_cover_info_t *reg_cover; |
| reg_id_t base; |
| int disp; |
| |
| /* the case not handled */ |
| if (num_refs != 1 || !opnd_is_near_base_disp(refs[0]) || |
| opnd_get_index(refs[0]) != DR_REG_NULL) |
| return NULL; |
| |
| base = opnd_get_base(refs[0]); |
| disp = opnd_get_disp(refs[0]); |
| if (!IF_X64_ELSE(reg_is_64bit(base), reg_is_32bit(base))) { |
| ASSERT(false, "wrong base register"); |
| return NULL; |
| } |
| |
| reg_cover = &bi->reg_cover[base - REG_START]; |
| if (reg_cover->status == ELIDE_REG_COVER_STATUS_NONE) { |
| /* the first check, simply add at left */ |
| reg_cover->status = ELIDE_REG_COVER_STATUS_LEFT; |
| reg_cover->left.disp = disp; |
| return ®_cover->left; |
| } else if (reg_cover->status == ELIDE_REG_COVER_STATUS_LEFT) { |
| /* the second check, add in sorted order */ |
| reg_cover->status = ELIDE_REG_COVER_STATUS_BOTH; |
| ASSERT(disp != reg_cover->left.disp, "ref should be ignored"); |
| if (disp > reg_cover->left.disp) { /* add to right */ |
| reg_cover->right.disp = disp; |
| return ®_cover->right; |
| } else { /* add to left */ |
| reg_cover->right = reg_cover->left; |
| reg_cover->left.disp = disp; |
| return ®_cover->left; |
| } |
| } |
| /* two already, replace one */ |
| ASSERT(reg_cover->status == ELIDE_REG_COVER_STATUS_BOTH, |
| "wrong elide cover status"); |
| ASSERT((reg_cover->right.disp - reg_cover->left.disp) > 0, |
| "wrong order of checks"); |
| if (disp < reg_cover->left.disp) { |
| /* on the left */ |
| if ((reg_cover->right.disp - disp) <= options.redzone_size) { |
| /* remove the left one's instrumentation */ |
| pattern_remove_check(drcontext, ilist, |
| reg_cover->left.start, reg_cover->left.end); |
| } else { |
| /* move the left one to right */ |
| reg_cover->right = reg_cover->left; |
| } |
| reg_cover->left.disp = disp; |
| return ®_cover->left; |
| } else if (disp > reg_cover->right.disp) { |
| /* on the right */ |
| if ((disp - reg_cover->left.disp) <= options.redzone_size) { |
| /* remove the right one's instrumentation*/ |
| pattern_remove_check(drcontext, ilist, |
| reg_cover->right.start, reg_cover->right.end); |
| } else { |
| /* move the right one to left */ |
| reg_cover->left = reg_cover->right; |
| } |
| reg_cover->right.disp = disp; |
| return ®_cover->right; |
| } else { |
| /* in between */ |
| ASSERT(disp > reg_cover->left.disp && disp < reg_cover->right.disp, |
| "wrong order of reg_covers"); |
| ASSERT((reg_cover->right.disp - reg_cover->left.disp) > options.redzone_size, |
| "ref should be ignored"); |
| if ((disp - reg_cover->left.disp) >= (reg_cover->right.disp - disp)) { |
| if ((disp - reg_cover->left.disp) <= options.redzone_size) { |
| /* the large gap is smaller than the redzone size */ |
| reg_cover->right.disp = disp; |
| return ®_cover->right; |
| } else { |
| /* pick the small gap. */ |
| reg_cover->left.disp = disp; |
| return ®_cover->left; |
| } |
| } else { |
| if ((reg_cover->right.disp - disp) <= options.redzone_size) { |
| /* the large gap is smaller than the redzone size */ |
| reg_cover->left.disp = disp; |
| return ®_cover->left; |
| } else { |
| /* pick the small gap. */ |
| reg_cover->right.disp = disp; |
| return ®_cover->right; |
| } |
| } |
| } |
| ASSERT(false, "should not reach here"); |
| return NULL; |
| } |
| |
| /* If not called during drmgr's insert phase, the caller must preserve aflags */ |
| instr_t * |
| pattern_instrument_check(void *drcontext, instrlist_t *ilist, instr_t *app, |
| bb_info_t *bi, bool translating) |
| { |
| int num_refs, i; |
| opnd_t refs[MAX_REFS_PER_INSTR]; |
| instr_t *label; |
| elide_ref_check_info_t *check = NULL; |
| |
| ASSERT(options.pattern != 0, "should not be called"); |
| if (options.pattern_opt_elide_overlap) |
| pattern_opt_elide_overlap_update_regs(app, bi); |
| |
| if (IF_X86(instr_get_opcode(app) == OP_lea ||) |
| instr_is_prefetch(app) || |
| instr_is_nop(app)) |
| return NULL; |
| |
| num_refs = pattern_extract_refs(app, refs _IF_DEBUG(MAX_REFS_PER_INSTR)); |
| if (num_refs == 0) |
| return NULL; |
| if (!translating) |
| bi->pattern_4byte_check_only = pattern_4byte_check_only; |
| else { |
| ASSERT(bi->pattern_4byte_check_field_set, |
| "pattern_4byte_check_only is not initialized"); |
| } |
| if (options.pattern_opt_elide_overlap) { |
| if (pattern_opt_elide_overlap_ignore_refs(bi, num_refs, refs)) |
| return NULL; |
| check = pattern_opt_elide_overlap_update_checks |
| (drcontext, bi, ilist, num_refs, refs); |
| } |
| bi->added_instru = true; |
| /* pattern check code */ |
| label = INSTR_CREATE_label(drcontext); |
| PRE(ilist, app, label); |
| for (i = 0; i < num_refs; i++) { |
| if (check != NULL) { |
| check->start = INSTR_CREATE_label(drcontext); |
| PRE(ilist, app, check->start); |
| } |
| pattern_insert_check_code(drcontext, ilist, app, refs[i], bi); |
| if (check != NULL) { |
| check->end = INSTR_CREATE_label(drcontext); |
| PRE(ilist, app, check->end); |
| } |
| } |
| return label; |
| } |
| |
| #ifdef X86 |
| /* An aggressive optimization to optimize the loop expanded from rep string |
| * by introducing an inner loop to avoid the unncessary aflags save/restore. |
| * XXX: adding a loop inside bb violates DR's bb constraints: |
| * - a non-meta cbr must end the block |
| * we insert a fake non-meta jmp instead |
| * - violates assumptions in many parts of DR: non-precise flushing, |
| * signal delivery, trace building, etc.. |
| * + We add translation to all the related instructions to make sure the |
| * translation for signal delivery work. |
| * + For non-precise flush, we have to wait till the exit of the bb, |
| * which is similar to not expanding rep-string. |
| * + Such loop optimization should NOT be applied in a trace. |
| * |
| * The optimization relies on how drutil (a DynamoRIO extension) expands |
| * a rep string into a loop, the loop should look exactly the same as below: |
| * rep movs |
| * => |
| * jecxz zero |
| * jmp iter |
| * zero: |
| * mov $0x00000001 -> %ecx |
| * jmp pre_loop |
| * iter: |
| * movs %ds:(%esi) %esi %edi -> %es:(%edi) %esi %edi |
| * pre_loop: |
| * loop |
| * |
| * The instrumented code would be something like: |
| * jecxz zero |
| * jmp iter |
| * zero: |
| * jmp post_loop |
| * iter: |
| * # save aflags if necessary |
| * # first iteration check |
| * check: |
| * cmp %ds:(%esi) $pattern |
| * ... |
| * cmp %es:(%edi) $pattern |
| * ... |
| * movs %ds:(%esi) %esi %edi -> %es:(%edi) %esi %edi |
| * pre_loop: |
| * loop check |
| * # restore aflags if necessary |
| * post_loop: |
| * jmp next_pc |
| * The transformation is only performed at instru2instru phase to |
| * avoid confusing other passes. |
| */ |
| void |
| pattern_instrument_repstr(void *drcontext, instrlist_t *ilist, |
| bb_info_t *bi, bool translating) |
| { |
| instr_t *jecxz = NULL, *mov_1 = NULL, *jmp_skip = NULL; |
| IF_DEBUG(instr_t *jmp_iter = NULL;) |
| IF_DEBUG(drreg_status_t res;) |
| instr_t *stringop = NULL, *loop = NULL, *post_loop, *instr, *jmp; |
| instr_t *check = NULL; |
| app_pc next_pc; |
| |
| ASSERT(bi->is_repstr_to_loop && options.pattern_opt_repstr, |
| "should not be here"); |
| /* find all instr */ |
| instr = instrlist_first(ilist); |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| instr_get_opcode(instr) == OP_jecxz) { |
| jecxz = instr; |
| break; |
| } |
| } |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| instr_get_opcode(instr) == OP_jmp_short) { |
| IF_DEBUG(jmp_iter = instr;) |
| break; |
| } |
| } |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| instr_get_opcode(instr) == OP_mov_imm) { |
| mov_1 = instr; |
| break; |
| } |
| } |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| instr_get_opcode(instr) == OP_jmp) { |
| jmp_skip = instr; |
| break; |
| } |
| } |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| opc_is_stringop(instr_get_opcode(instr))) { |
| stringop = instr; |
| break; |
| } |
| } |
| for (; instr != NULL; instr = instr_get_next(instr)) { |
| if (instr_is_app(instr) && |
| opc_is_loopcc(instr_get_opcode(instr))) { |
| loop = instr; |
| break; |
| } |
| } |
| ASSERT(jecxz != NULL && jmp_iter != NULL && mov_1 != NULL && |
| jmp_skip != NULL && stringop != NULL && loop != NULL, |
| "wrong rep sring to loop code"); |
| ASSERT(loop == instrlist_last(ilist), "wrong last instr in loop"); |
| /* adjust jeczx target */ |
| instr_set_target(jecxz, opnd_create_instr(jmp_skip)); |
| /* remove mov_1 */ |
| instrlist_remove(ilist, mov_1); |
| instr_destroy(drcontext, mov_1); |
| /* adjust jmp_skip target */ |
| post_loop = INSTR_CREATE_label(drcontext); |
| instr_set_target(jmp_skip, opnd_create_instr(post_loop)); |
| /* insert checks */ |
| /* the pattern checks will suspend their own aflags saves b/c it's not insert phase */ |
| IF_DEBUG(res =) |
| drreg_reserve_aflags(drcontext, ilist, stringop); |
| ASSERT(res == DRREG_SUCCESS, "should reserve aflags"); |
| /* As we're in instru2instru, we have to manually restore app-used regs. */ |
| if (instr_uses_reg(stringop, DR_REG_XAX)) { |
| IF_DEBUG(res =) |
| drreg_get_app_value(drcontext, ilist, stringop, DR_REG_XAX, DR_REG_XAX); |
| ASSERT(res == DRREG_SUCCESS, "should restore xax"); |
| } |
| check = pattern_instrument_check |
| (drcontext, ilist, stringop, bi, translating); |
| ASSERT(check != NULL, "check label must not be NULL"); |
| /* set loop target to check */ |
| instr_set_target(loop, opnd_create_instr(check)); |
| instr_set_meta(loop); |
| /* post_loop */ |
| PRE(ilist, NULL, post_loop); |
| next_pc = instr_get_app_pc(stringop) + |
| instr_length(drcontext, stringop) + 1 /* rep prefix */; |
| /* restore aflags before post_loop if necessary */ |
| IF_DEBUG(res =) |
| drreg_unreserve_aflags(drcontext, ilist, post_loop); |
| ASSERT(res == DRREG_SUCCESS, "should unreserve aflags"); |
| /* jmp next_pc */ |
| jmp = INSTR_XL8(INSTR_CREATE_jmp(drcontext, opnd_create_pc(next_pc)), |
| instr_get_app_pc(loop)); |
| PREXL8(ilist, NULL, jmp); |
| } |
| #endif /* X86 */ |
| |
| /* Assumes the caller has set the ISA mode to match the fault point */ |
| static bool |
| pattern_ill_instr_is_instrumented(void *drcontext, byte *pc) |
| { |
| #ifdef X86 |
| byte buf[6]; |
| /* check if our code sequence */ |
| if (!safe_read(pc - JNZ_SHORT_LENGTH - 2 /* 2 bytes of cmp immed value */, |
| BUFFER_SIZE_BYTES(buf), buf) || |
| (buf[2] != JNZ_SHORT_OPCODE) || |
| (buf[3] != UD2A_LENGTH) || |
| ((*(ushort *)&buf[0] != (ushort)options.pattern) && |
| (*(ushort *)&buf[0] != (ushort)pattern_reverse)) || |
| (*(ushort *)&buf[4] != (ushort)UD2A_OPCODE)) |
| return false; |
| #elif defined(ARM) |
| /* For Thumb: |
| * +30 m4 @0x4f7d2008 f8d3 c004 ldr +0x04(%r3)[4byte] -> %r12 |
| * +34 m4 @0x4f7d1f70 f24f 14fd movw $0x0000f1fd -> %r4 |
| * +38 m4 @0x4f7d1f24 f2cf 14fd movt $0x0000f1fd -> %r4 |
| * +42 m4 @0x4f7d1ee4 ebbc 0f04 cmp %r12 %r4 $0x00 |
| * +46 m4 @0x537a1ea4 d100 b.ne @0x537a20a0[4byte] |
| * +48 m4 @0x537a1bb0 de00 udf $0x00000000 |
| * +50 m4 @0x537a20a0 <label> |
| * When the app memref is predicated, we add a b.!pred at the top, so the |
| * rest does not change. |
| * For ARM: |
| * +20 m4 @0x4f3fe0f4 e5901000 ldr (%r0)[4byte] -> %r1 |
| * +24 m4 @0x4f3fe180 e30f21fd movw $0x0000f1fd -> %r2 |
| * +28 m4 @0x4f3fdee8 e34f21fd movt $0x0000f1fd -> %r2 |
| * +32 m4 @0x4f3fdf28 e1510002 cmp %r1 %r2 $0x00 |
| * +36 m4 @0x4f3fdf68 1a000000 b.ne @0x4f3fdc34[4byte] |
| * +40 m4 @0x4f3fdb50 e7f000f0 udf $0x00000000 |
| * +44 m4 @0x4f3fdc34 <label> |
| * For 2-byte or 1-byte memrefs, there is no movt. |
| */ |
| if (dr_get_isa_mode(drcontext) == DR_ISA_ARM_THUMB) { |
| byte buf[12]; /* 12 bytes from movt (or movw for <4byte) through udf */ |
| # define BNE_THUMB_OPCODE 0xd100 |
| int immed; |
| if (!safe_read(pc + UDF_THUMB_LENGTH - sizeof(buf), sizeof(buf), buf)) |
| return false; |
| immed = ((buf[0] & 0xf) << 12) | ((buf[1] & 0x4) << 9) | |
| ((buf[3] & 0x70) << 4) | buf[2]; |
| DOLOG(3, { |
| int i; |
| LOG(3, "%s: read ", __FUNCTION__); |
| for (i = 0; i < sizeof(buf); i++) |
| LOG(3, " %02x", buf[i]); |
| LOG(3, "\n\timmed=0x%x, bne=0x%x, udf=0x%x\n", immed, |
| *(ushort *)&buf[8], *(ushort *)&buf[10]); |
| }); |
| if ((immed != (options.pattern & 0xffff) && |
| immed != (pattern_reverse & 0xffff)) || |
| *(ushort *)&buf[8] != (ushort)BNE_THUMB_OPCODE || |
| *(ushort *)&buf[10] != (ushort)UDF_THUMB_OPCODE) |
| return false; |
| } else { |
| byte buf[16]; /* 16 bytes from movt (or movw for <4byte) through udf */ |
| # define BNE_ARM_OPCODE 0x1a000000 |
| int immed; |
| if (!safe_read(pc + UDF_ARM_LENGTH - sizeof(buf), sizeof(buf), buf)) |
| return false; |
| immed = ((buf[2] & 0xf) << 12) | ((buf[1] & 0xf) << 8) | buf[0]; |
| if ((immed != (options.pattern & 0xffff) && |
| immed != (pattern_reverse & 0xffff)) || |
| *(ushort *)&buf[8] != (ushort)BNE_ARM_OPCODE || |
| *(ushort *)&buf[12] != (ushort)UDF_ARM_OPCODE) |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| /* switch to 4-byte-check only mode */ |
| static void |
| pattern_switch_instrumentation_style(void) |
| { |
| if (num_2byte_faults >= options.pattern_max_2byte_faults && |
| dr_mutex_trylock(flush_lock) /* to avoid flush storm */) { |
| if (num_2byte_faults >= options.pattern_max_2byte_faults) { |
| pattern_4byte_check_only = true; |
| num_2byte_faults = 0; |
| dr_delay_flush_region(0, (size_t)-1, 0, NULL); |
| } |
| dr_mutex_unlock(flush_lock); |
| } |
| } |
| |
| bool |
| pattern_handle_ill_fault(void *drcontext, |
| dr_mcontext_t *raw_mc, |
| dr_mcontext_t *mc) |
| { |
| app_pc addr; |
| bool is_write; |
| int memopidx; |
| instr_t instr; |
| uint pos, skip; |
| ASSERT(options.pattern != 0, "incorrectly called"); |
| STATS_INC(num_slowpath_faults); |
| #ifdef ARM |
| dr_isa_mode_t old_mode; |
| dr_isa_mode_t fault_mode = get_isa_mode_from_fault_mc(raw_mc); |
| dr_set_isa_mode(drcontext, fault_mode, &old_mode); |
| #endif |
| |
| /* check if ill-instr is our code */ |
| if (!pattern_ill_instr_is_instrumented(drcontext, raw_mc->pc)) { |
| IF_ARM(dr_set_isa_mode(drcontext, old_mode, NULL)); |
| return false; |
| } |
| /* get the information of the instr that triggered the ill fault. |
| * will report on all unaddr refs in this instr and don't care |
| * which one triggered the ud2a |
| */ |
| instr_init(drcontext, &instr); |
| decode(drcontext, mc->pc, &instr); |
| for (memopidx = 0; |
| instr_compute_address_ex_pos(&instr, mc, memopidx, |
| &addr, &is_write, &pos); |
| memopidx++) { |
| app_loc_t loc; |
| size_t size; |
| opnd_t opnd = is_write ? |
| instr_get_dst(&instr, pos) : instr_get_src(&instr, pos); |
| if (!opnd_uses_nonignorable_memory(opnd)) |
| continue; |
| size = opnd_size_in_bytes(opnd_get_size(opnd)); |
| if (size <= 2) { |
| /* it is ok to have racy update here */ |
| num_2byte_faults++; |
| } |
| pc_to_loc(&loc, mc->pc); |
| pattern_handle_mem_ref(&loc, addr, size, mc, is_write); |
| } |
| instr_free(drcontext, &instr); |
| /* we are not skipping all cmps for this instr, which is ok because we |
| * clobberred the pattern if a 2nd memref was unaddr. |
| */ |
| skip = IF_ARM_ELSE(fault_mode == DR_ISA_ARM_THUMB ? |
| UDF_THUMB_LENGTH : UDF_ARM_LENGTH, UD2A_LENGTH); |
| LOG(2, "pattern check ud2a triggered@"PFX" => skip to "PFX"\n", |
| raw_mc->pc, raw_mc->pc + skip); |
| raw_mc->pc += skip; |
| |
| if (options.pattern_max_2byte_faults > 0 && |
| num_2byte_faults >= options.pattern_max_2byte_faults) { |
| /* 2-byte checks caused too many faults, switch instrumentation style */ |
| pattern_switch_instrumentation_style(); |
| } |
| IF_ARM(dr_set_isa_mode(drcontext, old_mode, NULL)); |
| return true; |
| } |
| |
| /* Assumes the caller has set the ISA mode to match the fault point */ |
| static bool |
| pattern_segv_instr_is_instrumented(byte *pc, byte *next_next_pc, |
| instr_t *inst, instr_t *next) |
| { |
| #ifdef X86 |
| ushort ud2a; |
| /* check code sequence: cmp; jne_short; ud2a */ |
| if (instr_get_opcode(inst) == OP_cmp && |
| instr_get_opcode(next) == OP_jne_short && |
| safe_read(next_next_pc, sizeof(ushort), &ud2a) && |
| ud2a == (ushort)UD2A_OPCODE) { |
| DODEBUG({ |
| opnd_t opnd = instr_get_src(inst, 1); |
| ASSERT(opnd_is_immed_int(opnd), "Similar code sequence is seen"); |
| if (opnd_get_size(opnd) == OPSZ_4) { |
| ASSERT(opnd_get_immed_int(opnd) == (int)options.pattern || |
| opnd_get_immed_int(opnd) == (int)pattern_reverse, |
| "Similar code sequence is seen"); |
| } else { |
| ASSERT((ushort)opnd_get_immed_int(opnd) == |
| (ushort)options.pattern || |
| (ushort)opnd_get_immed_int(opnd) == |
| (ushort)pattern_reverse, |
| "Similar code sequence is seen"); |
| } |
| }); |
| return true; |
| } |
| #elif defined(ARM) |
| /* check code sequence: ldr; movw+movt; cmp; b.ne; udf */ |
| if ((instr_get_opcode(inst) == OP_ldr || |
| instr_get_opcode(inst) == OP_ldrb || |
| instr_get_opcode(inst) == OP_ldrh) && |
| instr_get_opcode(next) == OP_movw && |
| opnd_is_immed_int(instr_get_src(next, 0))) { |
| uint imm = opnd_get_immed_int(instr_get_src(next, 0)); |
| if (imm == (ushort)options.pattern || |
| imm == (ushort)pattern_reverse) |
| return true; |
| } |
| #endif |
| return false; |
| } |
| |
| /* In pattern mode, there are several possible ways that segv fault happens |
| * - wrong pc |
| * + it is more possible to be a bug, do nothing and return false; |
| * - app code |
| * + if it is intended segv fault, app will handle it, do not report |
| * + if it is unintended, app will crash natively, do not report either. |
| * + return false for both cases |
| * - instrumented code, i.e. pattern check code |
| * + trigger the segv before app |
| * + skip the check and continue |
| * |
| * i#1070: this function is also used for guard page violation handling, |
| * in which case, we should restore the guard page if the violation is caused |
| * by us. |
| */ |
| bool |
| pattern_handle_segv_fault(void *drcontext, dr_mcontext_t *raw_mc, |
| dr_mcontext_t *mc |
| _IF_WINDOWS(app_pc target) |
| _IF_WINDOWS(bool guard)) |
| { |
| bool ours = false; |
| instr_t inst, next; |
| byte *next_pc; |
| #ifdef ARM |
| dr_isa_mode_t old_mode; |
| dr_isa_mode_t fault_mode = get_isa_mode_from_fault_mc(raw_mc); |
| dr_set_isa_mode(drcontext, fault_mode, &old_mode); |
| #endif |
| |
| /* check if wrong pc */ |
| instr_init(drcontext, &inst); |
| instr_init(drcontext, &next); |
| if (!safe_decode(drcontext, raw_mc->pc, &inst, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| if (!safe_decode(drcontext, next_pc, &next, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| /* check if our own code */ |
| if (!pattern_segv_instr_is_instrumented(raw_mc->pc, next_pc, &inst, &next)) { |
| app_pc addr; |
| bool is_write; |
| uint pos; |
| int memopidx; |
| app_loc_t loc; |
| size_t size; |
| opnd_t opnd; |
| dr_mem_info_t info; |
| for (memopidx = 0; |
| instr_compute_address_ex_pos(&inst, mc, memopidx, |
| &addr, &is_write, &pos); |
| memopidx++) { |
| if (dr_query_memory_ex(addr, &info)) { |
| if (info.type == DR_MEMTYPE_FREE) { |
| opnd = is_write ? |
| instr_get_dst(&inst, pos) : instr_get_src(&inst, pos); |
| size = opnd_size_in_bytes(opnd_get_size(opnd)); |
| pc_to_loc(&loc, mc->pc); |
| report_unaddressable_access(&loc, addr, size, |
| is_write ? DR_MEMPROT_WRITE : |
| DR_MEMPROT_READ, |
| addr, addr + size, mc); |
| } else if (is_write && options.report_write_to_read_only && |
| !TEST(DR_MEMPROT_WRITE, info.prot)) { |
| if (IF_WINDOWS(guard ||) TEST(info.prot, DR_MEMPROT_PRETEND_WRITE)) |
| continue; |
| opnd = instr_get_dst(&inst, pos); |
| size = opnd_size_in_bytes(opnd_get_size(opnd)); |
| pc_to_loc(&loc, mc->pc); |
| report_unaddr_warning(&loc, mc, "writing to readonly memory", |
| addr, size, true); |
| } |
| /* FIXME i#1015: report unaddr error for write on read-only */ |
| } |
| } |
| goto handle_light_mode_segv_fault_done; |
| } |
| #ifdef WINDOWS |
| if (guard) { |
| /* violation caused by us, restore the guard page. |
| * XXX: there could be a race here. Because the guard page is a |
| * one-shot alarm, there might be another thread touches that page |
| * without exception before we restore it, and behave incorrectly. |
| */ |
| uint prot; |
| # ifdef DEBUG /* some sanity checks */ |
| bool ok; |
| opnd_t mem = instr_get_src(&inst, 0); |
| size_t size = opnd_size_in_bytes(opnd_get_size(mem)); |
| ASSERT(opnd_uses_nonignorable_memory(mem), "wrong cmp opnd"); |
| ASSERT(opnd_compute_address(mem, raw_mc) <= target && |
| opnd_compute_address(mem, raw_mc) + size > target, |
| "wrong exception address"); |
| # endif |
| /* get prot of that page */ |
| IF_DEBUG(ok = ) |
| dr_query_memory((byte *)target, NULL, NULL, &prot); |
| ASSERT(ok, "failed to get prot on guarded page"); |
| /* restore GUARD_PAGE */ |
| IF_DEBUG(ok = ) |
| dr_memory_protect((byte *)target, 1, prot | DR_MEMPROT_GUARD); |
| ASSERT(ok, "failed to restore guard page"); |
| } |
| #endif |
| /* skip pattern check code */ |
| #ifdef X86 |
| LOG(2, "pattern check cmp fault@"PFX" => skip to "PFX"\n", |
| raw_mc->pc, next_pc + UD2A_LENGTH); |
| raw_mc->pc = next_pc + UD2A_LENGTH; |
| #elif defined(ARM) |
| instr_reset(drcontext, &next); |
| if (!safe_decode(drcontext, next_pc, &next, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| if (instr_get_opcode(&next) == OP_movt) { |
| instr_reset(drcontext, &next); |
| if (!safe_decode(drcontext, next_pc, &next, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| } |
| ASSERT(instr_get_opcode(&next) == OP_cmp, "invalid pattern instru"); |
| instr_reset(drcontext, &next); |
| if (!safe_decode(drcontext, next_pc, &next, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| ASSERT(instr_is_cbr(&next), "invalid pattern instru"); |
| instr_reset(drcontext, &next); |
| if (!safe_decode(drcontext, next_pc, &next, &next_pc)) |
| goto handle_light_mode_segv_fault_done; |
| ASSERT(instr_get_opcode(&next) == OP_udf, "invalid pattern instru"); |
| LOG(2, "pattern check fault@"PFX" => skip to "PFX"\n", raw_mc->pc, next_pc); |
| raw_mc->pc = next_pc; |
| #endif |
| ours = true; |
| handle_light_mode_segv_fault_done: |
| IF_ARM(dr_set_isa_mode(drcontext, old_mode, NULL)); |
| instr_free(drcontext, &inst); |
| instr_free(drcontext, &next); |
| return ours; |
| } |
| |
| |
| /*************************************************************************** |
| * Memory allocation bookkeeping Functions |
| */ |
| |
| static bool |
| pattern_addr_in_malloc_tree(byte *addr, size_t size) |
| { |
| rb_node_t *node; |
| bool res = false; |
| |
| /* walk the pattern_malloc_tree */ |
| dr_rwlock_read_lock(pattern_malloc_tree_rwlock); |
| node = rb_in_node(pattern_malloc_tree, addr); |
| if (node != NULL) { |
| byte *start; |
| void *data; |
| size_t real_size; |
| size_t app_size; |
| rb_node_fields(node, &start, &real_size, &data); |
| app_size = (size_t)data; |
| ASSERT(app_size + options.redzone_size * 2 <= real_size, |
| "wrong node information"); |
| if (addr < start + options.redzone_size || |
| addr >= start + options.redzone_size + app_size) |
| res = true; |
| } |
| dr_rwlock_read_unlock(pattern_malloc_tree_rwlock); |
| return res; |
| } |
| |
| static void |
| pattern_insert_malloc_tree(malloc_info_t *info) |
| { |
| IF_DEBUG(rb_node_t *node;) |
| /* only used to find redzone overlap of live allocs */ |
| if (!info->has_redzone) |
| return; |
| dr_rwlock_write_lock(pattern_malloc_tree_rwlock); |
| /* due to padding, the real_size might be larger than |
| * (app_size + redzone_size*2), which makes the size of |
| * rear redzone not fixed, so store app_size in rb_tree. |
| */ |
| IF_DEBUG(node =) |
| rb_insert(pattern_malloc_tree, info->base - options.redzone_size, |
| info->pad_size + options.redzone_size*2, (void *)info->request_size); |
| dr_rwlock_write_unlock(pattern_malloc_tree_rwlock); |
| ASSERT(node == NULL, "error in inserting pattern malloc tree"); |
| } |
| |
| static void |
| pattern_remove_malloc_tree(malloc_info_t *info) |
| { |
| rb_node_t *node; |
| void *data; |
| app_pc real_base; |
| size_t size; |
| |
| /* only used to find redzone overlap of live allocs */ |
| if (!info->has_redzone) |
| return; |
| dr_rwlock_write_lock(pattern_malloc_tree_rwlock); |
| node = rb_find(pattern_malloc_tree, info->base - options.redzone_size); |
| if (node != NULL) { |
| rb_node_fields(node, &real_base, &size, &data); |
| ASSERT(info->request_size == (size_t)data, |
| "wrong app size in pattern malloc tree"); |
| ASSERT(real_base == info->base - options.redzone_size, |
| "wrong real_base in pattern malloc tree"); |
| ASSERT(size == info->pad_size + options.redzone_size * 2, |
| "Wrong real_size in pattern malloc tree"); |
| /* XXX i#786: we simply remove the memory here, which can be |
| * improved by invalidating/removing malloc rbtree instead, |
| * though we still need do the lookup to change the node status. |
| */ |
| rb_delete(pattern_malloc_tree, node); |
| } |
| dr_rwlock_write_unlock(pattern_malloc_tree_rwlock); |
| } |
| |
| |
| |
| /* If an addr contains pattern value, we check the memory before and after, |
| * and return true if there are enough number of contiguous pattern value. |
| * XXX: the pattern value in the redzone could be clobbered by earlier error, |
| * (see pattern_handle_ill_fault,) which may cause false negative here, |
| * e.g. test case registers. |
| * Now we return true if see more than half are pattern values. |
| */ |
| #define ADDR_PRE_CHECK_SIZE (options.redzone_size) |
| #define ADDR_PRE_CHECK_COUNT (ADDR_PRE_CHECK_SIZE / sizeof(uint)) |
| #define ADDR_PRE_CHECK_THRESHOLD (ADDR_PRE_CHECK_COUNT/2) |
| |
| static bool |
| pattern_addr_pre_check(byte *addr) |
| { |
| uint *val; |
| uint match = 0; |
| int i; |
| |
| addr = (byte *) ALIGN_BACKWARD(addr, sizeof(uint)); |
| /* read memory after addr */ |
| DR_TRY_EXCEPT(dr_get_current_drcontext(), { |
| /* we check the memory after aligned addr instead of addr |
| * to handle the case like: |
| * char *p = malloc(3); *(p + 3) = ...; |
| */ |
| val = (uint *)addr + 1; |
| for (i = 0; i < ADDR_PRE_CHECK_COUNT; i++) { |
| if (*val != options.pattern) |
| break; |
| val++; |
| match++; |
| } |
| }, { /* EXCEPT */ |
| }); |
| if (match > ADDR_PRE_CHECK_THRESHOLD) |
| return true; |
| DR_TRY_EXCEPT(dr_get_current_drcontext(), { |
| val = (uint *)addr; |
| for (i = 0; i < ADDR_PRE_CHECK_COUNT; i++) { |
| if (*val != options.pattern) |
| break; |
| val--; |
| match++; |
| } |
| }, { /* EXCEPT */ |
| }); |
| if (match > ADDR_PRE_CHECK_THRESHOLD) |
| return true; |
| return false; |
| } |
| |
| /* Checks whether [addr, addr+size) overlaps a redzone or padding */ |
| static bool |
| pattern_addr_in_redzone(byte *addr, size_t size) |
| { |
| bool res = false; |
| LOG(3, "%s: "PFX"-"PFX"\n", __FUNCTION__, addr, addr+size); |
| if (options.pattern_use_malloc_tree) |
| res = pattern_addr_in_malloc_tree(addr, size); |
| else |
| res = region_in_redzone(addr, size, NULL, NULL, NULL, NULL, NULL); |
| return res; |
| } |
| |
| /* Assumes that it's ok to write the pattern value beyond end! |
| * I.e., if a small region is passed in, assumes there's already a |
| * redzone beyond it. |
| */ |
| static void |
| pattern_write_pattern(byte *start, byte *end _IF_DEBUG(const char *description)) |
| { |
| /* gcc -O3 loads from options.pattern every iter of the loop so we |
| * explicitly put into a local. |
| */ |
| register uint *addr = (uint *) start; |
| register uint pattern_val = options.pattern; |
| LOG(2, "set pattern value at "PFX"-"PFX" in %s\n", |
| start, end, description); |
| if (!ALIGNED(addr, 2)) { |
| *addr = pattern_reverse; |
| } else if (!ALIGNED(addr, 4)) { |
| /* the addr must be 2-byte aligned, fill the 2-byte |
| * if it is not 4-byte aligned |
| */ |
| *(ushort *)addr = (ushort)options.pattern; |
| } |
| for (addr = (uint *)ALIGN_FORWARD(start, 4); |
| addr < (uint *)end; /* we assume ok to write past end! */ |
| addr++) |
| *addr = pattern_val; |
| } |
| |
| void |
| pattern_handle_malloc(malloc_info_t *info) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| ASSERT(ALIGNED(info->base, sizeof(uint)), "base is unaligned"); |
| ASSERT(ALIGNED(info->pad_size, sizeof(uint)), "pad size is unaligned"); |
| |
| if (info->has_redzone) { |
| if (options.pattern_use_malloc_tree) |
| pattern_insert_malloc_tree(info); |
| pattern_write_pattern(info->base - options.redzone_size, info->base |
| _IF_DEBUG("malloc pre-redzone")); |
| /* the app_size might be unaligned, which will be expanded with padding |
| * by allocator. We will fill the padding whenever possible. |
| */ |
| pattern_write_pattern(info->base + info->request_size, |
| info->base + info->pad_size + options.redzone_size |
| _IF_DEBUG("malloc padding + post-redzone")); |
| } else { |
| #if 0 /* FIXME: i#832, no redzone for debug CRT, so cannot use ASSERT here */ |
| ASSERT(malloc_is_pre_us(app_base), "unknown malloc region"); |
| #endif |
| } |
| } |
| |
| void |
| pattern_handle_real_free(malloc_info_t *info, bool delayed) |
| { |
| size_t rz_sz = options.redzone_size; |
| ASSERT(options.pattern != 0, "should not be called"); |
| if (delayed) { |
| /* removing the pattern to avoid false positive faults. */ |
| byte *rz_start = info->base - (info->has_redzone ? rz_sz : 0); |
| size_t tot_sz = info->pad_size + (info->has_redzone ? rz_sz*2 : 0); |
| LOG(2, "clear pattern value "PFX"-"PFX" %d bytes in freed block\n", |
| rz_start, rz_start + tot_sz, tot_sz); |
| memset(rz_start, 0, tot_sz); |
| } else { |
| if (options.pattern_use_malloc_tree) { |
| /* if !delayed, the base is app base, and the size is app size. |
| * we can ignore the size since our rbtree holds the app_size, |
| * now use passed in size for sanity check. |
| */ |
| pattern_remove_malloc_tree(info); |
| } |
| /* if !delayed, only need remove the pattern in redzone */ |
| if (info->has_redzone) { |
| IF_DEBUG(uint val;) |
| ASSERT(safe_read(info->base - rz_sz, sizeof(val), &val) && |
| val == options.pattern, "wrong free address"); |
| LOG(2, "clear pattern value "PFX"-"PFX" %d bytes in pre-redzone\n", |
| info->base - rz_sz, info->base, rz_sz); |
| memset(info->base - rz_sz, 0, rz_sz); |
| LOG(2, "clear pattern value "PFX"-"PFX" %d bytes in post-redzone\n", |
| info->base + info->pad_size, info->base + info->pad_size + rz_sz, rz_sz); |
| memset(info->base + info->pad_size, 0, rz_sz); |
| } else { |
| #if 0 /* FIXME: i#832, no redzone for debug CRT, so cannot use ASSERT here */ |
| ASSERT(info->pre_us, "unknown malloc region"); |
| #endif |
| } |
| } |
| } |
| |
| void |
| pattern_handle_delayed_free(malloc_info_t *info) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| /* We assume that any invalid free won't come here */ |
| if (options.pattern_use_malloc_tree) |
| pattern_remove_malloc_tree(info); |
| /* We assume the actually alloced block length will be 4-byte aligned, |
| * e.g. if size is 2, the allocator will alloc 4 bytes instead, |
| * so it is ok to fill 4-byte uint pattern. |
| */ |
| ASSERT(ALIGNED(info->base, 4), "unaligned pointer for free"); |
| pattern_write_pattern(info->base, info->base + info->request_size |
| _IF_DEBUG("delay-freed block")); |
| } |
| |
| void |
| pattern_handle_realloc(malloc_info_t *old_info, malloc_info_t *new_info, |
| bool for_reuse) |
| { |
| LOG(2, "%s: "PFX"-"PFX", "PFX"-"PFX"\n", __FUNCTION__, |
| old_info->base, old_info->base + old_info->request_size, |
| new_info->base, new_info->base + new_info->request_size); |
| if (new_info->base != old_info->base) { |
| /* treat as free+malloc */ |
| if (options.replace_malloc) { |
| if (!for_reuse) |
| pattern_handle_delayed_free(old_info); |
| } else { |
| /* XXX: with wrapping, we can have a race here with -no_replace_realloc. |
| * That option combo is just unsafe: pattern + wrap + -no_replace_realloc! |
| */ |
| pattern_handle_real_free(old_info, false); |
| } |
| pattern_handle_malloc(new_info); |
| } else { |
| if (new_info->request_size > old_info->request_size) { |
| /* clear pattern from padding + trailing redzone */ |
| size_t rm_sz = old_info->pad_size - old_info->request_size + |
| (old_info->has_redzone ? options.redzone_size : 0); |
| size_t add_sz = new_info->pad_size - new_info->request_size + |
| (new_info->has_redzone ? options.redzone_size : 0); |
| LOG(2, "clear pattern value "PFX"-"PFX" %d bytes on in-place realloc\n", |
| old_info->base + old_info->request_size, |
| old_info->base + old_info->request_size + rm_sz, rm_sz); |
| memset(old_info->base + old_info->request_size, 0, rm_sz); |
| pattern_write_pattern(new_info->base + new_info->request_size, |
| new_info->base + new_info->request_size + add_sz |
| _IF_DEBUG("realloc in-place new pad + post-redzone")); |
| } else if (new_info->request_size < old_info->request_size) { |
| pattern_write_pattern(new_info->base + new_info->request_size, |
| new_info->base + old_info->request_size |
| _IF_DEBUG("realloc shrunk in-place new pad")); |
| } |
| } |
| } |
| |
| void |
| pattern_new_redzone(app_pc start, size_t size) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| /* We assume the redzone will be 4-byte aligned */ |
| ASSERT(ALIGNED(start, 4), "unaligned redzone start"); |
| ASSERT(ALIGNED(size, 4), "unaligned redzone size"); |
| pattern_write_pattern(start, start + size _IF_DEBUG("new redzone")); |
| } |
| |
| /* returns true if no errors were found */ |
| bool |
| pattern_handle_mem_ref(app_loc_t *loc, byte *addr, size_t size, |
| dr_mcontext_t *mc, bool is_write) |
| { |
| uint val; |
| size_t check_sz; |
| /* XXX i#774: for ref of >4 byte, we check the starting 4-byte only */ |
| check_sz = (size <= 2) ? 2 : 4; |
| /* there are several memory opnd, so it should be faster to check |
| * before lookup in the rbtree. |
| */ |
| if (safe_read(addr, check_sz, &val) && |
| ((ushort)val == (ushort)options.pattern || |
| (ushort)val == (ushort)pattern_reverse) && |
| (check_sz == 4 ? |
| (val == options.pattern || val == pattern_reverse) : true) && |
| /* we first do a pre-check to avoid expensive lookup |
| * XXX: we might miss the use-after-free error that accessing |
| * a freed pre-us block with smaller-than-redzone size. |
| */ |
| pattern_addr_pre_check(addr) && |
| /* We don't have alloc_ops.global_lock set, but by iterating for |
| * live chunks before freed, we shouldn't miss anything: even |
| * chunk re-use should still show up, and we will synchronize |
| * with a split or coalesce. |
| */ |
| (pattern_addr_in_redzone(addr, size) || |
| overlaps_delayed_free(addr, addr + size, NULL, NULL, NULL, false/*any*/))) { |
| /* XXX: i#786: the actually freed memory is neither in malloc tree |
| * nor in delayed free rbtree, in which case we cannot detect. We |
| * can maintain the information in pattern malloc tree, i.e. mark |
| * the tree node as invalid on free and remove/change the tree |
| * node on re-use of the memory. |
| */ |
| if (!check_unaddressable_exceptions(is_write, loc, addr, size, |
| false, mc)) { |
| report_unaddressable_access(loc, addr, size, |
| is_write ? DR_MEMPROT_WRITE : DR_MEMPROT_READ, |
| addr, addr + size, mc); |
| } |
| /* clobber the pattern to avoid duplicate reports for this same addr |
| * or possible ud2a if the 2nd memref is also unaddressable. |
| * XXX i#1476: full mode no longer avoids dup reports for unaddr: |
| * so we could perhaps remove this, though I don't know what the |
| * 2nd half of the above sentence means. Plus, pattern can't detect |
| * any more errors after a write, as the app clobbers the pattern value. |
| */ |
| /* should this be a safe_write? |
| * we reach here which means safe_read works and |
| * it is in redzone or delayed free, so not worth the overhead. |
| */ |
| /* i#902: it is only safe to set one byte here since the memory |
| * [addr, addr + size] might be partial buffer underflow. |
| */ |
| *(byte *)addr = 0; |
| return false; |
| } |
| return true; |
| } |
| |
| /*************************************************************************** |
| * Init/exit |
| */ |
| |
| void |
| pattern_init(void) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| if (options.pattern_use_malloc_tree) { |
| pattern_malloc_tree = rb_tree_create(NULL); |
| pattern_malloc_tree_rwlock = dr_rwlock_create(); |
| } |
| |
| /* reverse the byte order for unaligned checks: |
| * for example, if the pattern is 0x43214321, the reversed pattern is |
| * 0x21432143. If we check both value on any memory access, we are able |
| * to check both aligned and unaligned access. |
| */ |
| pattern_reverse = PATTERN_REVERSE(options.pattern); |
| /* do we use 4-byte checks only */ |
| if (options.pattern_max_2byte_faults == 0) |
| pattern_4byte_check_only = true; |
| flush_lock = dr_mutex_create(); |
| } |
| |
| void |
| pattern_exit(void) |
| { |
| ASSERT(options.pattern != 0, "should not be called"); |
| if (options.pattern_use_malloc_tree) { |
| dr_rwlock_destroy(pattern_malloc_tree_rwlock); |
| rb_tree_destroy(pattern_malloc_tree); |
| } |
| dr_mutex_destroy(flush_lock); |
| } |