| /* ********************************************************** |
| * Copyright (c) 2010-2024 Google, Inc. All rights reserved. |
| * Copyright (c) 2008-2010 VMware, 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. |
| */ |
| |
| /*************************************************************************** |
| * slowpath_x86.c: Dr. Memory memory read/write slowpath handling for x86 |
| */ |
| |
| #include "dr_api.h" |
| #include "drutil.h" |
| #include "drmemory.h" |
| #include "instru.h" |
| #include "slowpath.h" |
| #include "slowpath_arch.h" |
| #include "spill.h" |
| #include "fastpath.h" |
| #include "stack.h" |
| #include "alloc_drmem.h" |
| #include "heap.h" |
| #include "alloc.h" |
| #include "report.h" |
| #include "shadow.h" |
| #include "syscall.h" |
| #include "replace.h" |
| #include "perturb.h" |
| #include "annotations.h" |
| #ifdef TOOL_DR_HEAPSTAT |
| # include "../drheapstat/staleness.h" |
| #endif |
| #include "pattern.h" |
| #include <stddef.h> |
| #include "asm_utils.h" |
| |
| #ifdef STATISTICS |
| uint movs4_src_unaligned; |
| uint movs4_dst_unaligned; |
| uint movs4_src_undef; |
| uint movs4_med_fast; |
| uint cmps1_src_undef; |
| uint cmps1_med_fast; |
| #endif |
| |
| /*************************************************************************** |
| * ISA |
| */ |
| |
| #if 0 /* currently unused */ |
| static bool |
| reg_is_caller_saved(reg_id_t reg) |
| { |
| return (reg == DR_REG_XAX || reg == DR_REG_XDX || reg == DR_REG_XCX); |
| } |
| #endif |
| |
| bool |
| reg_is_8bit(reg_id_t reg) |
| { |
| return (reg >= REG_START_8 && reg <= REG_STOP_8); |
| } |
| |
| bool |
| reg_is_8bit_high(reg_id_t reg) |
| { |
| return (reg >= REG_AH && reg <= REG_BH); |
| } |
| |
| bool |
| reg_is_16bit(reg_id_t reg) |
| { |
| return (reg >= REG_START_16 && reg <= REG_STOP_16); |
| } |
| |
| bool |
| reg_offs_in_dword(reg_id_t reg) |
| { |
| if (reg_is_8bit_high(reg)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| bool |
| opc_is_push(uint opc) |
| { |
| return (opc == OP_push || opc == OP_push_imm || |
| opc == OP_call || opc == OP_call_ind || |
| opc == OP_call_far || opc == OP_call_far_ind || |
| opc == OP_pushf || opc == OP_pusha || opc == OP_enter); |
| /* We ignore kernel stack changes: OP_int, OP_int3, and OP_into */ |
| } |
| |
| bool |
| opc_is_pop(uint opc) |
| { |
| return (opc == OP_pop || opc == OP_popf || opc == OP_popa || opc == OP_leave || |
| opc == OP_ret || opc == OP_ret_far || opc == OP_iret); |
| /* DRi#537 made the post-sysenter ret visible so we no |
| * longer need to treat OP_sysenter as a ret. |
| */ |
| } |
| |
| bool |
| opc_is_stringop_loop(uint opc) |
| { |
| return (opc == OP_rep_ins || opc == OP_rep_outs || opc == OP_rep_movs || |
| opc == OP_rep_stos || opc == OP_rep_lods || opc == OP_rep_cmps || |
| opc == OP_repne_cmps || opc == OP_rep_scas || opc == OP_repne_scas); |
| } |
| |
| bool |
| opc_is_stringop(uint opc) |
| { |
| return (opc_is_stringop_loop(opc) || |
| opc == OP_ins || opc == OP_outs || opc == OP_movs || |
| opc == OP_stos || opc == OP_lods || opc == OP_cmps || |
| opc == OP_cmps || opc == OP_scas || opc == OP_scas); |
| } |
| |
| bool |
| opc_is_loopcc(uint opc) |
| { |
| return (opc == OP_loop || opc == OP_loope || opc == OP_loopne); |
| } |
| |
| static bool |
| opc_is_loop(uint opc) |
| { |
| return (opc == OP_jecxz || opc == OP_loop || opc == OP_loope || opc == OP_loopne); |
| } |
| |
| static bool |
| opc_is_move(uint opc) |
| { |
| return (opc == OP_mov_ld || opc == OP_mov_st || opc == OP_xchg || |
| opc == OP_movzx || opc == OP_movsx || |
| opc == OP_push || opc == OP_pop || opc == OP_pusha || opc == OP_popa || |
| opc == OP_ins || opc == OP_outs || opc == OP_movs || |
| opc == OP_stos || opc == OP_lods || |
| opc == OP_rep_ins || opc == OP_rep_outs || opc == OP_rep_movs || |
| opc == OP_rep_stos || opc == OP_rep_lods); |
| } |
| |
| static bool |
| opc_is_load_seg(uint opc) |
| { |
| return (opc == OP_les || opc == OP_lds || opc == OP_lss || |
| opc == OP_lfs || opc == OP_lgs); |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| /* count is in src #0 */ |
| static bool |
| opc_is_shift_src0(uint opc) |
| { |
| switch (opc) { |
| case OP_shl: case OP_shr: |
| case OP_sar: |
| case OP_rol: case OP_ror: |
| case OP_rcl: case OP_rcr: |
| case OP_psrlw: case OP_psrld: case OP_psrlq: |
| case OP_psraw: case OP_psrad: |
| case OP_psrldq: |
| case OP_psllw: case OP_pslld: case OP_psllq: |
| case OP_pslldq: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| # ifdef DEBUG |
| /* count is in src #1 */ |
| static bool |
| opc_is_shift_src1(uint opc) |
| { |
| switch (opc) { |
| case OP_shld: case OP_shrd: |
| case OP_vpsrlw: case OP_vpsrld: case OP_vpsrlq: |
| case OP_vpsraw: case OP_vpsrad: |
| case OP_vpsrldq: |
| case OP_vpsravd: |
| case OP_vpsrlvd: case OP_vpsrlvq: |
| case OP_vpsllw: case OP_vpslld: case OP_vpsllq: |
| case OP_vpslldq: |
| case OP_vpsllvd: case OP_vpsllvq: |
| return true; |
| default: |
| return false; |
| } |
| } |
| # endif |
| #endif /* TOOL_DR_MEMORY */ |
| |
| /* count is in src #0 */ |
| static bool |
| opc_is_gpr_shift_src0(uint opc) |
| { |
| return (opc == OP_shl || opc == OP_shr || opc == OP_sar || |
| opc == OP_rol || opc == OP_ror || |
| opc == OP_rcl || opc == OP_rcr); |
| } |
| |
| /* count is in src #1 */ |
| static bool |
| opc_is_gpr_shift_src1(uint opc) |
| { |
| return (opc == OP_shld || opc == OP_shrd); |
| } |
| |
| bool |
| opc_is_gpr_shift(uint opc) |
| { |
| return (opc_is_gpr_shift_src0(opc) || opc_is_gpr_shift_src1(opc)); |
| } |
| |
| bool |
| instr_is_jcc(instr_t *inst) |
| { |
| uint opc = instr_get_opcode(inst); |
| return ((opc >= OP_jo && opc <= OP_jnle) || |
| (opc >= OP_jo_short && opc <= OP_jnle_short)); |
| } |
| |
| bool |
| opc_is_cmovcc(uint opc) |
| { |
| return (opc >= OP_cmovo && opc <= OP_cmovnle); |
| } |
| |
| bool |
| opc_is_fcmovcc(uint opc) |
| { |
| return (opc >= OP_fcmovb && opc <= OP_fcmovnu); |
| } |
| |
| static bool |
| opc_loads_into_eip(uint opc) |
| { |
| return (opc == OP_ret || opc == OP_ret_far || opc == OP_iret || |
| opc == OP_call_ind || opc == OP_jmp_ind || |
| opc == OP_call_far_ind || opc == OP_jmp_far_ind); |
| /* DRi#537 made the post-sysenter ret visible so we no |
| * longer need to treat OP_sysenter as a ret. |
| */ |
| } |
| |
| /* can 2nd dst be treated as simply an extension of the 1st */ |
| bool |
| opc_2nd_dst_is_extension(uint opc) |
| { |
| return (opc == OP_rdtsc || opc == OP_rdmsr || opc == OP_rdpmc || |
| opc == OP_cpuid /*though has 4 dsts*/ || |
| opc == OP_div || opc == OP_idiv || |
| opc == OP_mul || opc == OP_imul); |
| } |
| |
| bool |
| reg_is_shadowed(int opc, reg_id_t reg) |
| { |
| /* i#471: we don't yet shadow floating-point regs */ |
| return (reg_is_gpr(reg) || |
| /* i#243: we don't yet shadow ymm regs */ |
| (reg_is_xmm(reg) && !reg_is_ymm(reg)) || |
| /* i#1473: propagate mmx */ |
| reg_is_mmx(reg)); |
| } |
| |
| bool |
| xax_is_used_subsequently(instr_t *inst) |
| { |
| while (inst != NULL) { |
| if (instr_uses_reg(inst, DR_REG_XAX)) |
| return true; |
| inst = instr_get_next(inst); |
| } |
| return false; |
| } |
| |
| uint |
| adjust_memop_push_offs(instr_t *inst) |
| { |
| /* DR's IR now properly encodes push sizes (DRi#164), except for OP_enter */ |
| if (instr_get_opcode(inst) == OP_enter) { |
| /* OP_enter's esp adjust (1st immed) is handled in |
| * instrument_esp_adjust, as it doesn't write those bytes |
| */ |
| uint extra_pushes = (uint) opnd_get_immed_int(instr_get_src(inst, 1)); |
| uint sz = opnd_size_in_bytes(opnd_get_size(instr_get_dst(inst, 1))); |
| ASSERT(opnd_is_immed_int(instr_get_src(inst, 1)), "internal error"); |
| return sz*extra_pushes; |
| } |
| return 0; |
| } |
| |
| opnd_t |
| adjust_memop(instr_t *inst, opnd_t opnd, bool write, uint *opsz, bool *pushpop_stackop) |
| { |
| /* DR's IR now properly encodes push sizes (DRi#164), except for OP_enter */ |
| uint opc = instr_get_opcode(inst); |
| uint sz = opnd_size_in_bytes(opnd_get_size(opnd)); |
| bool push = opc_is_push(opc); |
| bool pop = opc_is_pop(opc); |
| bool pushpop = false; /* is mem ref on stack for push/pop */ |
| if (opnd_uses_reg(opnd, DR_REG_XSP) || opc == OP_leave/*(ebp) not (esp)*/) { |
| if (write && push && opnd_is_base_disp(opnd)) { |
| uint extra_push_sz = adjust_memop_push_offs(inst); |
| pushpop = true; |
| if (extra_push_sz > 0) { |
| sz += extra_push_sz; |
| opnd_set_disp(&opnd, opnd_get_disp(opnd) - sz); |
| } |
| } else if (!write && pop && opnd_is_base_disp(opnd)) { |
| if (!instr_pop_into_esp(inst)) |
| pushpop = true; |
| if (opc == OP_leave) { |
| /* OP_leave's ebp->esp is handled in instrument_esp_adjust; here we |
| * treat it as simply a pop into ebp, though using the esp value |
| * copied from ebp, which we emulate here since we're doing it |
| * before the adjust instead of after: FIXME we'll report |
| * errors in both in the wrong order. |
| */ |
| ASSERT(opnd_get_base(opnd) == DR_REG_XBP, "OP_leave opnd wrong"); |
| } |
| /* OP_ret w/ immed is treated as single pop here; its esp |
| * adjustment is handled separately, as it doesn't read those bytes. |
| */ |
| } |
| } |
| /* we assume only +w ref for push (-w for pop) is the stack adjust */ |
| ASSERT(pushpop || (!(write && push) && !(!write && pop)) || instr_pop_into_esp(inst), |
| "internal stack op bad assumption"); |
| *opsz = sz; |
| *pushpop_stackop = pushpop; |
| return opnd; |
| } |
| |
| /* Some source operands we always check. Note that we don't need to explicitly |
| * list stack pointers or stringop bases as our basedisp checks already |
| * look at those registers. |
| */ |
| bool |
| always_check_definedness(instr_t *inst, int opnum) |
| { |
| uint opc = instr_get_opcode(inst); |
| return ((opc == OP_leave && opnum == 0) /* ebp */ || |
| /* count of loop is important: check it */ |
| (opc_is_stringop_loop(opc) && opnd_is_reg(instr_get_src(inst, opnum)) && |
| reg_overlap(opnd_get_reg(instr_get_src(inst, opnum)), DR_REG_XCX)) || |
| /* the comparison operands only: the others are moves */ |
| (opc == OP_cmpxchg && opnum > 0/*dst, eax*/) || |
| (opc == OP_cmpxchg8b && opnum <= 2/*memop, eax, edx*/) || |
| /* always check %cl. FIXME PR 408549: Valgrind propagates it (if undefined, |
| * dest is always undefined), but for us to propagate we need |
| * special checks to avoid calling adjust_source_shadow() |
| */ |
| ((opc_is_gpr_shift_src0(opc) && opnum == 0) || |
| (opc_is_gpr_shift_src1(opc) && opnum == 1))); |
| } |
| |
| /* For some instructions we check all source operands for definedness. */ |
| bool |
| instr_check_definedness(instr_t *inst) |
| { |
| uint opc = instr_get_opcode(inst); |
| return |
| /* always check conditional jumps */ |
| instr_is_cbr(inst) || |
| (options.check_uninit_non_moves && !opc_is_move(opc)) || |
| options.check_uninit_all || |
| (options.check_uninit_cmps && |
| /* a compare writes eflags but nothing else, or is a loop, cmps, or cmovcc. |
| * for cmpxchg* only some operands are compared: see always_check_definedness. |
| */ |
| ((instr_num_dsts(inst) == 0 && |
| TESTANY(EFLAGS_WRITE_6, instr_get_eflags(inst, DR_QUERY_INCLUDE_ALL))) || |
| opc_is_loop(opc) || opc_is_cmovcc(opc) || opc_is_fcmovcc(opc) || |
| opc == OP_cmps || opc == OP_rep_cmps || opc == OP_repne_cmps)) || |
| /* if eip is a destination we have to check the corresponding |
| * source. for ret or call, the other dsts/srcs are just esp, which |
| * has to be checked as an addressing register anyway. */ |
| opc_loads_into_eip(opc) || |
| opc_is_load_seg(opc) || /* we could not check the offs part */ |
| /* We consider arith flags as enough to transfer definedness to. |
| * Note that we don't shadow the floating-point status word, so |
| * most float ops should hit this. */ |
| (!instr_propagatable_dsts(inst) && |
| !TESTANY(EFLAGS_WRITE_6, instr_get_eflags(inst, DR_QUERY_INCLUDE_ALL)) && |
| /* though prefetch has nowhere to propagate uninit shadow vals to, we |
| * do not want to raise errors. so we ignore, under the assumption |
| * that a real move will happen soon enough to propagate: if not, |
| * little harm done. i#244. |
| */ |
| !instr_is_prefetch(inst)); |
| } |
| |
| /*************************************************************************** |
| * Definedness and Addressability Checking |
| */ |
| |
| bool |
| result_is_always_defined(instr_t *inst, bool natively) |
| { |
| /* Even if source operands are undefined, don't consider this instr as |
| * reading undefined values if: |
| * 1) clearing/setting all bits via: |
| * - and with 0 |
| * - or with ~0 |
| * - xor with self |
| * - packed subtract with self |
| * - sbb with self (PR 425498): now handled via num_true_srcs since must |
| * propagate eflags (PR 425622) |
| */ |
| int opc = instr_get_opcode(inst); |
| |
| #ifdef TOOL_DR_MEMORY |
| /* i#1529: mark an entire module defined */ |
| if (!natively && options.check_uninit_blocklist[0] != '\0') { |
| /* Fastpath should have already checked the cached value in |
| * bb_info_t.mark_defined, so we should only be paying this |
| * cost for each slowpath entry. |
| */ |
| app_pc pc = instr_get_app_pc(inst) != NULL ? |
| instr_get_app_pc(inst) : instr_get_raw_bits(inst); |
| if (module_is_on_check_uninit_blocklist(pc)) { |
| LOG(3, "module is on uninit blocklist: always defined\n"); |
| return true; |
| } |
| } |
| #endif |
| |
| /* Though our general non-const per-byte 0/1 checking would cover this, |
| * we optimize by looking for entire-dword consts up front |
| */ |
| if ((opc == OP_and && |
| opnd_is_immed_int(instr_get_src(inst, 0)) && |
| opnd_get_immed_int(instr_get_src(inst, 0)) == 0) || |
| (opc == OP_test && |
| opnd_is_immed_int(instr_get_src(inst, 1)) && |
| opnd_get_immed_int(instr_get_src(inst, 1)) == 0) || |
| (opc == OP_or && |
| opnd_is_immed_int(instr_get_src(inst, 0)) && |
| opnd_get_immed_int(instr_get_src(inst, 0)) == ~0) || |
| ((opc == OP_xor || opc == OP_pxor || opc == OP_xorps || opc == OP_xorpd || |
| opc == OP_psubq || opc == OP_subps || opc == OP_subpd) && |
| opnd_same(instr_get_src(inst, 0), instr_get_src(inst, 1)))) { |
| STATS_INC(andor_exception); |
| return true; |
| } |
| return false; |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| # ifdef UNIX |
| static bool |
| is_rawmemchr_uninit(void *drcontext, app_pc pc, reg_id_t reg, |
| dr_mcontext_t *mc, instr_t *inst) |
| { |
| char buf[16]; /* for safe_read */ |
| /* PR 406535: glibc's rawmemchr does some bit tricks that can end up using |
| * undefined or unaddressable values: |
| * <rawmemchr+113>: |
| * 0x0046b0d1 8b 48 08 mov 0x08(%eax) -> %ecx |
| * 0x0046b0d4 bf ff fe fe fe mov $0xfefefeff -> %edi |
| * 0x0046b0d9 31 d1 xor %edx %ecx -> %ecx |
| * 0x0046b0db 01 cf add %ecx %edi -> %edi |
| * 0x0046b0dd 73 2c jnb $0x0046b10b |
| * we have two different checks: one for !options.check_uninit_non_moves where |
| * the error isn't raised until the jnb and one for error on xor. |
| * FIXME: share code w/ is_rawmemchr_pattern() in alloc_drmem.c |
| */ |
| if (options.check_uninit_non_moves || |
| options.check_uninit_all) { |
| static const byte RAWMEMCHR_PATTERN_NONMOVES[5] = {0xbf, 0xff, 0xfe, 0xfe, 0xfe}; |
| ASSERT(sizeof(RAWMEMCHR_PATTERN_NONMOVES) <= BUFFER_SIZE_BYTES(buf), |
| "buf too small"); |
| if (reg == DR_REG_XCX && |
| instr_get_opcode(inst) == OP_xor && |
| safe_read(pc - sizeof(RAWMEMCHR_PATTERN_NONMOVES), |
| sizeof(RAWMEMCHR_PATTERN_NONMOVES), buf) && |
| memcmp(buf, RAWMEMCHR_PATTERN_NONMOVES, |
| sizeof(RAWMEMCHR_PATTERN_NONMOVES)) == 0) { |
| LOG(3, "suppressing positive from glibc rawmemchr pattern\n"); |
| register_shadow_set_ptrsz(DR_REG_XCX, SHADOW_PTRSZ_DEFINED); |
| STATS_INC(rawmemchr_exception); |
| return true; |
| } |
| } else { |
| /* FIXME PR 406535: verify there's no chance of a true positive */ |
| static const byte RAWMEMCHR_PATTERN[9] = |
| {0xbf, 0xff, 0xfe, 0xfe, 0xfe, 0x31, 0xd1, 0x01, 0xcf}; |
| ASSERT(sizeof(RAWMEMCHR_PATTERN) <= BUFFER_SIZE_BYTES(buf), "buf too small"); |
| if (reg == REG_EFLAGS && |
| (instr_get_opcode(inst) == OP_jnb || |
| instr_get_opcode(inst) == OP_jnb_short) && |
| safe_read(pc - sizeof(RAWMEMCHR_PATTERN), sizeof(RAWMEMCHR_PATTERN), buf) && |
| memcmp(buf, RAWMEMCHR_PATTERN, sizeof(RAWMEMCHR_PATTERN)) == 0) { |
| uint val = get_shadow_register(DR_REG_XCX); |
| /* We want to only allow the end of the search to be suppressed, |
| * to avoid suppressing a real positive. We assume going in |
| * forward direction (there is no rawmemrchr). |
| * Not easy to compute whether done w/ search: for now just |
| * going to require bottom byte to be defined to at least |
| * not suppress the entire byte being undefined. |
| * FIXME: do better: similarly w/ the strlen exceptions. |
| */ |
| if ((val & 0x3) == 0) { |
| LOG(3, "suppressing positive from glibc rawmemchr pattern\n"); |
| set_shadow_eflags(SHADOW_DWORD_DEFINED); |
| STATS_INC(rawmemchr_exception); |
| return true; |
| } else |
| LOG(3, "NOT suppressing glibc rawmemchr w/ val 0x%x\n", val); |
| } |
| } |
| return false; |
| } |
| |
| static bool |
| is_strrchr_uninit(void *drcontext, app_pc pc, reg_id_t reg, |
| dr_mcontext_t *mc, instr_t *inst) |
| { |
| bool match = false; |
| char buf[16]; /* for safe_read */ |
| /* glibc's strrchr: |
| * +0 L3 8b 56 08 mov 0x08(%esi) -> %edx |
| * +3 L3 bf ff fe fe fe mov $0xfefefeff -> %edi |
| * +8 L3 01 d7 add %edx %edi -> %edi |
| * +10 L3 73 66 jnb $0x00466986 |
| * glibc's strlen has several of these, for offsets 0, 4, 8, and 0xc: |
| * +0 L3 8b 48 0c mov 0x0c(%eax) -> %ecx |
| * +3 L3 ba ff fe fe fe mov $0xfefefeff -> %edx |
| * +8 L3 01 ca add %ecx %edx -> %edx |
| * +10 L3 73 0b jnb $0x0046640e |
| */ |
| /* FIXME PR 406535: verify there's no chance of a true positive */ |
| /* we skip the modrm of the add since it varies */ |
| static const uint skip = 1; |
| /* we stop prior to the mov-imm opcode since it varies */ |
| static const byte STR_ROUTINE_PATTERN[5] = |
| {0xff, 0xfe, 0xfe, 0xfe, 0x01}; |
| ASSERT(sizeof(STR_ROUTINE_PATTERN) <= BUFFER_SIZE_BYTES(buf), "buf too small"); |
| if (reg == REG_EFLAGS && |
| (instr_get_opcode(inst) == OP_jnb || |
| instr_get_opcode(inst) == OP_jnb_short) && |
| safe_read(pc - skip - sizeof(STR_ROUTINE_PATTERN), |
| sizeof(STR_ROUTINE_PATTERN), buf) && |
| memcmp(buf, STR_ROUTINE_PATTERN, sizeof(STR_ROUTINE_PATTERN)) == 0) { |
| uint val; |
| /* See above notes on only end of search. For these patterns, |
| * the load is into the 1st source of the add. |
| */ |
| instr_t add; |
| instr_init(drcontext, &add); |
| if (safe_decode(drcontext, pc - 2, &add, NULL) && |
| instr_valid(&add) && opnd_is_reg(instr_get_src(&add, 0))) { |
| val = get_shadow_register(opnd_get_reg(instr_get_src(&add, 0))); |
| if ((val & 0x3) == 0) { |
| LOG(3, "suppressing positive from glibc strrchr/strlen pattern\n"); |
| set_shadow_eflags(SHADOW_DWORD_DEFINED); |
| STATS_INC(strrchr_exception); |
| match = true; |
| } else |
| LOG(3, "NOT suppressing glibc strrchr/strlen w/ val 0x%x\n", val); |
| } else { |
| ASSERT(false, "strrchr/strlen pattern: invalid/unexpected instr"); |
| } |
| instr_free(drcontext, &add); |
| } |
| /* strrchr again, w/ some variations on what registers are xor'd: |
| * +0 L3 31 d7 xor %edx %edi -> %edi |
| * +2 L3 81 cf ff fe fe fe or $0xfefefeff %edi -> %edi |
| * +8 L3 47 inc %edi -> %edi |
| * +9 L3 0f 85 cb 00 00 00 jnz $0x0046698c |
| * variation: |
| * +0 L3 31 ca xor %ecx %edx -> %edx |
| * +2 L3 81 ca ff fe fe fe or $0xfefefeff %edx -> %edx |
| * +8 L3 42 inc %edx -> %edx |
| * +9 L3 75 4e jnz $0x00466417 |
| */ |
| /* FIXME PR 406535: verify there's no chance of a true positive */ |
| static const byte STRRCHR_PATTERN_1[7] = |
| {0x81, 0xcf, 0xff, 0xfe, 0xfe, 0xfe, 0x47}; |
| static const byte STRRCHR_PATTERN_2[7] = |
| {0x81, 0xca, 0xff, 0xfe, 0xfe, 0xfe, 0x42}; |
| ASSERT(sizeof(STRRCHR_PATTERN_1) == sizeof(STRRCHR_PATTERN_2), "size changed"); |
| ASSERT(sizeof(STRRCHR_PATTERN_1) <= BUFFER_SIZE_BYTES(buf), "buf too small"); |
| if (reg == REG_EFLAGS && |
| /* I've seen OP_je_short as well (in esxi glibc) => allowing any jcc */ |
| instr_is_cbr(inst) && |
| safe_read(pc - sizeof(STRRCHR_PATTERN_1), sizeof(STRRCHR_PATTERN_1), buf) && |
| (memcmp(buf, STRRCHR_PATTERN_1, sizeof(STRRCHR_PATTERN_1)) == 0 || |
| memcmp(buf, STRRCHR_PATTERN_2, sizeof(STRRCHR_PATTERN_2)) == 0)) { |
| uint val; |
| if (memcmp(buf, STRRCHR_PATTERN_2, sizeof(STRRCHR_PATTERN_2)) == 0) |
| val = get_shadow_register(DR_REG_XDX); |
| else |
| val = get_shadow_register(DR_REG_XDI); |
| if ((val & 0x3) == 0) { |
| LOG(3, "suppressing positive from glibc strrchr pattern\n"); |
| set_shadow_eflags(SHADOW_DWORD_DEFINED); |
| STATS_INC(strrchr_exception); |
| match = true; |
| } else |
| LOG(3, "NOT suppressing glibc strrchr/strlen w/ val 0x%x\n", val); |
| } |
| return match; |
| } |
| # endif /* UNIX */ |
| |
| static bool |
| is_strlen_uninit(void *drcontext, app_pc pc, reg_id_t reg, |
| dr_mcontext_t *mc, instr_t *inst) |
| { |
| char buf[16]; /* for safe_read */ |
| /* i#1505: mingw inlines strlen: |
| * +0 L3 8b 13 mov (%ebx)[4byte] -> %edx |
| * +2 L3 83 c3 04 add $0x00000004 %ebx -> %ebx |
| * +5 L3 8d 82 ff fe fe fe lea 0xfefefeff(%edx) -> %eax |
| * +11 L3 f7 d2 not %edx -> %edx |
| * +13 L3 21 d0 and %edx %eax -> %eax |
| * +15 L3 25 80 80 80 80 and $0x80808080 %eax -> %eax |
| * +20 L3 74 ea jz $0x0040f7b0 |
| */ |
| static const byte STRLEN_PATTERN[] = |
| {0xfe, 0xfe, 0xfe, 0xf7, 0xd2, 0x21, 0xd0, 0x25, 0x80, 0x80, 0x80, 0x80}; |
| ASSERT(sizeof(STRLEN_PATTERN) <= BUFFER_SIZE_BYTES(buf), "buf too small"); |
| if (reg == REG_EFLAGS && |
| (instr_get_opcode(inst) == OP_jz || |
| instr_get_opcode(inst) == OP_jz_short) && |
| safe_read(pc - sizeof(STRLEN_PATTERN), |
| sizeof(STRLEN_PATTERN), buf) && |
| memcmp(buf, STRLEN_PATTERN, sizeof(STRLEN_PATTERN)) == 0) { |
| STATS_INC(strlen_uninit_exception); |
| return true; |
| } |
| return false; |
| } |
| |
| /* All current non-syscall uses already have inst decoded so we require it |
| * for efficiency |
| */ |
| bool |
| check_undefined_reg_exceptions(void *drcontext, app_loc_t *loc, reg_id_t reg, |
| dr_mcontext_t *mc, instr_t *inst) |
| { |
| bool match = false; |
| byte *pc; |
| if (loc->type != APP_LOC_PC) |
| return false; /* syscall */ |
| ASSERT(inst != NULL, "must pass in inst if non-syscall"); |
| pc = loc_to_pc(loc); |
| ASSERT(instr_valid(inst), "unknown suspect instr"); |
| |
| #ifdef UNIX |
| match = is_rawmemchr_uninit(drcontext, pc, reg, mc, inst); |
| if (!match) { |
| match = is_strrchr_uninit(drcontext, pc, reg, mc, inst); |
| } |
| #endif |
| if (!match) { |
| match = is_strlen_uninit(drcontext, pc, reg, mc, inst); |
| } |
| |
| return match; |
| } |
| |
| static bool |
| instr_is_load_to_nongpr(instr_t *inst) |
| { |
| int opc = instr_get_opcode(inst); |
| if (opc == OP_fld) |
| return true; |
| /* With the initial i#243 implementation we no longer need to look for |
| * movq/movdqu/movdqa here. |
| */ |
| return false; |
| } |
| |
| static bool |
| instr_is_store_from_nongpr(instr_t *inst) |
| { |
| int opc = instr_get_opcode(inst); |
| /* With the initial i#243 implementation we no longer need to look for |
| * movq/movdqu/movdqa here. |
| */ |
| if (opc == OP_fstp) { |
| return opnd_is_memory_reference(instr_get_dst(inst, 0)) && |
| opnd_is_reg(instr_get_src(inst, 0)); |
| } |
| return false; |
| } |
| |
| /* i#471/i#931 heuristic: match "fld;fstp" to avoid false pos until we have proper |
| * floating point register shadowing and propagation. |
| * |
| * i#1453: generalized to handle "load memory from address A into |
| * xmmN; store xmmN into address B". |
| * |
| * Should only be called on an uninit memory read. The general |
| * approach is to mark B as bitlevel so we come back to the slowpath |
| * on the store, where we perform the shadow copy A->B. |
| * |
| * An alternative to this uninit-exception-based approach would be to recognize |
| * "fld;fstp" during instrumentation, special-case the fld to check |
| * addressability and not definedness, and special-case the fstp to do a mem2mem |
| * propagation with a fake src taken from the fld -- but, we'd need the slowpath |
| * to recognize that too, which requires decode-backward or a hashtable; and, |
| * the current fastpath doesn't support 8-byte+ mem2mem propagation. Thus, this |
| * approach here seems simpler and not much slower. |
| */ |
| static bool |
| check_mem_copy_via_nongpr(app_loc_t *loc, app_pc addr, uint sz, dr_mcontext_t *mc, |
| uint *idx) |
| { |
| void *drcontext = dr_get_current_drcontext(); |
| cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem); |
| app_pc pc, next_pc; |
| opnd_t fstp_mem; |
| byte *store_addr = NULL; |
| app_pc load_pc, store_pc = NULL; |
| reg_id_t xfer_reg = DR_REG_NULL; |
| instr_t inst; |
| umbra_shadow_memory_info_t info; |
| int num_regs; |
| bool allow_base_write, writes_ebp; |
| opnd_t overlap_write; |
| |
| /* Handle subsequent calls from handle_mem_ref() (one per byte). |
| * We don't short-circuit b/c we want to ensure they're all addressable. |
| * This also avoid mixups on a double load;load;store;store. |
| */ |
| if (cpt->mem2fpmm_source != NULL) |
| return true; |
| |
| if (!options.fpxmm_mem2mem_prop) |
| return false; |
| if (loc->type == APP_LOC_SYSCALL) |
| return false; |
| if (addr == NULL) /* equals our sentinel value in cpt->mem2fpmm_source */ |
| return false; |
| instr_init(drcontext, &inst); |
| pc = loc_to_pc(loc); |
| if (!safe_decode(drcontext, pc, &inst, &next_pc)) |
| return false; |
| if (!instr_is_load_to_nongpr(&inst)) { |
| instr_free(drcontext, &inst); |
| return false; |
| } |
| load_pc = pc; |
| xfer_reg = opnd_get_reg(instr_get_dst(&inst, 0)); |
| /* We support instructions in between the load and store, as we see |
| * such patterns with several different compilers. |
| */ |
| # define NONGPR_MEMCOPY_MAX_DISTANCE 128 |
| DOLOG(3, { |
| LOG(3, "%s: found load @"PFX", looking for store:\n", __FUNCTION__, load_pc); |
| disassemble_with_info(drcontext, pc, LOGFILE_GET(drcontext), true, true); |
| }); |
| do { |
| instr_reset(drcontext, &inst); |
| pc = next_pc; |
| DOLOG(3, { |
| disassemble_with_info(drcontext, pc, LOGFILE_GET(drcontext), true, true); |
| }); |
| if (!safe_decode(drcontext, pc, &inst, &next_pc)) |
| break; |
| if (instr_is_cti(&inst) || instr_is_syscall(&inst) || !instr_opcode_valid(&inst)) |
| break; |
| /* XXX: we could check for overlap with the src to ensure it doesn't |
| * change, but it gets complex with our allowance of changes to the |
| * base reg of the store. We live w/ the risk for now. |
| */ |
| if (instr_is_store_from_nongpr(&inst) && |
| opnd_get_reg(instr_get_src(&inst, 0)) == xfer_reg) { |
| store_pc = pc; |
| break; |
| } |
| } while (next_pc < pc + NONGPR_MEMCOPY_MAX_DISTANCE); |
| if (store_pc == NULL) { |
| instr_free(drcontext, &inst); |
| return false; |
| } |
| fstp_mem = instr_get_dst(&inst, 0); |
| ASSERT(opnd_is_memory_reference(fstp_mem), "fstp must write to mem"); |
| instr_free(drcontext, &inst); |
| /* Second pass to ensure we can identify the memory address of the store. |
| * We're aggressive here and we risk corner cases with aliases in order to |
| * avoid having to go to suppressions to deal with these. |
| * We'll do the wrong thing if something in between the load and store |
| * writes to memory overlapping the load address: we'll then propagate |
| * the wrong shadow value. |
| */ |
| pc = load_pc; |
| num_regs = opnd_num_regs_used(fstp_mem); |
| allow_base_write = num_regs == 1 && opnd_is_base_disp(fstp_mem); |
| writes_ebp = false; |
| overlap_write = opnd_create_null(); |
| while (pc < store_pc) { |
| /* Bail if in-between instr writes to any reg used to construct fstp address */ |
| int i; |
| instr_reset(drcontext, &inst); |
| if (!safe_decode(drcontext, pc, &inst, &pc)) |
| return false; |
| if (!writes_ebp && instr_writes_to_reg(&inst, DR_REG_XBP, DR_QUERY_INCLUDE_ALL)) |
| writes_ebp = true; |
| for (i = 0; i < num_regs; i++) { |
| if (instr_writes_to_reg(&inst, opnd_get_reg_used(fstp_mem, i), |
| DR_QUERY_INCLUDE_ALL)) { |
| /* We do support a write to a reg if that's the sole addressing |
| * reg and if the write comes from an ebp-based slot where |
| * ebp does not change. This is to handle cases like this |
| * gcc code: |
| * dd 40 04 fld 0x04(%eax) -> %st0 |
| * 8b 45 08 mov 0x08(%ebp) -> %eax |
| * dd 58 04 fstp %st0 -> 0x04(%eax) |
| * And this VS2013 Chromium Release code: |
| * f30f6f00 movdqu xmm0,xmmword ptr [eax] |
| * 8b4508 mov eax,dword ptr [ebp+8] |
| * f30f7f00 movdqu xmmword ptr [eax],xmm0 |
| */ |
| if (allow_base_write && opnd_is_null(overlap_write) && |
| instr_num_srcs(&inst) == 1 && |
| instr_num_dsts(&inst) == 1 && opnd_is_reg(instr_get_dst(&inst, 0)) && |
| opnd_get_reg(instr_get_dst(&inst, 0)) == opnd_get_base(fstp_mem)) { |
| overlap_write = instr_get_src(&inst, 0); |
| } else { |
| instr_free(drcontext, &inst); |
| return false; |
| } |
| } |
| } |
| } |
| instr_free(drcontext, &inst); |
| if (!opnd_is_null(overlap_write)) { |
| LOG(3, "%s: checking overlap with xmm/fp store\n", __FUNCTION__); |
| if (opnd_is_base_disp(overlap_write) && |
| opnd_get_base(overlap_write) == DR_REG_XBP && |
| opnd_get_index(overlap_write) == DR_REG_NULL && |
| !writes_ebp) { |
| reg_t baseval; |
| reg_t *slot = (reg_t *) opnd_compute_address(overlap_write, mc); |
| if (safe_read(slot, sizeof(baseval), &baseval)) { |
| reg_id_t basereg = opnd_get_base(fstp_mem); |
| reg_t save = reg_get_value(basereg, mc); |
| LOG(3, "%s: old %s = "PFX", new="PFX"\n", __FUNCTION__, |
| get_register_name(basereg), save, baseval); |
| reg_set_value(basereg, mc, baseval); |
| store_addr = opnd_compute_address(fstp_mem, mc); |
| LOG(3, "%s: store addr computed to be "PFX"\n", __FUNCTION__, store_addr); |
| reg_set_value(basereg, mc, save); |
| } |
| } |
| } else |
| store_addr = opnd_compute_address(fstp_mem, mc); |
| if (store_addr == NULL || |
| /* overlap is ok, but we require the same size */ |
| opnd_size_in_bytes(opnd_get_size(fstp_mem)) != sz) { |
| return false; |
| } |
| LOG(3, "%s: matched pattern for store addr="PFX"\n", __FUNCTION__, store_addr); |
| /* Now disable the fstp instru fastpath by marking the dest mem as bitlevel. |
| * We'll propagate the shadow values in the fstp slowpath. |
| * Otherwise, fstp's fastpath will blindly mark the dest memory as defined. |
| * Note that we can't instead propagate here and mark base reg as |
| * undefined, as that check may be optimized out. |
| */ |
| umbra_shadow_memory_info_init(&info); |
| cpt->mem2fpmm_prev_shadow = shadow_get_byte(&info, store_addr); |
| cpt->mem2fpmm_dest = store_addr; |
| cpt->mem2fpmm_pc = store_pc; |
| DODEBUG({ |
| /* used for a debug check in slow_path() */ |
| cpt->mem2fpmm_load_pc = load_pc; |
| }); |
| shadow_set_byte(&info, store_addr, SHADOW_DEFINED_BITLEVEL); |
| ASSERT(cpt->mem2fpmm_source == NULL, "mem2fpmm_source wasn't cleared"); |
| /* Point at the start addr, as we're going to propagate the whole thing |
| * (we might be midway through if the first few bytes are defined and we |
| * hit uninit bytes in the middle). |
| */ |
| cpt->mem2fpmm_source = addr - *idx; |
| STATS_INC(fldfst_exception); |
| return true; |
| } |
| |
| bool |
| check_undefined_exceptions(bool pushpop, bool write, app_loc_t *loc, app_pc addr, |
| uint sz, uint *shadow, dr_mcontext_t *mc, uint *idx) |
| { |
| bool match = false; |
| /* I used to have an exception for uninits in heap headers, but w/ |
| * proper operation headers should be unaddr. Plus, the exception here, |
| * which marked as defined, was only in slowpath: in fastpath the uninit |
| * bits are propagated and won't be recognized as heap headers when |
| * they bubble up. Thus it was removed since no longer necessary. |
| */ |
| /* We now check for result_is_always_defined() up front in the |
| * slow path to avoid the redundant decode here, which can be a |
| * noticeable performance hit (PR 622253) |
| */ |
| if (!match && !pushpop && !write) |
| match = check_mem_copy_via_nongpr(loc, addr, sz, mc, idx); |
| return match; |
| } |
| |
| #endif /* TOOL_DR_MEMORY */ |
| |
| /* Opcodes that write to subreg at locations not fixed in the low part of the reg */ |
| bool |
| opc_dst_subreg_nonlow(int opc) |
| { |
| switch (opc) { |
| case OP_pextrb: |
| case OP_pextrw: |
| case OP_pextrd: |
| case OP_vpextrb: |
| case OP_vpextrw: |
| case OP_vpextrd: |
| case OP_extractps: |
| case OP_pinsrb: |
| case OP_pinsrw: |
| case OP_pinsrd: |
| case OP_vpinsrb: |
| case OP_vpinsrw: |
| case OP_vpinsrd: |
| case OP_insertps: |
| case OP_movhps: |
| case OP_movhpd: |
| return true; |
| } |
| return false; |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| |
| /* Same base interface as map_src_to_dst(), but this one takes in an |
| * arbitrary opcode that can differ from comb->opcode, a per-element |
| * src_bytenum, an offset for the dst bytenum, and a per-element opsz, |
| * allowing for use on packed shifts as well as GPR shifts. |
| */ |
| static bool |
| map_src_to_dst_shift(shadow_combine_t *comb DR_PARAM_INOUT, uint opc, int opnum, |
| int src_bytenum, uint src_offs, uint opsz, uint shadow) |
| { |
| reg_t shift; |
| /* Be sure to use opsz, the element size, NOT comb->opsz; similarly, use |
| * the passed-in opc, NOT comb->opcode, for the operation. |
| */ |
| ASSERT(comb->inst != NULL, "need inst for shifts"); |
| ASSERT(opc_is_shift_src0(comb->opcode) || opc_is_shift_src1(comb->opcode), |
| "unknown shift"); |
| if (!get_cur_src_value(dr_get_current_drcontext(), comb->inst, |
| opc_is_shift_src0(comb->opcode) ? 0 : 1, &shift)) { |
| ASSERT(false, "failed to get shift amount"); |
| /* graceful failure */ |
| return false; |
| } |
| LOG(4, " src bytenum %d, offs %d, opsz %d, shift %d\n", src_bytenum, src_offs, |
| opsz, shift); |
| if (shift > opsz*8) |
| shift = opsz*8; /* XXX i#101: for a rotate we want shift % (opsz*8) */ |
| if (shift == 0) { |
| /* no flags are changed for shift==0 */ |
| accum_shadow(&comb->dst[src_offs + src_bytenum], shadow); |
| return true; |
| } |
| if (opc == OP_shl) { |
| /* If shift % 8 != 0 we touch two bytes: */ |
| int map1 = src_bytenum + shift/8; |
| int map2 = src_bytenum + (shift-1)/8 + 1; |
| if (map1 >= opsz) { |
| if (map1 == opsz) { |
| /* bit shifted off goes to CF */ |
| LOG(4, " accum eflags %d + %d\n", comb->eflags, shadow); |
| accum_shadow(&comb->eflags, shadow); |
| } |
| /* shifted off the end */ |
| return true; |
| } |
| LOG(4, " accum @%d %d + %d\n", src_offs + map1, comb->dst[src_offs + map1], |
| shadow); |
| accum_shadow(&comb->dst[src_offs + map1], shadow); |
| if (map1 != map2 && map2 < opsz) { |
| LOG(4, " accum @%d %d + %d\n", src_offs + map2, comb->dst[src_offs + map2], |
| shadow); |
| accum_shadow(&comb->dst[src_offs + map2], shadow); |
| } |
| accum_shadow(&comb->eflags, shadow); |
| return true; |
| } else if (opc == OP_shr || opc == OP_sar) { |
| /* If shift % 8 != 0 we touch two bytes: */ |
| int map1 = src_bytenum - shift/8; |
| int map2 = src_bytenum - ((shift-1)/8 + 1); |
| if (opc == OP_sar && src_bytenum == opsz - 1) { |
| /* Top bit is what's shifted in */ |
| int i; |
| for (i = 0; i <= shift/8 && i < opsz; i ++) |
| accum_shadow(&comb->dst[src_offs + opsz - 1 - i], shadow); |
| accum_shadow(&comb->eflags, shadow); |
| return true; |
| } |
| if (map1 >= 0) { /* if not shifted off the end */ |
| LOG(4, " accum @%d %d + %d\n", src_offs + map1, comb->dst[src_offs + map1], |
| shadow); |
| accum_shadow(&comb->dst[src_offs + map1], shadow); |
| } |
| if (map1 != map2 && map2 >= 0) { |
| LOG(4, " accum @%d %d + %d\n", src_offs + map2, comb->dst[src_offs + map2], |
| shadow); |
| accum_shadow(&comb->dst[src_offs + map2], shadow); |
| } |
| accum_shadow(&comb->eflags, shadow); |
| /* We assume we don't need to proactively mark the top now-0 bits |
| * as defined for OP_shr b/c our method of combining starts w/ defined |
| */ |
| return true; |
| } else { |
| /* FIXME i#101: add rotate opcodes + shrd/shld */ |
| } |
| return false; /* unhandled */ |
| } |
| |
| /* Takes the shadow value \p shadow for the \p src_bytenum-th byte in |
| * the source operand ordinal \p opnum of instruction \p inst and |
| * places it into comb's dst and eflags repositories, combining with |
| * what's already there. |
| */ |
| void |
| map_src_to_dst(shadow_combine_t *comb DR_PARAM_INOUT, int opnum, int src_bytenum, |
| uint shadow) |
| { |
| int opc = comb->opcode; |
| uint opsz = comb->opsz; |
| uint shift = 0; |
| if (opc_is_gpr_shift(opc)) { |
| if (map_src_to_dst_shift(comb, comb->opcode, opnum, src_bytenum, 0, |
| comb->opsz, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } |
| } |
| /* XXX PR 408551: for now we are not considering propagation to |
| * more-significant bytes from arithmetic ops. |
| */ |
| /* For instrs w/ multiple GPR/mem dests, or concatenated sources, |
| * we need to make sure we lay out the dests side by side in the array. |
| * |
| * Here we check for srcs that do NOT simply go into the lowest slot: |
| */ |
| switch (opc) { |
| case OP_xchg: |
| case OP_xadd: |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| accum_shadow(&comb->dst[opsz*(1 - opnum) + src_bytenum], shadow); |
| break; |
| case OP_cmpxchg8b: |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| /* opnds: cmpxchg8b mem8 %eax %edx %ecx %ebx -> mem8 %eax %edx |
| * operation: if (edx:eax == mem8) mem8 = ecx:ebx; else edx:eax = mem8 |
| * we just combine all 3 sources and write the result to both dests. |
| */ |
| switch (opnum) { |
| case 0: shift = 0; break; |
| case 1: shift = 0; break; |
| case 2: shift = 1; break; |
| case 3: shift = 1; break; |
| case 4: shift = 0; break; |
| default: ASSERT(false, "invalid opnum"); |
| } |
| accum_shadow(&comb->dst[opsz*shift + src_bytenum], shadow); |
| break; |
| case OP_bswap: |
| accum_shadow(&comb->dst[(opsz - 1) - src_bytenum], shadow); |
| return; |
| #ifndef X64 |
| case OP_pusha: |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| if (opnd_is_reg(comb->opnd)) { |
| reg_id_t reg = opnd_get_reg(comb->opnd); |
| shift = opsz*(reg_to_pointer_sized(reg) - DR_REG_EAX); |
| accum_shadow(&comb->dst[shift + src_bytenum], shadow); |
| } |
| break; |
| #endif |
| case OP_punpcklbw: |
| case OP_vpunpcklbw: |
| /* Dst is opnum==1 and its 0-n/2 => 0, 2, 4, 6, .... |
| * Src is opnum==0 and its 0-n/2 => 1, 3, 5, 7, ... |
| */ |
| if (src_bytenum < opsz/2) |
| accum_shadow(&comb->dst[src_bytenum*2 + (1 - opnum)], shadow); |
| break; |
| case OP_punpcklwd: |
| case OP_vpunpcklwd: |
| if (src_bytenum < opsz/2) { |
| accum_shadow(&comb->dst[(src_bytenum/2) *4 + (src_bytenum % 2) + |
| 2*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpckldq: |
| case OP_vpunpckldq: |
| case OP_unpcklps: |
| case OP_vunpcklps: |
| if (src_bytenum < opsz/2) { |
| accum_shadow(&comb->dst[(src_bytenum/4) *8 + (src_bytenum % 4) + |
| 4*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpcklqdq: |
| case OP_vpunpcklqdq: |
| case OP_unpcklpd: |
| case OP_vunpcklpd: |
| if (src_bytenum < opsz/2) { |
| accum_shadow(&comb->dst[(src_bytenum/8) *16 + (src_bytenum % 8) + |
| 8*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpckhbw: |
| case OP_vpunpckhbw: |
| if (src_bytenum >= opsz/2) { |
| accum_shadow(&comb->dst[(src_bytenum-opsz/2)*2 + (1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpckhwd: |
| case OP_vpunpckhwd: |
| if (src_bytenum >= opsz/2) { |
| accum_shadow(&comb->dst[((src_bytenum-opsz/2)/2) *4 + (src_bytenum % 2) + |
| 2*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpckhdq: |
| case OP_vpunpckhdq: |
| case OP_unpckhps: |
| case OP_vunpckhps: |
| if (src_bytenum >= opsz/2) { |
| accum_shadow(&comb->dst[((src_bytenum-opsz/2)/4) *8 + (src_bytenum % 4) + |
| 4*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_punpckhqdq: |
| case OP_vpunpckhqdq: |
| case OP_unpckhpd: |
| case OP_vunpckhpd: |
| if (src_bytenum >= opsz/2) { |
| accum_shadow(&comb->dst[((src_bytenum-opsz/2)/8) *16 + (src_bytenum % 8) + |
| 8*(1 - opnum)], shadow); |
| } |
| break; |
| case OP_shufps: |
| case OP_shufpd: |
| case OP_vshufps: |
| case OP_vshufpd: { |
| ptr_uint_t immed = 0; |
| ASSERT(comb->inst != NULL, "need inst for OP_shuf*"); |
| if (!get_cur_src_value(NULL, comb->inst, |
| (opc == OP_vshufps || opc == OP_vshufpd) ? 2 : 1, |
| &immed)) |
| ASSERT(false, "failed to get immed"); /* rel build: keep going */ |
| switch (opc) { |
| case OP_shufps: |
| case OP_vshufps: { |
| uint mod = src_bytenum % 4; |
| uint shift = src_bytenum >= 16 ? 16 : 0; |
| if (opnum == 0) { /* the src */ |
| uint dst2 = (immed >> 4) & 0x3; |
| uint dst3 = (immed >> 6) & 0x3; |
| if (dst2 == (src_bytenum % 16)/4) |
| accum_shadow(&comb->dst[shift + 8 + mod], shadow); |
| if (dst3 == (src_bytenum % 16)/4) |
| accum_shadow(&comb->dst[shift + 12 + mod], shadow); |
| } else { /* the dst, or 2nd src for vshufps */ |
| uint dst0 = immed & 0x3; |
| uint dst1 = (immed >> 2) & 0x3; |
| if (dst0 == (src_bytenum % 16)/4) |
| accum_shadow(&comb->dst[shift + mod], shadow); |
| if (dst1 == (src_bytenum % 16)/4) |
| accum_shadow(&comb->dst[shift + 4 + mod], shadow); |
| } |
| break; |
| } |
| case OP_shufpd: |
| case OP_vshufpd: { |
| uint mod = src_bytenum % 8; |
| uint shift = src_bytenum >= 16 ? 16 : 0; |
| if (opnum == 0) { /* the src */ |
| if (immed == (src_bytenum % 16)/8) |
| accum_shadow(&comb->dst[shift + 8 + mod], shadow); |
| } else { /* the dst, or 2nd src for vshufps */ |
| if (immed == (src_bytenum % 16)/8) |
| accum_shadow(&comb->dst[shift + mod], shadow); |
| } |
| break; |
| } |
| } |
| break; |
| } |
| case OP_pshufw: |
| case OP_pshufd: |
| case OP_pshufhw: |
| case OP_pshuflw: |
| case OP_vpshufhw: |
| case OP_vpshufd: |
| case OP_vpshuflw: |
| /* FIXME i#1484: fill in proper shuffling */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_pshufb: |
| case OP_vpshufb: |
| /* FIXME i#1484: this one is complex, bailing for now */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psrlw: |
| case OP_vpsrlw: |
| if (map_src_to_dst_shift(comb, OP_shr, opnum, src_bytenum % 2, |
| ALIGN_BACKWARD(src_bytenum, 2), 2, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psrld: |
| case OP_vpsrld: |
| if (map_src_to_dst_shift(comb, OP_shr, opnum, src_bytenum % 4, |
| ALIGN_BACKWARD(src_bytenum, 4), 4, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psrlq: |
| case OP_vpsrlq: |
| if (map_src_to_dst_shift(comb, OP_shr, opnum, src_bytenum % 8, |
| ALIGN_BACKWARD(src_bytenum, 8), 8, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psraw: |
| case OP_vpsraw: |
| if (map_src_to_dst_shift(comb, OP_sar, opnum, src_bytenum % 2, |
| ALIGN_BACKWARD(src_bytenum, 2), 2, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psrad: |
| case OP_vpsrad: |
| if (map_src_to_dst_shift(comb, OP_sar, opnum, src_bytenum % 4, |
| ALIGN_BACKWARD(src_bytenum, 4), 4, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psrldq: |
| case OP_vpsrldq: { |
| /* These shift by *bytes*, not *bits*! */ |
| reg_t shift; |
| if (get_cur_src_value(dr_get_current_drcontext(), comb->inst, |
| opc_is_shift_src0(opc) ? 0 : 1, &shift)) { |
| if (shift > opsz) |
| shift = opsz; |
| if (src_bytenum >= shift) |
| accum_shadow(&comb->dst[src_bytenum - shift], shadow); |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| } |
| case OP_psllw: |
| case OP_vpsllw: |
| if (map_src_to_dst_shift(comb, OP_shl, opnum, src_bytenum % 2, |
| ALIGN_BACKWARD(src_bytenum, 2), 2, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_pslld: |
| case OP_vpslld: |
| if (map_src_to_dst_shift(comb, OP_shl, opnum, src_bytenum % 4, |
| ALIGN_BACKWARD(src_bytenum, 4), 4, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_psllq: |
| case OP_vpsllq: |
| if (map_src_to_dst_shift(comb, OP_shl, opnum, src_bytenum % 8, |
| ALIGN_BACKWARD(src_bytenum, 8), 8, shadow)) { |
| SHADOW_COMBINE_CHECK_OPND(comb, src_bytenum); |
| return; /* handled */ |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_pslldq: |
| case OP_vpslldq: { |
| /* These shift by *bytes*, not *bits*! */ |
| reg_t shift; |
| if (get_cur_src_value(dr_get_current_drcontext(), comb->inst, |
| opc_is_shift_src0(opc) ? 0 : 1, &shift)) { |
| if (shift > opsz) |
| shift = opsz; |
| if (src_bytenum + shift < opsz) |
| accum_shadow(&comb->dst[src_bytenum + shift], shadow); |
| } else /* gracefully continue */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| } |
| case OP_vpsravd: |
| case OP_vpsrlvd: |
| case OP_vpsrlvq: |
| case OP_vpsllvd: |
| case OP_vpsllvq: |
| /* FIXME i#1484: these are complex, bailing for now */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| case OP_pextrb: |
| case OP_pextrw: |
| case OP_pextrd: |
| case OP_vpextrb: |
| case OP_vpextrw: |
| case OP_vpextrd: |
| case OP_extractps: |
| case OP_pinsrb: |
| case OP_pinsrw: |
| case OP_pinsrd: |
| case OP_insertps: { |
| ptr_uint_t immed = 0; |
| ASSERT(comb->inst != NULL, "need inst for OP_pextr*"); |
| /* zero-extended so we only write bottom */ |
| /* we get passed the entire xmm reg (reg_get_size, not opnd_get_size) */ |
| if (!get_cur_src_value(NULL, comb->inst, 1, &immed)) |
| ASSERT(false, "failed to get shift amount"); /* rel build: keep going */ |
| switch (opc) { |
| case OP_pextrb: |
| case OP_vpextrb: |
| if (src_bytenum == immed) |
| accum_shadow(&comb->dst[0], shadow); |
| break; |
| case OP_pextrw: |
| case OP_vpextrw: |
| if (src_bytenum >= immed*2 && src_bytenum < (immed+1)*2) |
| accum_shadow(&comb->dst[src_bytenum % 2], shadow); |
| break; |
| case OP_pextrd: |
| case OP_vpextrd: |
| case OP_extractps: |
| if (src_bytenum >= immed*4 && src_bytenum < (immed+1)*4) |
| accum_shadow(&comb->dst[src_bytenum % 4], shadow); |
| break; |
| case OP_pinsrb: |
| if (src_bytenum == 0) /* DRi#1388: we'll iterate >1 byte for reg */ |
| accum_shadow(&comb->dst[immed], shadow); |
| break; |
| case OP_vpinsrb: |
| if (src_bytenum == 0) /* DRi#1388: we'll iterate >1 byte for reg */ |
| accum_shadow(&comb->dst[immed], shadow); |
| break; |
| case OP_pinsrw: |
| if (src_bytenum < 2) /* DRi#1388: we'll iterate >2 bytes for reg */ |
| accum_shadow(&comb->dst[immed*2 + (src_bytenum % 2)], shadow); |
| break; |
| case OP_pinsrd: |
| accum_shadow(&comb->dst[immed*4 + (src_bytenum % 4)], shadow); |
| break; |
| case OP_insertps: { |
| uint count_s = opnd_is_reg(comb->opnd) ? (immed >> 6) : 0; |
| uint count_d = (immed >> 4) & 0x3; |
| uint zmask = immed & 0xf; |
| uint i; |
| if (src_bytenum >= count_s*4 && src_bytenum < (count_s+1)*4) |
| accum_shadow(&comb->dst[count_d*4 + (src_bytenum % 4)], shadow); |
| if (src_bytenum == 0) { /* arbitrary, just do it once */ |
| for (i = 0; i < 3; i++) { |
| if (TEST(0x1, zmask)) |
| accum_shadow(&comb->dst[i], SHADOW_DEFINED); |
| if (TEST(0x2, zmask)) |
| accum_shadow(&comb->dst[4+i], SHADOW_DEFINED); |
| if (TEST(0x4, zmask)) |
| accum_shadow(&comb->dst[8+i], SHADOW_DEFINED); |
| if (TEST(0x8, zmask)) |
| accum_shadow(&comb->dst[12+i], SHADOW_DEFINED); |
| } |
| } |
| } |
| } |
| break; |
| } |
| case OP_vpinsrb: |
| case OP_vpinsrw: |
| case OP_vpinsrd: { |
| ptr_uint_t immed = 0; |
| ASSERT(comb->inst != NULL, "need inst for OP_vpinsr*"); |
| /* we get passed the entire xmm reg (reg_get_size, not opnd_get_size) */ |
| if (!get_cur_src_value(NULL, comb->inst, 2, &immed)) |
| ASSERT(false, "failed to get shift amount"); /* rel build: keep going */ |
| switch (opc) { |
| case OP_vpinsrb: |
| if (opnum == 0) { |
| if (src_bytenum != immed) |
| accum_shadow(&comb->dst[src_bytenum], shadow); |
| } else if (src_bytenum == 0) /* DRi#1388: we'll iterate >1 byte */ |
| accum_shadow(&comb->dst[immed], shadow); |
| break; |
| case OP_vpinsrw: |
| if (opnum == 0) { |
| if (src_bytenum < immed*2 || src_bytenum >= (immed+1)*2) |
| accum_shadow(&comb->dst[src_bytenum], shadow); |
| } else if (src_bytenum < 2) /* DRi#1388: we'll iterate >2 bytes */ |
| accum_shadow(&comb->dst[immed*2 + (src_bytenum % 2)], shadow); |
| break; |
| case OP_vpinsrd: |
| if (opnum == 0) { |
| if (src_bytenum < immed*4 || src_bytenum >= (immed+1)*4) |
| accum_shadow(&comb->dst[src_bytenum], shadow); |
| } else |
| accum_shadow(&comb->dst[immed*4 + (src_bytenum % 4)], shadow); |
| break; |
| } |
| break; |
| } |
| case OP_movhps: |
| case OP_movhpd: |
| ASSERT(comb->inst != NULL, "need comb->inst for movhps"); |
| if (opnd_is_memory_reference(comb->opnd)) { |
| accum_shadow(&comb->dst[src_bytenum + (opsz - 8)], shadow); |
| } else if (opnd_is_reg(instr_get_dst(comb->inst, 0))) { |
| /* reg-reg is OP_movlhps */ |
| if (src_bytenum < 8) |
| accum_shadow(&comb->dst[src_bytenum + (opsz - 8)], shadow); |
| } else { |
| if (src_bytenum >= opsz - 8) /* high quadword */ |
| accum_shadow(&comb->dst[src_bytenum - (opsz - 8)], shadow); |
| } |
| break; |
| case OP_movlps: |
| ASSERT(comb->inst != NULL, "need comb->inst for movlps"); |
| if (opnd_is_reg(comb->opnd) && opnd_is_reg(instr_get_dst(comb->inst, 0))) { |
| /* reg-reg is OP_movhlps */ |
| if (src_bytenum >= opsz - 8) /* high quadword */ |
| accum_shadow(&comb->dst[src_bytenum - (opsz - 8)], shadow); |
| } else if (src_bytenum < 8) |
| accum_shadow(&comb->dst[src_bytenum], shadow); |
| break; |
| |
| /* XXX i#1484/i#243: add more xmm opcodes here. Also add to either |
| * set_check_definedness_pre_regs() (if check_definedness is |
| * enough) or instr_ok_for_instrument_fastpath() if fastpath |
| * can't handle them. |
| * |
| * See all the "FIXME i#1484" comments above as well. |
| * |
| * Opcodes that need extra handling: and + or operations with constants; |
| * widening/narrowing (OP_cvt*); conditional moves (OP_*blend*); |
| * shifting and selecting (OP_palignr, OP_phminposuw, OP_pcmpestr*). |
| * |
| * For now we mark these as defined to avoid false negatives. |
| * OP_por, OP_pand, and OP_pand are not yet listed here b/c it should |
| * be much rarer to and or or with a constant in xmm vs gpr and we'd |
| * rather not have false negatives on common operations. |
| */ |
| /* conversions that shrink */ |
| case OP_cvttpd2pi: |
| case OP_cvttsd2si: |
| case OP_cvtpd2pi: |
| case OP_cvtsd2si: |
| case OP_cvtpd2ps: |
| case OP_cvtsd2ss: |
| case OP_cvtdq2pd: |
| case OP_cvttpd2dq: |
| case OP_cvtpd2dq: |
| /* blend and other complex operations */ |
| case OP_pblendvb: |
| case OP_blendvps: |
| case OP_blendvpd: |
| case OP_blendps: |
| case OP_blendpd: |
| case OP_pblendw: |
| case OP_vpblendvb: |
| case OP_vblendvps: |
| case OP_vblendvpd: |
| case OP_vblendps: |
| case OP_vblendpd: |
| case OP_vpblendw: |
| case OP_vpblendd: |
| case OP_palignr: |
| case OP_phminposuw: |
| case OP_pcmpestrm: |
| case OP_pcmpestri: |
| /* FIXME i#1484: implement proper handling */ |
| accum_shadow(&comb->dst[src_bytenum], SHADOW_DEFINED); |
| break; |
| |
| /* cpuid: who cares if collapse to eax */ |
| /* rdtsc, rdmsr, rdpmc: no srcs, so can use bottom slot == defined */ |
| /* mul, imul, div, idiv: FIXME PR 408551: should split: for now we collapse */ |
| |
| default: |
| accum_shadow(&comb->dst[src_bytenum], shadow); |
| break; |
| } |
| |
| /* By default all source bytes influence eflags. If an opcode wants to do |
| * otherwise it needs to return prior to here. |
| */ |
| if (comb->inst != NULL && |
| TESTANY(EFLAGS_WRITE_6, instr_get_eflags(comb->inst, DR_QUERY_INCLUDE_ALL))) |
| accum_shadow(&comb->eflags, shadow); |
| return; |
| } |
| #endif /* TOOL_DR_MEMORY */ |
| |
| bool |
| instr_needs_all_srcs_and_vals(instr_t *inst) |
| { |
| /* For bitwise and + or we need to know the shadow value and the real value |
| * of all sources before we can decide whether any one source's |
| * undefinedness is an issue! |
| */ |
| int opc = instr_get_opcode(inst); |
| return (opc == OP_and || opc == OP_test || opc == OP_or); |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| /* i#489: handle the "mov C->B, xor A->B, and D->B, xor B->C" sequence |
| * used by optimizing compilers (cl /GL in particular) to set a sequence of |
| * bits within a word to a given non-constant value. |
| * We don't want to spend the time scanning for this pattern up front in |
| * the bb events b/c of overhead and extra complexity of passing data |
| * to the instrumentation code (or of an app2app xform). We leverage |
| * the fact that OP_and w/ undef srcs comes to slowpath (just like i#849 |
| * relies on it). It's painful to go backward though (we'd have to walk |
| * the bb table to find start of bb) so we just look for "and D->B, xor B->C" |
| * and mark B and C defined which is a reasonable compromise. |
| * |
| * i#878: handle the "mov C->B, xor A->B, and D->B, xor C->B" sequence, for |
| * which we look for "and D->B, xor C->B" |
| * |
| * i#1523: we also handle a double intertwined mov;xor;and;xor. |
| */ |
| |
| static void |
| xor_bitfield_mark_defined(opnd_t op, dr_mcontext_t *mc, opnd_t and_src, opnd_t and_dst) |
| { |
| if (opnd_is_reg(op)) { |
| register_shadow_mark_defined(opnd_get_reg(op), |
| opnd_size_in_bytes(opnd_get_size(op))); |
| } else { |
| ASSERT(opnd_is_memory_reference(op), "invalid xor dst"); |
| /* No need for adjust_memop: not a push or pop */ |
| /* Rule out OP_and's operands affecting base/index of xor (so we can |
| * rely on opnd_compute_address() below), or D==C. |
| */ |
| if (((opnd_is_memory_reference(op) && |
| opnd_is_memory_reference(and_src)) || |
| !opnd_share_reg(op, and_src)) && |
| ((opnd_is_memory_reference(op) && |
| opnd_is_memory_reference(and_dst)) || |
| !opnd_share_reg(op, and_dst))) { |
| shadow_set_non_matching_range(opnd_compute_address(op, mc), |
| opnd_size_in_bytes(opnd_get_size(op)), |
| SHADOW_DEFINED, SHADOW_UNADDRESSABLE); |
| } |
| } |
| } |
| |
| static bool |
| xor_bitfield_check_instr(void *drcontext, dr_mcontext_t *mc, instr_t *and, instr_t *xor, |
| shadow_combine_t *comb DR_PARAM_INOUT, size_t sz) |
| { |
| bool matches = false; |
| /* While someone could construct an L4 OP_and where src0==dst0 (or |
| * with 30 sources, for that matter), it won't encode, so we go ahead |
| * and assume it matches the canonical form. |
| */ |
| opnd_t and_src = instr_get_src(and, 0); |
| opnd_t and_dst = instr_get_dst(and, 0); |
| opnd_t xor_src = instr_get_src(xor, 0); |
| opnd_t xor_dst = instr_get_dst(xor, 0); |
| ASSERT(instr_get_opcode(xor) == OP_xor, "caller should check"); |
| if ((opnd_same(xor_src, and_dst) || opnd_same(xor_dst, and_dst)) && |
| /* Rule out: 1) nop; 2) xor where B and C are not completely separate */ |
| !opnd_share_reg(xor_dst, xor_src)) { |
| int i; |
| /* XXX: in debug build try to go backward and verify the prior mov,xor |
| * instrs to find out whether any other patterns match this tail end. |
| */ |
| LOG(4, "%s: matched @"PFX"\n", __FUNCTION__, instr_get_app_pc(and)); |
| matches = true; |
| STATS_INC(bitfield_xor_exception); |
| /* Caller already collapsed the 2nd src so we just set bottom indices */ |
| for (i = 0; i < sz; i++) { |
| if (comb->dst[i] == SHADOW_UNDEFINED) |
| comb->dst[i] = SHADOW_DEFINED; |
| } |
| /* Eflags will be marked defined since comb->dst is all defined */ |
| /* i#878: we mark both xor dst and xor src b/c this pattern match code |
| * is executed at the OP_and and marking dst defined doesn't help b/c |
| * the xor then executes and propagates the uninit bits from the src. |
| */ |
| xor_bitfield_mark_defined(xor_src, mc, and_src, and_dst); |
| xor_bitfield_mark_defined(xor_dst, mc, and_src, and_dst); |
| } |
| return matches; |
| } |
| |
| static bool |
| check_xor_bitfield(void *drcontext, dr_mcontext_t *mc, instr_t *inst, |
| shadow_combine_t *comb DR_PARAM_INOUT, size_t sz, app_pc next_pc) |
| { |
| bool matches = false; |
| instr_t xor; |
| byte *pc = next_pc; |
| ASSERT(instr_get_opcode(inst) == OP_and, "caller should check"); |
| if (options.strict_bitops) /* not worth separate option */ |
| return false; |
| instr_init(drcontext, &xor); |
| if (!safe_decode(drcontext, pc, &xor, &pc) || !instr_valid(&xor)) |
| goto check_xor_bitfield_done; |
| if (instr_get_opcode(&xor) != OP_xor) { |
| /* Try the next instr, to handle double intertwined patterns (i#1523) |
| * or just a random instr in between (i#1530, i#1542). |
| */ |
| instr_t *between = instr_clone(drcontext, &xor); |
| instr_reset(drcontext, &xor); |
| if (!safe_decode(drcontext, pc, &xor, &pc) || !instr_valid(&xor) || |
| /* Bail if the instr in between has any non-memory-alias overlap */ |
| /* i#1542: the instr in between could have shared reg |
| * mov al,[edi+0x20] |
| * xor al,[ebp+0x10] ss:002b:0018e110=00 |
| * and al,0x1 |
| * mov dword ptr [edi],0x103356c8 |
| * xor [edi+0x20],al |
| */ |
| instrs_share_opnd(between, &xor)) { |
| instr_destroy(drcontext, between); |
| goto check_xor_bitfield_done; |
| } |
| instr_destroy(drcontext, between); |
| } |
| if (instr_get_opcode(&xor) == OP_xor) { |
| matches = xor_bitfield_check_instr(drcontext, mc, inst, &xor, comb, sz); |
| if (!matches) { |
| /* Try the next instr, to handle double intertwined patterns (i#1523) */ |
| instr_reset(drcontext, &xor); |
| if (!safe_decode(drcontext, pc, &xor, &pc) || !instr_valid(&xor)) |
| goto check_xor_bitfield_done; |
| if (instr_get_opcode(&xor) == OP_xor) { |
| matches = xor_bitfield_check_instr(drcontext, mc, inst, &xor, comb, sz); |
| } |
| } |
| } |
| check_xor_bitfield_done: |
| instr_free(drcontext, &xor); |
| return matches; |
| } |
| |
| /* Caller has already checked that "val" is defined */ |
| static bool |
| check_andor_vals(int opc, reg_t val, uint i, bool bitmask_immed) |
| { |
| if (options.strict_bitops) { |
| bool def = ((opc != OP_or && DWORD2BYTE(val, i) == 0) || |
| (opc == OP_or && DWORD2BYTE(val, i) == ~0)); |
| DOSTATS({ |
| if (def) |
| STATS_INC(bitfield_const_exception); |
| }); |
| return def; |
| } else { |
| /* i#849: we relax typical bitfield operations: |
| * + OP_or with a defined value (not enough to just allow non-0 value) |
| * used to set a bitfield var to a non-const-zero value |
| * XXX: could put this on fastpath for perf. |
| * XXX i#489: another sequence used to set is a double xor |
| * sequence which will require pattern matching. |
| * + OP_and with a constant that has only one sequence of 0's and at |
| * least a few 1's, used to set a bitfield var to zero |
| */ |
| return (opc != OP_or && DWORD2BYTE(val, i) == 0) || |
| (opc == OP_and && bitmask_immed) || |
| opc == OP_or; |
| } |
| } |
| |
| static bool |
| check_and_not_test(void *drcontext, dr_mcontext_t *mc, instr_t *and, app_pc next_pc) |
| { |
| /* i#1520: we expand our bitfield heuristics to handle single-bit fields as |
| * well as multiple contiguous bitfield variables. These end up initialized |
| * using OP_and with constants like 0x80000000 or 0xf3009000 that don't |
| * meet the original i#849 requirements. Here, we allow any OP_and constant |
| * so long as there's no eflags-reading instr soon after, to rule out |
| * non-assignment uses like testing that are using OP_and instead of OP_test |
| * for some reason. |
| */ |
| bool matches = false; |
| instr_t inst; |
| uint count; |
| byte *pc = next_pc; |
| # define AND_NOT_TEST_INSTRS_TO_CHECK 3 |
| ASSERT(instr_get_opcode(and) == OP_and, "caller should check"); |
| if (options.strict_bitops) /* not worth separate option */ |
| return false; |
| instr_init(drcontext, &inst); |
| for (count = 0; count < AND_NOT_TEST_INSTRS_TO_CHECK; count++) { |
| if (!safe_decode(drcontext, pc, &inst, &pc) || |
| !instr_valid(&inst)) |
| break; |
| /* for case like: and %ecx 0x80; jecxz */ |
| if (instr_is_cbr(&inst)) |
| break; |
| if (TESTANY(EFLAGS_READ_6, instr_get_eflags(&inst, DR_QUERY_DEFAULT))) |
| break; |
| if (/* report match on aflags written by other following instr */ |
| TESTALL(EFLAGS_WRITE_6, instr_get_eflags(&inst, DR_QUERY_DEFAULT)) || |
| /* i#1576, i#1586: report match on non-cbr-cti */ |
| instr_is_cti(&inst)) { |
| count = AND_NOT_TEST_INSTRS_TO_CHECK; |
| break; |
| } |
| instr_reset(drcontext, &inst); |
| } |
| if (count == AND_NOT_TEST_INSTRS_TO_CHECK) { |
| matches = true; |
| LOG(4, "%s: no eflags-reading instrs after and-with-const @"PFX"\n", |
| __FUNCTION__, next_pc); |
| STATS_INC(bitfield_const_exception); |
| } |
| instr_free(drcontext, &inst); |
| return matches; |
| } |
| |
| |
| static bool |
| check_andor_bitmask_immed(int opc, size_t sz, reg_t immed, bool *byte_bounds DR_PARAM_OUT) |
| { |
| /* For i#849, we're looking for OP_and with a constant that sets a contiguous |
| * sequence of bits to 0 and leaves the rest alone: used to initialize |
| * a bitfield var to zero. |
| * We look for one set of 0's and more than 2 1's (to rule out testing |
| * just one or two bits w/ OP_and instead of OP_test). |
| */ |
| bool bitmask_immed = false; |
| bool byte_only = false; |
| if (!options.strict_bitops && opc == OP_and/*no OP_test*/) { |
| uint num_contig_1bits = 0; |
| /* Also look for byte-aligned and byte-length sequences */ |
| uint sequence_0 = 0, sequence_1 = 0; |
| reg_t curval = immed; |
| bool found_zero = false, last_zero = false, two_zeroes = false; |
| uint i; |
| byte_only = true; |
| for (i = 0; i < sz*8; i++) { |
| /* XXX perf: per-byte table lookup would be better though |
| * need logic to stitch bytes together |
| */ |
| if (TEST(1, curval)) { |
| num_contig_1bits++; |
| last_zero = false; |
| if (sequence_0 % 8 != 0) |
| byte_only = false; |
| sequence_0 = 0; |
| sequence_1++; |
| } else { |
| if (!last_zero && found_zero) { |
| /* two sets of zeros: but we can't break b/c of sequence* */ |
| two_zeroes = true; |
| } else { |
| found_zero = true; |
| last_zero = true; |
| } |
| if (sequence_1 % 8 != 0) |
| byte_only = false; |
| sequence_1 = 0; |
| sequence_0++; |
| } |
| curval = curval >> 1; |
| } |
| if (!two_zeroes && i == sz*8 && num_contig_1bits > 2) { |
| STATS_INC(bitfield_const_exception); |
| bitmask_immed = true; |
| } |
| } |
| *byte_bounds = byte_only; |
| return bitmask_immed; |
| } |
| |
| /* Returns whether the definedness values changed at all */ |
| bool |
| check_andor_sources(void *drcontext, dr_mcontext_t *mc, instr_t *inst, |
| shadow_combine_t *comb DR_PARAM_INOUT, app_pc next_pc) |
| { |
| /* The two sources have been laid out side-by-side in comb->dst. |
| * We need to combine, with special rules that suppress undefinedness |
| * based on 0 or 1 values. |
| */ |
| int opc = instr_get_opcode(inst); |
| reg_t val0, val1, immed = 0; |
| uint i, immed_opnum, nonimmed_opnum; |
| bool changed = false; |
| bool have_vals = (get_cur_src_value(drcontext, inst, 0, &val0) && |
| get_cur_src_value(drcontext, inst, 1, &val1)); |
| bool have_immed = false; |
| bool bitmask_immed = false; |
| size_t sz; |
| ASSERT(instr_needs_all_srcs_and_vals(inst) && |
| (opc == OP_and || opc == OP_test || opc == OP_or), "must be OP_{and,test,or}"); |
| if (opnd_is_immed_int(instr_get_src(inst, 0))) { |
| immed_opnum = 0; |
| nonimmed_opnum = 1; |
| have_immed = true; |
| immed = val0; |
| sz = opnd_size_in_bytes(opnd_get_size(instr_get_src(inst, 1))); |
| } else { |
| immed_opnum = 1; |
| nonimmed_opnum = 0; |
| sz = opnd_size_in_bytes(opnd_get_size(instr_get_src(inst, 0))); |
| if (opnd_is_immed_int(instr_get_src(inst, 1))) { |
| have_immed = true; |
| immed = val1; |
| } |
| ASSERT(opnd_is_immed_int(instr_get_src(inst, 1)) || |
| sz == opnd_size_in_bytes(opnd_get_size(instr_get_src(inst, 1))), |
| "check_andor_sources assumption error"); |
| } |
| ASSERT(instr_needs_all_srcs_and_vals(inst), "check_andor_sources called incorrectly"); |
| |
| /* Check for intl\strlen.asm case where it reads 4 bytes for efficiency, |
| * but only if aligned. while it does look at the extra bytes the string |
| * should terminate in the valid bytes. We assume that these constants are |
| * enough to avoid false positives and don't check surrounding instrs. |
| * hello!strlen [F:\SP\vctools\crt_bld\SELF_X86\crt\src\intel\strlen.asm]: |
| * 00405f91 a900010181 test eax,0x81010100 |
| * 00405f96 74e8 jz hello!strlen+0x30 (00405f80) |
| * cygwin1!strcpy: |
| * 610deb96 a980808080 test eax,0x80808080 |
| * 610deb9b 74e3 jz cygwin1!strcpy+0x20 (610deb80) |
| * cygwin1!strchr+0x37: |
| * 610ded17 f7c280808080 test edx,0x80808080 |
| * cygwin1!cygwin_internal+0xe7f: |
| * 6101a79f 81e280808080 and edx,0x80808080 |
| * 6101a7a5 74e9 jz cygwin1!cygwin_internal+0xe70 (6101a790) |
| * 6101a7a7 f7c280800000 test edx,0x8080 |
| * 6101a7ad 7506 jnz cygwin1!cygwin_internal+0xe95 (6101a7b5) |
| * crafty+0x246dd: |
| * 00424706 81e100010181 and ecx,0x81010100 |
| * 0042470c 751c jnz crafty+0x2472a (0042472a) |
| * 0042470e 2500010181 and eax,0x81010100 |
| * 00424713 74d3 jz crafty+0x246e8 (004246e8) |
| * (haven't yet needed these ones, haven't analyzed whether might) |
| * 00424715 2500010101 and eax,0x1010100 |
| * 0042471a 7508 jnz crafty+0x24724 (00424724) |
| * 0042471c 81e600000080 and esi,0x80000000 |
| * 00424722 75c4 jnz crafty+0x246e8 (004246e8) |
| * |
| * We can't do these checks solely on reported undefined instances b/c of the |
| * OP_and (and OP_test if -no_check_uninit_cmps) so we must mark defined. |
| * Of course this leaves us open to false negatives. |
| */ |
| if (opc != OP_or && sz == 4 && |
| opnd_is_reg(instr_get_src(inst, nonimmed_opnum)) && |
| (opnd_get_reg(instr_get_src(inst, nonimmed_opnum)) == DR_REG_XAX || |
| opnd_get_reg(instr_get_src(inst, nonimmed_opnum)) == DR_REG_XCX || |
| opnd_get_reg(instr_get_src(inst, nonimmed_opnum)) == DR_REG_XDX) && |
| opnd_is_immed_int(instr_get_src(inst, immed_opnum)) && |
| (opnd_get_immed_int(instr_get_src(inst, immed_opnum)) == 0x81010100 || |
| opnd_get_immed_int(instr_get_src(inst, immed_opnum)) == 0x80808080 || |
| opnd_get_immed_int(instr_get_src(inst, immed_opnum)) == 0x00008080)) { |
| LOG(3, "strlen/strcpy magic constant @"PFX"\n", instr_get_app_pc(inst)); |
| #ifdef STATISTICS |
| if (opnd_get_immed_int(instr_get_src(inst, immed_opnum)) == 0x81010100) |
| STATS_INC(strlen_exception); |
| else |
| STATS_INC(strcpy_exception); |
| #endif |
| for (i = 0; i < sz; i++) { |
| comb->dst[i] = SHADOW_DEFINED; |
| comb->dst[i+sz] = SHADOW_DEFINED; |
| } |
| return true; |
| } |
| |
| if (!options.strict_bitops && have_immed) { |
| bool byte_bounds; |
| bitmask_immed = check_andor_bitmask_immed(opc, sz, immed, &byte_bounds); |
| /* XXX i#1520c#8: we'd like to set bitmask_immed to false if |
| * byte_bounds is true, but that leads to false positives. |
| * So for now we ignore it and prefer to err on the false neg side |
| * until we add true per-bit tracking. |
| */ |
| if (!bitmask_immed && opc == OP_and) |
| bitmask_immed = check_and_not_test(drcontext, mc, inst, next_pc); |
| } |
| |
| for (i = 0; i < sz; i++) { |
| LOG(4, "%s: have_vals=%d i=%d def=%d %d val=%d %d\n", |
| __FUNCTION__, have_vals, i, comb->dst[i], comb->dst[i+sz], |
| DWORD2BYTE(val1, i), DWORD2BYTE(val0, i)); |
| if (comb->dst[i] == SHADOW_UNDEFINED) { |
| if (have_vals && comb->dst[i+sz] == SHADOW_DEFINED && |
| check_andor_vals(opc, val1, i, bitmask_immed)) { |
| /* The 0/1 byte makes the source undefinedness not matter */ |
| comb->dst[i] = SHADOW_DEFINED; |
| changed = true; |
| STATS_INC(andor_exception); |
| LOG(3, "0/1 byte %d suppresses undefined and/or source\n", i); |
| } |
| } else { |
| ASSERT(comb->dst[i] == SHADOW_DEFINED, "shadow val inconsistency"); |
| if (comb->dst[i+sz] == SHADOW_UNDEFINED) { |
| if (have_vals && check_andor_vals(opc, val0, i, bitmask_immed)) { |
| /* The 0/1 byte makes the source undefinedness not matter */ |
| STATS_INC(andor_exception); |
| LOG(3, "0/1 byte %d suppresses undefined and/or source\n", i); |
| changed = true; |
| } else { |
| /* We probably don't need this as map_src_to_dst() should |
| * already have propagated i+sz to eflags? |
| */ |
| changed = true; |
| comb->dst[i] = SHADOW_UNDEFINED; |
| } |
| } else |
| ASSERT(comb->dst[i+sz] == SHADOW_DEFINED, "shadow val inconsistency"); |
| } |
| /* Throw out the 2nd source vals now that we've integrated */ |
| comb->dst[i+sz] = SHADOW_DEFINED; |
| } |
| |
| if (opc == OP_and && check_xor_bitfield(drcontext, mc, inst, comb, sz, next_pc)) |
| changed = true; |
| |
| return changed; |
| } |
| |
| /* Returns whether to skip the general integration */ |
| bool |
| integrate_register_shadow_arch(shadow_combine_t *comb DR_PARAM_INOUT, int opnum, |
| reg_id_t reg, uint shadow, bool pushpop) |
| { |
| uint opc = comb->opcode; |
| /* PR 426162: ignore stack register source -- see comment in slowpath.c */ |
| if ((opc == OP_leave || opc == OP_enter) && reg_overlap(reg, DR_REG_XBP)) |
| return true; |
| return false; |
| } |
| |
| /* Returns whether to skip the general assignment code */ |
| bool |
| assign_register_shadow_arch(shadow_combine_t *comb DR_PARAM_INOUT, int opnum, opnd_t opnd, |
| reg_id_t reg, bool pushpop, uint *shift DR_PARAM_INOUT) |
| { |
| int opc = comb->opcode; |
| |
| /* Here we need to de-mux from the side-by-side dests in the array |
| * into individual register dests. |
| * We ignore some dests: |
| * - push/pop esp (or enter/leave ebp): we checked esp (ebp) source |
| * - string ops ecx/esi/edi: we checked ecx/esi/edi source |
| * Some instrs w/ GPR dests we'll naturally mark as defined: |
| * - les, lds, lss, lfs, lgs: seg part should be checked up front, |
| * for simplicity we check the whole thing |
| * We also have to shift dsts that do NOT simply go into the lowest slot. |
| */ |
| if (opc_is_stringop(opc)) { |
| if (reg_overlap(reg, DR_REG_XDI) || reg_overlap(reg, DR_REG_XSI) || |
| reg_overlap(reg, DR_REG_XCX)) |
| return true; |
| } else if (/* pushpop + xsp handled in shared code */ |
| (opc == OP_leave || opc == OP_enter) && reg_overlap(reg, DR_REG_XBP)) { |
| return true; |
| } else { |
| /* We need special handling for multi-dest opcodes */ |
| switch (opc) { |
| case OP_popa: |
| *shift = (reg_to_pointer_sized(reg) - DR_REG_XAX); |
| break; |
| case OP_xchg: |
| case OP_xadd: |
| *shift = opnum; |
| break; |
| case OP_cmpxchg8b: |
| /* opnds: cmpxchg8b mem8 %eax %edx %ecx %ebx -> mem8 %eax %edx |
| * operation: if (edx:eax == mem8) mem8 = ecx:ebx; else edx:eax = mem8 |
| * we just combine all 3 sources and write the result to both dests. |
| */ |
| switch (opnum) { |
| case 0: *shift = 0; break; |
| case 1: *shift = 0; break; |
| case 2: *shift = 1; break; |
| default: ASSERT(false, "invalid opnum"); |
| } |
| break; |
| } |
| } |
| |
| if (comb->inst != NULL && proc_avx_enabled() && instr_zeroes_ymmh(comb->inst)) { |
| if (opnd_is_reg(instr_get_dst(comb->inst, 0))) { |
| reg_id_t reg = opnd_get_reg(instr_get_dst(comb->inst, 0)); |
| if (reg_is_xmm(reg) && !reg_is_ymm(reg)) { |
| /* If instr doesn't zero (i.e., not VEX_encoded), and DR |
| * presents its dst as just xmm, we'll naturally leave the |
| * ymmh shadow as-is. But if it zeroes we need to explicitly |
| * define the ymmh shadow. |
| */ |
| int i; |
| reg = reg - DR_REG_XMM0 + DR_REG_YMM0; |
| for (i = 0; i < 16; i++) |
| register_shadow_set_byte(reg, 16 + i, SHADOW_DEFINED); |
| } |
| } |
| } |
| # ifdef X64 |
| if (opnd_get_size(opnd) == OPSZ_4 && reg_is_gpr(reg)) { |
| /* Writing to the 32-bit reg clears the top 32 bits. */ |
| register_shadow_set_high_dword(reg, SHADOW_DWORD_DEFINED); |
| } |
| # endif |
| return false; |
| } |
| #endif /* TOOL_DR_MEMORY */ |
| |
| /* PR 530902: cmovcc should ignore src+dst unless eflags matches. For both |
| * cmovcc and fcmovcc we treat an unmatched case as though the source and |
| * dest do not exist: certainly source should not propagate to dest, whether |
| * we should check for addressability of source is debatable: not doing it |
| * for now. We do have to check whether eflags is defined though. |
| * |
| * FIXME: what about cmpxchg* where the register (or ecx:ebx for 8b) |
| * is not read if the cmp fails? For now we go ahead and propagate |
| * but perhaps we shouldn't be: we'll wait for a false positive to |
| * take any action though, seems pretty unlikely, unlike cmovcc. |
| */ |
| /* XXX i#1649: handle other predication: OP_bsf, OP_bsr, OP_*maskmov*, etc. */ |
| int |
| num_true_srcs(instr_t *inst, dr_mcontext_t *mc /*optional*/) |
| { |
| int opc = instr_get_opcode(inst); |
| if (opc_is_cmovcc(opc) || opc_is_fcmovcc(opc)) { |
| /* PR 530902: cmovcc should ignore src+dst unless eflags matches */ |
| if (mc != NULL && !instr_cmovcc_triggered(inst, mc->xflags)) |
| return 0; |
| else { |
| /* i#1456: ignore the dst-as-src added by DR, since we model the |
| * conditional nature ourselves, and don't want a false pos on an uninit |
| * dst. |
| */ |
| return 1; |
| } |
| } |
| /* sbb with self should consider all srcs except eflags defined (thus can't |
| * be in result_is_always_defined) (PR 425498, PR 425622) |
| */ |
| if (opc == OP_sbb && opnd_same(instr_get_src(inst, 0), instr_get_src(inst, 1))) |
| return 0; |
| if (opc == OP_nop_modrm) /* i#1870 */ |
| return 0; |
| return instr_num_srcs(inst); |
| } |
| |
| int |
| num_true_dsts(instr_t *inst, dr_mcontext_t *mc /*optional*/) |
| { |
| int opc = instr_get_opcode(inst); |
| if (opc_is_cmovcc(opc) || opc_is_fcmovcc(opc)) { |
| /* PR 530902: cmovcc should ignore src+dst unless eflags matches */ |
| if (mc != NULL && !instr_cmovcc_triggered(inst, mc->xflags)) |
| return 0; |
| } |
| if (opc == OP_nop_modrm) /* i#1870 */ |
| return 0; |
| return instr_num_dsts(inst); |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| /* It turns out that it's not the clean call that's the bottleneck: it's |
| * the decode and all the IR processing in the slow path proper. So for a |
| * common slowpath case, 4-byte OP_movs, we have an easy-to-write |
| * "medium-speed-path". Because it's mem2mem, we do not have fastpath |
| * support for it yet when check_definedness fails, and maybe we never will |
| * need it since this medium-path works fairly well. |
| */ |
| void |
| medium_path_movs4(app_loc_t *loc, dr_mcontext_t *mc) |
| { |
| /* since esi and edi are used as base regs, we are checking |
| * definedness, so we ignore the reg operands |
| */ |
| int i; |
| shadow_combine_t comb; |
| umbra_shadow_memory_info_t info; |
| umbra_shadow_memory_info_init(&info); |
| LOG(3, "medium_path movs4 "PFX" src="PFX" %d%d%d%d dst="PFX" %d%d%d%d\n", |
| loc_to_pc(loc), mc->xsi, |
| shadow_get_byte(&info, (app_pc)mc->xsi), |
| shadow_get_byte(&info, (app_pc)mc->xsi+1), |
| shadow_get_byte(&info, (app_pc)mc->xsi+2), |
| shadow_get_byte(&info, (app_pc)mc->xsi+3), |
| mc->xdi, |
| shadow_get_byte(&info, (app_pc)mc->xdi), |
| shadow_get_byte(&info, (app_pc)mc->xdi+1), |
| shadow_get_byte(&info, (app_pc)mc->xdi+2), |
| shadow_get_byte(&info, (app_pc)mc->xdi+3)); |
| #ifdef STATISTICS |
| if (!ALIGNED(mc->xsi, 4)) |
| STATS_INC(movs4_src_unaligned); |
| if (!ALIGNED(mc->xdi, 4)) |
| STATS_INC(movs4_dst_unaligned); |
| if (shadow_get_byte(&info, (app_pc)mc->xsi) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+1) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+2) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+3) != SHADOW_DEFINED) |
| STATS_INC(movs4_src_undef); |
| #endif |
| STATS_INC(medpath_executions); |
| |
| if (!options.check_uninitialized) { |
| if ((!options.check_alignment || |
| (ALIGNED(mc->xsi, 4) && ALIGNED(mc->xdi, 4))) && |
| shadow_get_byte(&info, (app_pc)mc->xsi) != SHADOW_UNADDRESSABLE && |
| shadow_get_byte(&info, (app_pc)mc->xdi) != SHADOW_UNADDRESSABLE) { |
| STATS_INC(movs4_med_fast); |
| return; |
| } |
| /* no need to pass shadow_combine_t for MEMREF_CHECK_ADDRESSABLE */ |
| check_mem_opnd(OP_movs, MEMREF_CHECK_ADDRESSABLE, loc, |
| opnd_create_far_base_disp(SEG_DS, DR_REG_XSI, |
| REG_NULL, 0, 0, OPSZ_PTR), |
| 4, mc, 0, NULL); |
| check_mem_opnd(OP_movs, MEMREF_CHECK_ADDRESSABLE, loc, |
| opnd_create_far_base_disp(SEG_ES, DR_REG_XDI |
| , REG_NULL, 0, 0, OPSZ_PTR), |
| 4, mc, 0, NULL); |
| return; |
| } |
| |
| /* The generalized routines below are just a little too slow. The |
| * common case is an unaligned movs4 whose source is fully |
| * defined, or aligned or unaligned but with source undefined, and |
| * with eflags defined, so we have a fastpath here. This has been |
| * good enough so no need for a real fastpath in gencode. |
| * i#i#237. |
| */ |
| /* XXX: assuming SEG_DS and SEG_ES are flat+full */ |
| if (is_shadow_register_defined(get_shadow_register(DR_REG_XSI)) && |
| is_shadow_register_defined(get_shadow_register(DR_REG_XDI)) && |
| get_shadow_eflags() == SHADOW_DEFINED) { |
| uint src0 = shadow_get_byte(&info, (app_pc)mc->xsi+0); |
| uint src1 = shadow_get_byte(&info, (app_pc)mc->xsi+1); |
| uint src2 = shadow_get_byte(&info, (app_pc)mc->xsi+2); |
| uint src3 = shadow_get_byte(&info, (app_pc)mc->xsi+3); |
| if ((src0 == SHADOW_DEFINED || src0 == SHADOW_UNDEFINED) && |
| (src1 == SHADOW_DEFINED || src1 == SHADOW_UNDEFINED) && |
| (src2 == SHADOW_DEFINED || src2 == SHADOW_UNDEFINED) && |
| (src3 == SHADOW_DEFINED || src3 == SHADOW_UNDEFINED) && |
| shadow_get_byte(&info, (app_pc)mc->xdi+0) != SHADOW_UNADDRESSABLE && |
| shadow_get_byte(&info, (app_pc)mc->xdi+1) != SHADOW_UNADDRESSABLE && |
| shadow_get_byte(&info, (app_pc)mc->xdi+2) != SHADOW_UNADDRESSABLE && |
| shadow_get_byte(&info, (app_pc)mc->xdi+3) != SHADOW_UNADDRESSABLE) { |
| shadow_set_byte(&info, (app_pc)mc->xdi+0, src0); |
| shadow_set_byte(&info, (app_pc)mc->xdi+1, src1); |
| shadow_set_byte(&info, (app_pc)mc->xdi+2, src2); |
| shadow_set_byte(&info, (app_pc)mc->xdi+3, src3); |
| STATS_INC(movs4_med_fast); |
| return; |
| } |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_movs, 4); |
| check_mem_opnd(OP_movs, MEMREF_USE_VALUES, loc, |
| opnd_create_far_base_disp(SEG_DS, DR_REG_XSI, |
| REG_NULL, 0, 0, OPSZ_4), |
| 4, mc, 0, &comb); |
| for (i = 0; i < 4; i++) |
| accum_shadow(&comb.dst[i], get_shadow_eflags()); |
| check_mem_opnd(OP_movs, MEMREF_WRITE | MEMREF_USE_VALUES, loc, |
| opnd_create_far_base_disp(SEG_ES, DR_REG_XDI, |
| REG_NULL, 0, 0, OPSZ_4), |
| 4, mc, 0, &comb); |
| } |
| |
| /* See comments for medium_path_movs4(). For cmps1, because it has 2 memory |
| * refs, it only stays on the fastpath if both dwords are fully defined: |
| * which fails at the end of strings and other locations. |
| */ |
| void |
| medium_path_cmps1(app_loc_t *loc, dr_mcontext_t *mc) |
| { |
| /* since esi and edi are used as base regs, we are checking |
| * definedness, so we ignore the reg operands |
| */ |
| uint flags; |
| shadow_combine_t comb; |
| umbra_shadow_memory_info_t info; |
| umbra_shadow_memory_info_init(&info); |
| LOG(3, "medium_path cmps1 "PFX" src1="PFX" %d%d%d%d src2="PFX" %d%d%d%d\n", |
| loc_to_pc(loc), mc->xsi, |
| shadow_get_byte(&info, (app_pc)mc->xsi), |
| shadow_get_byte(&info, (app_pc)mc->xsi+1), |
| shadow_get_byte(&info, (app_pc)mc->xsi+2), |
| shadow_get_byte(&info, (app_pc)mc->xsi+3), |
| mc->xdi, |
| shadow_get_byte(&info, (app_pc)mc->xdi), |
| shadow_get_byte(&info, (app_pc)mc->xdi+1), |
| shadow_get_byte(&info, (app_pc)mc->xdi+2), |
| shadow_get_byte(&info, (app_pc)mc->xdi+3)); |
| #ifdef STATISTICS |
| if (shadow_get_byte(&info, (app_pc)mc->xsi) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+1) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+2) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xsi+3) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xdi) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xdi+1) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xdi+2) != SHADOW_DEFINED || |
| shadow_get_byte(&info, (app_pc)mc->xdi+3) != SHADOW_DEFINED) |
| STATS_INC(cmps1_src_undef); |
| #endif |
| STATS_INC(medpath_executions); |
| |
| if (!options.check_uninitialized) { |
| if (shadow_get_byte(&info, (app_pc)mc->xsi) != SHADOW_UNADDRESSABLE && |
| shadow_get_byte(&info, (app_pc)mc->xdi) != SHADOW_UNADDRESSABLE) { |
| STATS_INC(cmps1_med_fast); |
| return; |
| } |
| /* no need to initialize shadow_vals for MEMREF_CHECK_ADDRESSABLE */ |
| check_mem_opnd(OP_cmps, MEMREF_CHECK_ADDRESSABLE, loc, |
| opnd_create_far_base_disp(SEG_DS, DR_REG_XSI, |
| REG_NULL, 0, 0, OPSZ_1), |
| 1, mc, 0, NULL); |
| check_mem_opnd(OP_cmps, MEMREF_CHECK_ADDRESSABLE, loc, |
| opnd_create_far_base_disp(SEG_ES, DR_REG_XDI, |
| REG_NULL, 0, 0, OPSZ_1), |
| 1, mc, 0, NULL); |
| return; |
| } |
| |
| /* The generalized routines are just a little too slow. |
| * Xref i#i#237 for movs4. |
| */ |
| /* XXX: assuming SEG_DS and SEG_ES are flat+full */ |
| if (is_shadow_register_defined(get_shadow_register(DR_REG_XSI)) && |
| is_shadow_register_defined(get_shadow_register(DR_REG_XDI)) && |
| get_shadow_eflags() == SHADOW_DEFINED) { |
| uint src0 = shadow_get_byte(&info, (app_pc)mc->xsi); |
| uint src1 = shadow_get_byte(&info, (app_pc)mc->xdi); |
| if ((src0 == SHADOW_DEFINED || |
| (!options.check_uninit_cmps && src0 == SHADOW_UNDEFINED)) && |
| (src1 == SHADOW_DEFINED || |
| (!options.check_uninit_cmps && src1 == SHADOW_UNDEFINED))) { |
| set_shadow_eflags(combine_shadows(src0, src1)); |
| STATS_INC(cmps1_med_fast); |
| return; |
| } |
| } |
| |
| flags = MEMREF_USE_VALUES; |
| if (options.check_uninit_cmps) |
| flags |= MEMREF_CHECK_DEFINEDNESS; |
| if (options.check_uninit_blocklist[0] != '\0') { |
| /* i#1529: mark an entire module defined */ |
| /* XXX: this is the wrong pc if decode_pc != pc. For now we live with it. */ |
| if (module_is_on_check_uninit_blocklist(loc_to_pc(loc))) |
| flags = 0; /* w/o MEMREF_USE_VALUES, handle_mem_ref() uses SHADOW_DEFINED */ |
| } |
| shadow_combine_init(&comb, NULL, OP_cmps, 1); |
| check_mem_opnd(OP_cmps, flags, loc, |
| opnd_create_far_base_disp(SEG_DS, DR_REG_XSI, |
| REG_NULL, 0, 0, OPSZ_1), |
| 1, mc, 0, &comb); |
| check_mem_opnd(OP_cmps, flags, loc, |
| opnd_create_far_base_disp(SEG_ES, DR_REG_XDI, |
| REG_NULL, 0, 0, OPSZ_1), |
| 1, mc, 1, &comb); |
| /* b/c we set inst to NULL, map_src_to_dst won't do this for us */ |
| accum_shadow(&comb.dst[0], get_shadow_eflags()); |
| set_shadow_eflags(comb.dst[0]); |
| } |
| |
| /* Returns whether it handled the instruction */ |
| bool |
| medium_path_arch(app_pc decode_pc, app_loc_t *loc, dr_mcontext_t *mc) |
| { |
| if (*decode_pc == MOVS_4_OPCODE || |
| /* we now pass original pc from -repstr_to_loop including rep. |
| * ignore other prefixes here: data16 most likely and then not movs4. |
| */ |
| (options.repstr_to_loop && *decode_pc == REP_PREFIX && |
| *(decode_pc + 1) == MOVS_4_OPCODE)) { |
| /* see comments for this routine: common enough it's worth optimizing */ |
| medium_path_movs4(loc, mc); |
| /* no sharing with string instrs so no need to call |
| * slow_path_xl8_sharing |
| */ |
| return true; |
| } else if (*decode_pc == CMPS_1_OPCODE || |
| (options.repstr_to_loop && |
| (*decode_pc == REP_PREFIX || *decode_pc == REPNE_PREFIX) && |
| *(decode_pc + 1) == CMPS_1_OPCODE)) { |
| medium_path_cmps1(loc, mc); |
| return true; |
| } |
| return false; |
| } |
| #endif /* TOOL_DR_MEMORY */ |
| |
| void |
| slowpath_update_app_loc_arch(uint opc, app_pc decode_pc, app_loc_t *loc) |
| { |
| if (options.repstr_to_loop && opc == OP_loop) { |
| /* to point at an OP_loop but use app's repstr pc we use this table (i#391) */ |
| byte *rep_pc; |
| dr_mutex_lock(stringop_lock); |
| rep_pc = (byte *) hashtable_lookup(&stringop_us2app_table, decode_pc); |
| dr_mutex_unlock(stringop_lock); |
| if (rep_pc != NULL) { |
| ASSERT(dr_memory_is_dr_internal(decode_pc), "must be drmem heap"); |
| /* use this as app pc if we report an error */ |
| pc_to_loc(loc, rep_pc); |
| } |
| } |
| } |
| |
| static bool |
| stringop_equal(uint val1, uint val2, uint opsz) |
| { |
| if (opsz == 1) |
| return ((val1 & 0xff) == (val2 & 0xff)); |
| else if (opsz == 2) |
| return ((val1 & 0xffff) == (val2 & 0xffff)); |
| else if (opsz == 4) |
| return (val1 == val2); |
| else |
| ASSERT(false, "bad stringop opsz internal error"); |
| return false; |
| } |
| |
| static void |
| get_stringop_range(reg_t base, reg_t count, reg_t eflags, uint opsz, |
| app_pc *start/*OUT*/, app_pc *end/*OUT*/) |
| { |
| /* ecx is a pre-iter check; zf is a post-iter check */ |
| if (TEST(EFLAGS_DF, eflags)) { |
| /* decrement each iter */ |
| *start = (app_pc) (base - (count-1)*opsz); |
| *end = (app_pc) (base + opsz); |
| } else { |
| *start = (app_pc) (base); |
| *end = (app_pc) (base + count*opsz); |
| } |
| } |
| |
| bool |
| check_mem_opnd_arch(uint opc, uint flags, app_loc_t *loc, opnd_t opnd, uint sz, |
| dr_mcontext_t *mc, int opnum, shadow_combine_t *comb DR_PARAM_INOUT) |
| { |
| app_pc addr = NULL, end; |
| if (opc_is_stringop_loop(opc) && |
| /* with -repstr_to_loop, a decoded repstr is really a non-rep str */ |
| !options.repstr_to_loop) { |
| /* We assume flat segments for es and ds */ |
| /* FIXME: support addr16! we're assuming 32-bit edi, esi! */ |
| ASSERT(reg_get_size(opnd_get_base(opnd)) == OPSZ_4, |
| "no support yet for addr16 string operations!"); |
| if (opc == OP_rep_stos || opc == OP_rep_lods) { |
| /* store from al/ax/eax into es:edi; load from es:esi into al/ax/eax */ |
| get_stringop_range(opc == OP_rep_stos ? mc->xdi : mc->xsi, |
| mc->xcx, mc->xflags, sz, &addr, &end); |
| LOG(3, "rep %s "PFX"-"PFX"\n", opc == OP_rep_stos ? "stos" : "lods", |
| addr, end); |
| flags |= (sz == 1 ? MEMREF_SINGLE_BYTE : |
| (sz == 2 ? MEMREF_SINGLE_WORD : MEMREF_SINGLE_DWORD)); |
| sz = end - addr; |
| } else if (opc == OP_rep_movs) { |
| /* move from ds:esi to es:edi */ |
| LOG(3, "rep movs "PFX" "PFX" "PIFX"\n", mc->xdi, mc->xsi, mc->xcx); |
| /* FIXME: if checking definedness of sources, really |
| * should do read+write in lockstep, since an earlier |
| * write could make a later read ok; for now we punt on |
| * that. We do an overlap check and warn below. |
| * If we're propagating and not checking sources, then the |
| * overlap is fine: we'll go through the source, ensure addressable |
| * but do nothing if undefined, and then go through dest copying |
| * from source in lockstep. |
| */ |
| get_stringop_range(mc->xsi, mc->xcx, mc->xflags, sz, &addr, &end); |
| if (!TEST(MEMREF_WRITE, flags)) { |
| flags &= ~MEMREF_USE_VALUES; |
| } else { |
| ASSERT(comb != NULL, "assuming have shadow if marked write"); |
| flags |= MEMREF_MOVS | MEMREF_USE_VALUES; |
| comb->movs_addr = addr; |
| get_stringop_range(mc->xdi, mc->xcx, mc->xflags, sz, &addr, &end); |
| if (TEST(MEMREF_CHECK_DEFINEDNESS, flags) && |
| end > comb->movs_addr && |
| addr < comb->movs_addr + (end - addr)) |
| ELOG(0, "WARNING: rep movs overlap while checking definedness not fully supported!\n"); |
| } |
| sz = end - addr; |
| } else if (opc == OP_rep_scas || opc == OP_repne_scas) { |
| /* compare es:edi to al/ax/eax */ |
| /* we can't just do post-instr check since we want to warn of |
| * unaddressable refs prior to their occurrence, so we emulate |
| * FIXME: we aren't aggregating errors in adjacent bytes */ |
| LOG(3, "rep scas @"PFX" "PFX" "PIFX"\n", loc_to_print(loc), mc->xdi, mc->xcx); |
| while (mc->xcx != 0) { /* note the != instead of > */ |
| uint val; |
| bool eq; |
| handle_mem_ref(flags, loc, (app_pc)mc->xdi, sz, mc); |
| /* remember that our notion of unaddressable is not real so we have |
| * to check with the OS to see if this will fault |
| */ |
| ASSERT(sz <= sizeof(uint), "internal error"); |
| if (safe_read((void *)mc->xdi, sz, &val)) { |
| /* Assume the real instr will fault here. |
| * FIXME: if the instr gets resumed our check won't re-execute! */ |
| break; |
| } |
| eq = stringop_equal(val, mc->xax, sz); |
| mc->xdi += (TEST(EFLAGS_DF, mc->xflags) ? -1 : 1) * sz; |
| mc->xcx--; |
| if ((opc == OP_rep_scas && !eq) || |
| (opc == OP_repne_scas && eq)) |
| break; |
| } |
| return true; |
| } else if (opc == OP_rep_cmps || opc == OP_repne_cmps) { |
| /* compare ds:esi to es:edi */ |
| /* FIXME: we aren't aggregating errors in adjacent bytes */ |
| if (reg_overlap(opnd_get_base(opnd), DR_REG_XDI)) |
| return true; /* we check both when passed esi base */ |
| LOG(3, "rep cmps @"PFX" "PFX" "PFX" "PIFX"\n", |
| loc_to_print(loc), mc->xdi, mc->xsi, mc->xcx); |
| while (mc->xcx != 0) { /* note the != instead of > */ |
| uint val1, val2; |
| bool eq; |
| handle_mem_ref(flags, loc, (app_pc)mc->xsi, sz, mc); |
| handle_mem_ref(flags, loc, (app_pc)mc->xdi, sz, mc); |
| /* remember that our notion of unaddressable is not real so we have |
| * to check with the OS to see if this will fault |
| */ |
| ASSERT(sz <= sizeof(uint), "internal error"); |
| if (!safe_read((void *)mc->xsi, sz, &val1) || |
| !safe_read((void *)mc->xdi, sz, &val2)) { |
| /* Assume the real instr will fault here. |
| * FIXME: if the instr gets resumed our check won't re-execute! */ |
| break; |
| } |
| eq = stringop_equal(val1, val2, sz); |
| mc->xdi += (TEST(EFLAGS_DF, mc->xflags) ? -1 : 1) * sz; |
| mc->xsi += (TEST(EFLAGS_DF, mc->xflags) ? -1 : 1) * sz; |
| mc->xcx--; |
| if ((opc == OP_rep_cmps && !eq) || |
| (opc == OP_repne_cmps && eq)) |
| break; |
| } |
| return true; |
| } else |
| ASSERT(false, "unknown string operation"); |
| } |
| return false; |
| } |
| |
| #ifdef TOOL_DR_MEMORY |
| /*************************************************************************** |
| * Unit tests |
| */ |
| |
| #ifdef BUILD_UNIT_TESTS |
| void |
| test_punpck(void) |
| { |
| shadow_combine_t comb; |
| int i; |
| uint sz = opnd_size_in_bytes(reg_get_size(DR_REG_XMM0)); |
| comb.opsz = sz; |
| |
| shadow_combine_init(&comb, NULL, OP_punpcklbw, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i < sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT((i % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| (i % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpcklwd, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i < sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 2) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 2) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpckldq, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i < sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 4) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 4) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpcklqdq, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i < sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 8) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 8) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpckhbw, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i >= sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT((i % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| (i % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpckhwd, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i >= sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 2) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 2) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpckhdq, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i >= sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 4) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 4) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| |
| shadow_combine_init(&comb, NULL, OP_punpckhqdq, OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 1, i, i >= sz/2 ? SHADOW_DEFINED : SHADOW_UNDEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i / 8) % 2 == 0 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i / 8) % 2 == 1 && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| } |
| |
| void |
| test_pinsr(void *dc) |
| { |
| shadow_combine_t comb; |
| int i; |
| uint sz = opnd_size_in_bytes(reg_get_size(DR_REG_XMM0)); |
| instr_t *inst; |
| comb.opsz = sz; |
| |
| /* XXX: it's hard to test pinsr* as they need real shadow vals */ |
| |
| inst = INSTR_CREATE_vpinsrw(dc, opnd_create_reg(DR_REG_XMM0), |
| opnd_create_reg(DR_REG_XMM1), |
| opnd_create_reg(DR_REG_EAX), |
| OPND_CREATE_INT8(4)); |
| shadow_combine_init(&comb, inst, instr_get_opcode(inst), OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < 4; i++) |
| map_src_to_dst(&comb, 1, i, SHADOW_DEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT(((i == 8 || i == 9) && comb.dst[i] == SHADOW_DEFINED) || |
| ((i < 8 || i >= 10) && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| instr_destroy(dc, inst); |
| |
| inst = INSTR_CREATE_vpinsrd(dc, opnd_create_reg(DR_REG_XMM0), |
| opnd_create_reg(DR_REG_XMM1), |
| opnd_create_reg(DR_REG_EAX), |
| OPND_CREATE_INT8(2)); |
| shadow_combine_init(&comb, inst, instr_get_opcode(inst), OPND_SHADOW_ARRAY_LEN); |
| for (i = 0; i < sz; i++) |
| map_src_to_dst(&comb, 0, i, SHADOW_UNDEFINED); |
| for (i = 0; i < 4; i++) |
| map_src_to_dst(&comb, 1, i, SHADOW_DEFINED); |
| for (i = 0; i < sz; i++) { |
| EXPECT((i >= 8 && i < 12 && comb.dst[i] == SHADOW_DEFINED) || |
| ((i < 8 || i >= 12) && comb.dst[i] == SHADOW_UNDEFINED)); |
| } |
| instr_destroy(dc, inst); |
| } |
| |
| void |
| slowpath_unit_tests_arch(void *drcontext) |
| { |
| test_punpck(); |
| |
| test_pinsr(drcontext); |
| |
| /* add more tests here */ |
| } |
| #endif |
| |
| #endif /* TOOL_DR_MEMORY */ |