blob: cdcf02399ea41b8e0b09ef2f28c48b68ff81615e [file] [edit]
/* **********************************************************
* 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.c: Dr. Memory memory read/write slowpath handling
*/
#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
/* per-opcode counts */
uint64 slowpath_count[OP_LAST+1];
/* per-opsz counts */
uint64 slowpath_sz1;
uint64 slowpath_sz2;
uint64 slowpath_sz4;
uint64 slowpath_sz8;
uint64 slowpath_sz10;
uint64 slowpath_sz16;
uint64 slowpath_szOther;
/* PR 423757: periodic stats dump */
uint next_stats_dump;
uint num_faults;
uint num_slowpath_faults;
#endif
#ifdef STATISTICS
uint slowpath_executions;
uint medpath_executions;
uint read_slowpath;
uint write_slowpath;
uint push_slowpath;
uint pop_slowpath;
uint read_fastpath;
uint write_fastpath;
uint push_fastpath;
uint pop_fastpath;
uint read4_fastpath;
uint write4_fastpath;
uint push4_fastpath;
uint pop4_fastpath;
uint slow_instead_of_fast;
uint heap_header_exception;
uint tls_exception;
uint alloca_exception;
uint strlen_exception;
uint strlen_uninit_exception;
uint strcpy_exception;
uint rawmemchr_exception;
uint strmem_unaddr_exception;
uint strrchr_exception;
uint andor_exception;
uint bitfield_const_exception;
uint bitfield_xor_exception;
uint loader_DRlib_exception;
uint cppexcept_DRlib_exception;
uint fldfst_exception;
uint heap_func_ref_ignored;
uint reg_dead;
uint reg_xchg;
uint reg_spill;
uint reg_spill_slow;
uint reg_spill_own;
uint reg_spill_used_in_bb;
uint reg_spill_unused_in_bb;
uint addressable_checks_elided;
uint aflags_saved_at_top;
uint xl8_shared;
uint xl8_not_shared_reg_conflict;
uint xl8_not_shared_scratch_conflict;
uint xl8_not_shared_disp_too_big;
uint xl8_not_shared_unaligned;
uint xl8_not_shared_mem2mem;
uint xl8_not_shared_offs;
uint xl8_not_shared_slowpaths;
uint xl8_shared_slowpath_instrs;
uint xl8_shared_slowpath_count;
uint slowpath_unaligned;
uint slowpath_8_at_border;
uint num_bbs;
#endif
/***************************************************************************
* Registers
*/
/* To relocate restores to the common slowpath yet still support
* site-specific scratch registers we have restore patterns for
* every possible combination
*/
/* XXX i#1726: update for ARM */
/* variable-reg: reg1 and reg2 */
enum {
SPILL_REG_NONE, /* !used and !dead */
SPILL_REG_EAX, /* this reg is spilled to tls */
SPILL_REG_ECX,
SPILL_REG_EDX,
SPILL_REG_EBX,
SPILL_REG_EAX_DEAD, /* this reg is dead */
SPILL_REG_ECX_DEAD,
SPILL_REG_EDX_DEAD,
SPILL_REG_EBX_DEAD,
SPILL_REG_NUM,
};
enum {
SPILL_REG3_NOSPILL,
SPILL_REG3_SPILL,
SPILL_REG3_NUM,
};
enum {
SPILL_EFLAGS_NOSPILL,
SPILL_EFLAGS_5_NOEAX,
SPILL_EFLAGS_6_NOEAX,
SPILL_EFLAGS_5_EAX,
SPILL_EFLAGS_6_EAX,
SPILL_EFLAGS_NUM,
};
#define SPILL_REG3_REG DR_REG_XCX
/* The 4 indices are: reg1, reg2, reg3, eflags */
byte *shared_slowpath_entry_local[SPILL_REG_NUM][SPILL_REG_NUM][SPILL_REG3_NUM][SPILL_EFLAGS_NUM];
/* For whole-bb spilling, we do not restore eflags, but reg3 can be anything */
byte *shared_slowpath_entry_global[SPILL_REG_NUM][SPILL_REG_NUM][SPILL_REG_NUM];
byte *shared_slowpath_region;
byte *shared_slowpath_entry;
/* adjust_esp's shared fast and slow paths pointers are below */
/* Lock for updating gencode later */
static void *gencode_lock;
/***************************************************************************
* ISA
*/
bool
instr_propagatable_dsts(instr_t *inst)
{
int i;
bool res = false;
int opc = instr_get_opcode(inst);
for (i = 0; i < instr_num_dsts(inst); i++) {
opnd_t opnd = instr_get_dst(inst, i);
/* i#1543, i#243: we now shadow xmm regs and propagate and mirror xmm
* operations (at least most of them: work in progress).
*/
if ((opnd_is_reg(opnd) && reg_is_shadowed(opc, opnd_get_reg(opnd))) ||
opnd_is_memory_reference(opnd)) {
res = true;
} else {
res = false;
break;
}
}
return res;
}
#ifdef TOOL_DR_MEMORY
/* drcontext can be NULL if the operand is an immed int.
*
* For mmx, xmm, or ymm sources, returns just the lower reg_t bits.
* XXX: we'll need to return the full value for handling OP_pand, etc.!
* For now we only use this to get shift amounts for which we can ignore
* all high bits.
*/
bool
get_cur_src_value(void *drcontext, instr_t *inst, uint i, reg_t *val)
{
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
opnd_t src = instr_get_src(inst, i);
if (val == NULL)
return false;
if (opnd_is_immed_int(src)) {
*val = (reg_t) opnd_get_immed_int(src);
return true;
}
ASSERT(drcontext != NULL, "need drcontext for non-immed opnd");
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL|DR_MC_INTEGER; /* don't need xmm */
dr_get_mcontext(drcontext, &mc);
if (opnd_is_memory_reference(src)) {
app_pc addr = opnd_compute_address(src, &mc);
size_t sz = opnd_size_in_bytes(opnd_get_size(src));
if (sz > sizeof(*val))
return false;
return (safe_read(addr, sz, val));
} else if (opnd_is_reg(src)) {
byte val32[sizeof(dr_ymm_t)];
reg_id_t reg = opnd_get_reg(src);
if (!reg_is_gpr(reg)) {
mc.flags |= DR_MC_MULTIMEDIA;
dr_get_mcontext(drcontext, &mc);
}
if (!reg_get_value_ex(reg, &mc, val32))
return false;
*val = *(reg_t*)val32;
return true;
}
return false;
}
static inline bool
opnds_overlap(opnd_t op1, opnd_t op2)
{
/* XXX: should we check overlap on memory opnd? */
return (opnd_same(op1, op2) ||
(opnd_is_reg(op1) && opnd_is_reg(op2) && opnd_share_reg(op1, op2)));
}
bool
instrs_share_opnd(instr_t *in1, instr_t *in2)
{
int i, j;
for (i = 0; i < instr_num_srcs(in1); i++) {
for (j = 0; j < instr_num_srcs(in2); j++) {
if (opnds_overlap(instr_get_src(in1, i), instr_get_src(in2, j)))
return true;
}
for (j = 0; j < instr_num_dsts(in2); j++) {
if (opnds_overlap(instr_get_src(in1, i), instr_get_dst(in2, j)))
return true;
}
}
for (i = 0; i < instr_num_dsts(in1); i++) {
for (j = 0; j < instr_num_srcs(in2); j++) {
if (opnds_overlap(instr_get_dst(in1, i), instr_get_src(in2, j)))
return true;
}
for (j = 0; j < instr_num_dsts(in2); j++) {
if (opnds_overlap(instr_get_dst(in1, i), instr_get_dst(in2, j)))
return true;
}
}
return false;
}
#endif /* TOOL_DR_MEMORY */
/***************************************************************************
* Definedness and Addressability Checking
*/
void
shadow_combine_init(shadow_combine_t *comb, instr_t *inst, uint opcode, uint max)
{
uint i;
uint init_shadow = SHADOW_DEFINED;
if (opc_dst_subreg_nonlow(opcode) &&
inst != NULL && instr_num_dsts(inst) == 1) {
opnd_t dst = instr_get_dst(inst, 0);
if (opnd_is_reg(dst)) {
reg_id_t reg = opnd_get_reg(dst);
uint opsz = opnd_size_in_bytes(opnd_get_size(dst));
uint regsz = opnd_size_in_bytes(reg_get_size(reg));
if (opsz < regsz) {
/* For opcodes that write to only part of the reg and leave the
* rest unchanged and don't write to just the bottom of the reg,
* we have to pass every byte of the register shadow to
* map_src_to_dst(). We need to incorporate the prior reg
* shadow values, which we can't solely do later as we need to
* distinguish what was written by the opcode. By using
* BITLEVEL we ensure that shadow_combine() will clobber this
* rather than OR it in.
*/
init_shadow = SHADOW_DEFINED_BITLEVEL;
}
}
}
comb->dst = comb->raw;
/* Initialize to defined so we can aggregate operands as we go.
* This works with no-source instrs (rdtsc, etc.)
* This also makes small->large work out w/o any special processing
* (movsz, movzx, cwde, etc.): but XXX: are there any src/dst size
* mismatches where we do NOT want to set dst bytes beyond count
* of src bytes to defined?
*/
for (i = 0; i < max; i++)
comb->dst[i] = init_shadow;
comb->eflags = SHADOW_DEFINED;
comb->inst = inst;
comb->opcode = opcode;
}
#ifdef TOOL_DR_MEMORY
/* Adds a new source operand's value to the array of shadow vals in
* comb->dst to be assigned to the destination.
*/
static void
integrate_register_shadow(shadow_combine_t *comb DR_PARAM_INOUT, int opnum,
reg_id_t reg, uint shadow, bool pushpop)
{
uint i, sz;
if (reg == REG_EFLAGS) {
/* eflags propagates to all bytes */
uint dstsz;
accum_shadow(&comb->eflags, SHADOW_DWORD2BYTE(shadow, 0));
if (instr_num_dsts(comb->inst) == 0)
return;
dstsz = opnd_size_in_bytes(opnd_get_size(instr_get_dst(comb->inst, 0)));
for (i = 0; i < dstsz; i++)
accum_shadow(&comb->dst[i], SHADOW_DWORD2BYTE(shadow, i));
return;
}
/* PR 426162: ignore stack register source if instr also has memref
* using same register as addressing register, since memref will do a
* definedness check for us, and if the reg is undefined we do NOT want
* to propagate it as it will end up in a regular dest, say pop into a
* reg, when that dest should only depend on the memref (since on
* reported error we set addressing register to defined).
*/
if (pushpop && reg_overlap(reg, DR_REG_XSP))
return;
if (integrate_register_shadow_arch(comb, opnum, reg, shadow, pushpop))
return;
if (opc_dst_subreg_nonlow(comb->opcode)) {
/* Deliberately bypassing opnd_get_size() so we can pick the right bits out
* of the reg for opcodes that are sub-xmm but pull from higher than offset
* 0 (e.g., pextr*).
*/
ASSERT(comb->opnd_valid, "need opnd valid for subreg-nonzero opcodes");
sz = opnd_size_in_bytes(reg_get_size(reg));
} else
sz = opnd_size_in_bytes(opnd_get_size(comb->opnd));
for (i = 0; i < sz; i++)
map_src_to_dst(comb, opnum, i, SHADOW_DWORD2BYTE(shadow, i));
}
/* Assigns the array of source shadow_vals to the destination register shadow */
static void
assign_register_shadow(shadow_combine_t *comb DR_PARAM_INOUT, int opnum, opnd_t opnd,
reg_id_t reg, bool pushpop)
{
uint shift = 0;
uint sz, i;
/* Here we need to de-mux from the side-by-side dests in the array
* into individual register dests.
* We also have to shift dsts that do NOT simply go into the lowest slot.
*/
if (assign_register_shadow_arch(comb, opnum, opnd, reg, pushpop, &shift))
return;
if (pushpop && reg_overlap(reg, DR_REG_XSP))
return;
if (opc_dst_subreg_nonlow(comb->opcode)) {
uint shadow = get_shadow_register(reg);
/* Deliberately bypassing opnd_get_size() so we can pick the right bits out
* of the reg for opcodes that are sub-xmm but pull from higher than offset
* 0 (e.g., pextr*).
*/
sz = opnd_size_in_bytes(reg_get_size(reg));
/* Replace the BITLEVEL markers with the register's prior shadow value */
for (i = 0; i < sz; i++) {
if (comb->dst[i] == SHADOW_DEFINED_BITLEVEL)
comb->dst[i] = SHADOW_DWORD2BYTE(shadow, i);
}
} else
sz = opnd_size_in_bytes(opnd_get_size(opnd));
shift *= sz;
register_shadow_set_byte(reg, reg_offs_in_dword(reg), comb->dst[shift + 0]);
if (sz > 1) {
ASSERT(reg_offs_in_dword(reg) == 0, "invalid reg offs");
for (i = 1; i < sz; i++) {
ASSERT(shift + i < OPND_SHADOW_ARRAY_LEN, "shadow_vals overflow");
register_shadow_set_byte(reg, i, comb->dst[shift + i]);
}
}
}
void
register_shadow_mark_defined(reg_id_t reg, size_t sz)
{
uint i;
if (sz == sizeof(void*) && reg_is_gpr(reg))
register_shadow_set_ptrsz(reg, SHADOW_PTRSZ_DEFINED);
else if (sz == 4 && reg_is_gpr(reg)) {
/* We assume the cases where we call this are not app writes and thus we don't
* want to clear the top 32 bits for x64.
*/
register_shadow_set_dword(reg, SHADOW_DWORD_DEFINED);
} else {
for (i = 0; i < sz; i++)
register_shadow_set_byte(reg, i, SHADOW_DEFINED);
}
}
bool
opnd_uses_nonignorable_memory(opnd_t opnd)
{
/* XXX: we could track ebp/r11 and try to determine when not used as frame ptr */
return (opnd_is_memory_reference(opnd) &&
/* pattern mode */
(options.pattern == 0 ? true : pattern_opnd_needs_check(opnd)) &&
/* stack access */
(options.check_stack_access ||
!opnd_is_base_disp(opnd) ||
(reg_to_pointer_sized(opnd_get_base(opnd)) != DR_REG_XSP &&
reg_to_pointer_sized(opnd_get_base(opnd)) != REG_FRAME_PTR) ||
opnd_get_index(opnd) != REG_NULL ||
opnd_is_far_memory_reference(opnd)));
}
/* Called by slow_path() after initial decode. Expected to free inst. */
bool
slow_path_without_uninitialized(void *drcontext, dr_mcontext_t *mc, instr_t *inst,
app_loc_t *loc, size_t instr_sz)
{
opnd_t opnd, memop = opnd_create_null();
int opc, i, num_srcs, num_dsts;
uint sz;
bool pushpop_stackop;
uint flags;
ASSERT(!options.check_uninitialized, "should not be called");
opc = instr_get_opcode(inst);
num_srcs = (IF_X86_ELSE(opc == OP_lea, false)) ? 0 : num_true_srcs(inst, mc);
for (i = 0; i < num_srcs; i++) {
opnd = instr_get_src(inst, i);
if (opnd_uses_nonignorable_memory(opnd)) {
opnd = adjust_memop(inst, opnd, false, &sz, &pushpop_stackop);
if (pushpop_stackop && options.check_stack_bounds)
flags = MEMREF_PUSHPOP | MEMREF_IS_READ;
else
flags = MEMREF_CHECK_ADDRESSABLE | MEMREF_IS_READ;
memop = opnd;
check_mem_opnd_nouninit(opc, flags, loc, opnd, sz, mc);
}
}
num_dsts = num_true_dsts(inst, mc);
for (i = 0; i < num_dsts; i++) {
opnd = instr_get_dst(inst, i);
if (opnd_uses_nonignorable_memory(opnd)) {
opnd = adjust_memop(inst, opnd, true, &sz, &pushpop_stackop);
if (pushpop_stackop && options.check_stack_bounds)
flags = MEMREF_PUSHPOP | MEMREF_WRITE;
else
flags = MEMREF_CHECK_ADDRESSABLE;
memop = opnd;
check_mem_opnd_nouninit(opc, flags, loc, opnd, sz, mc);
}
}
instr_free(drcontext, inst);
/* call this last after freeing inst in case it does a synchronous flush */
slow_path_xl8_sharing(loc, instr_sz, memop, mc);
return true;
}
#endif /* TOOL_DR_MEMORY */
/* Does everything in C code, except for handling non-push/pop writes to esp.
*
* General design:
* + comb.dest[] array holds the shadow values for the destinations.
* If there are multiple dests, they are laid out side-by-side.
* + Shadow values are combined via combine_shadows() which does OR-combining.
*
* First we walk the sources and add each in turn to the shadow array via:
* + integrate_register_shadow() for regs
* + handle_mem_ref() with MEMREF_USE_VALUES for memrefs
* Both call map_src_to_dst() which determines where in
* the dst shadow array to put each source, thus handling arbitrary
* opcodes with weird data movements.
*
* Then we walk the dests and call handle_mem_ref() or
* assign_register_shadow() on each, which pulls from comb.dest[]'s shadow vals.
*
* XXX: can we change handle_mem_ref() and map_src_to_dst() to not operate on
* one byte at a time, so we can make the slowpath more closely match the
* fastpath code, and thus make it easier to transition opcodes to the fastpath?
*/
bool
slow_path_with_mc(void *drcontext, app_pc pc, app_pc decode_pc, dr_mcontext_t *mc)
{
instr_t inst;
int opc;
#ifdef TOOL_DR_MEMORY
opnd_t opnd;
int i, num_srcs, num_dsts;
uint sz;
shadow_combine_t comb;
bool check_definedness, pushpop, pushpop_stackop;
bool check_srcs_after;
bool always_defined;
opnd_t memop = opnd_create_null();
size_t instr_sz;
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
#endif
app_loc_t loc;
#if defined(STATISTICS) && defined(TOOL_DR_MEMORY)
/* PR 423757: periodic stats dump, both for server apps that don't
* close cleanly and to get stats out prior to overflow.
*/
int execs = atomic_add32_return_sum((volatile int *)&slowpath_executions, 1);
if (execs == next_stats_dump) {
/* still racy: could skip a dump, but that's ok */
ATOMIC_ADD32(next_stats_dump, options.stats_dump_interval);
dr_fprintf(f_global, "\n**** per-%dK-slowpath stats dump:\n",
options.stats_dump_interval/1000);
dump_statistics();
}
#endif
pc_to_loc(&loc, pc);
/* Locally-spilled and whole-bb-spilled (PR 489221) registers have
* already been restored in shared_slowpath, so we can properly
* emulate addresses referenced. We can't restore whole-bb-spilled
* here b/c we don't have the bb tag. Eflags may not be restored
* but we don't rely on them here.
*/
ASSERT(decode_pc != NULL, "single_arg_slowpath removed");
#ifdef TOOL_DR_MEMORY
if (decode_pc != NULL) {
if (medium_path_arch(decode_pc, &loc, mc))
return true;
}
#endif /* TOOL_DR_MEMORY */
instr_init(drcontext, &inst);
#ifdef TOOL_DR_MEMORY
instr_sz = decode(drcontext, decode_pc, &inst) - decode_pc;
#else
decode(drcontext, decode_pc, &inst);
#endif
ASSERT(instr_valid(&inst), "invalid instr");
opc = instr_get_opcode(&inst);
slowpath_update_app_loc_arch(opc, decode_pc, &loc);
#ifdef STATISTICS
STATS_INC(slowpath_count[opc]);
{
uint bytes = instr_memory_reference_size(&inst);
if (bytes == 0) {
if (instr_num_dsts(&inst) > 0 &&
!opnd_is_pc(instr_get_dst(&inst, 0)) &&
!opnd_is_instr(instr_get_dst(&inst, 0)))
bytes = opnd_size_in_bytes(opnd_get_size(instr_get_dst(&inst, 0)));
else if (instr_num_srcs(&inst) > 0 &&
!opnd_is_pc(instr_get_src(&inst, 0)) &&
!opnd_is_instr(instr_get_src(&inst, 0)))
bytes = opnd_size_in_bytes(opnd_get_size(instr_get_src(&inst, 0)));
else
bytes = 0;
}
if (bytes == 1)
STATS_INC(slowpath_sz1);
else if (bytes == 2)
STATS_INC(slowpath_sz2);
else if (bytes == 4)
STATS_INC(slowpath_sz4);
else if (bytes == 8)
STATS_INC(slowpath_sz8);
else if (bytes == 10)
STATS_INC(slowpath_sz10);
else if (bytes == 16)
STATS_INC(slowpath_sz16);
else
STATS_INC(slowpath_szOther);
}
#endif
DOLOG(3, {
LOG(3, "\nslow_path "PFX": ", pc);
instr_disassemble(drcontext, &inst, LOGFILE_GET(drcontext));
if (instr_num_dsts(&inst) > 0 &&
opnd_is_memory_reference(instr_get_dst(&inst, 0))) {
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
LOG(3, " | 0x%x",
shadow_get_byte(&info,
opnd_compute_address(instr_get_dst(&inst, 0),
mc)));
}
LOG(3, "\n");
});
#ifdef TOOL_DR_HEAPSTAT
return slow_path_for_staleness(drcontext, mc, &inst, &loc);
#else
if (!options.check_uninitialized)
return slow_path_without_uninitialized(drcontext, mc, &inst, &loc, instr_sz);
LOG(4, "shadow registers prior to instr:\n");
DOLOG(4, { print_shadow_registers(); });
/* We need to do the following:
* - check addressability of all memory operands
* - check definedness of all source operands if:
* o no GPR or memory dest (=> no way to store definedness)
* o if options.check_uninit_non_moves is on and this is not just a move
* - check definedness of certain source operands:
* o base or index register to a memory ref
* (includes esp/ebp operand to a push/pop)
* o ecx to stringop
* o ebp to enter/leave
* - combine definedness of source operands and store
* in dest operand shadows
* - if a pop, make stack slot(s) unaddressable
*
* Usually there's one destination we need to transfer
* definedness to. If there are more, we can fit them side by
* side in our 8-dword-capacity comb->dst array.
*/
check_definedness = instr_check_definedness(&inst);
always_defined = result_is_always_defined(&inst, false/*us*/);
pushpop = opc_is_push(opc) || opc_is_pop(opc);
check_srcs_after = instr_needs_all_srcs_and_vals(&inst);
if (check_srcs_after) {
/* We need to check definedness of addressing registers, and so we do
* our normal src loop but we do not check undefinedness or combine
* sources. Below we pass pointers to later in comb->dst to
* check_mem_opnd() and integrate_register_shadow(), causing the 2
* sources to be laid out side-by-side in comb->dst.
*/
ASSERT(instr_num_srcs(&inst) == 2, "and/or special handling error");
check_definedness = false;
IF_DEBUG(comb.opsz = 0;) /* for asserts below */
}
shadow_combine_init(&comb, &inst, opc, OPND_SHADOW_ARRAY_LEN);
num_srcs = num_true_srcs(&inst, mc);
#ifdef X86
if (opc == OP_lea)
num_srcs = IF_X64_ELSE(opnd_is_rel_addr(instr_get_src(&inst, 0)), false) ? 0 : 2;
#endif
check_srcs:
for (i = 0; i < num_srcs; i++) {
if (IF_X86_ELSE(opc == OP_lea, false)) {
/* special case: treat address+base as propagatable sources
* code below can handle REG_NULL
*/
if (i == 0)
opnd = opnd_create_reg(opnd_get_base(instr_get_src(&inst, 0)));
else
opnd = opnd_create_reg(opnd_get_index(instr_get_src(&inst, 0)));
} else {
opnd = instr_get_src(&inst, i);
}
if (opnd_is_memory_reference(opnd)) {
int flags = 0;
opnd = adjust_memop(&inst, opnd, false, &sz, &pushpop_stackop);
/* do not combine srcs if checking after */
if (check_srcs_after) {
ASSERT(i == 0 || sz >= comb.opsz, "check-after needs >=-size srcs");
comb.dst = &comb.raw[i*sz]; /* shift the dst in the array */
}
shadow_combine_set_opnd(&comb, opnd, sz);
/* check_mem_opnd() checks definedness of base registers,
* addressability of address, and if necessary checks definedness
* and adjusts addressability of address.
*/
if (pushpop_stackop)
flags |= MEMREF_PUSHPOP;
if (always_defined) {
LOG(2, "marking and/or/xor with 0/~0/self as defined @"PFX"\n", pc);
/* w/o MEMREF_USE_VALUES, handle_mem_ref() will use SHADOW_DEFINED */
} else if (check_definedness || always_check_definedness(&inst, i)) {
flags |= MEMREF_CHECK_DEFINEDNESS;
if (options.leave_uninit)
flags |= MEMREF_USE_VALUES;
} else {
/* If we're checking, to avoid further errors we do not
* propagate the shadow vals (and thus we essentially
* propagate SHADOW_DEFINED).
* Conveniently all the large operand sizes always
* have check_definedness since they involve fp or sse.
*/
ASSERT(sz <= sizeof(comb.raw), "internal shadow val error");
flags |= MEMREF_USE_VALUES;
}
memop = opnd;
check_mem_opnd(opc, flags, &loc, opnd, sz, mc, i, &comb);
} else if (opnd_is_reg(opnd)) {
reg_id_t reg = opnd_get_reg(opnd);
if (reg_is_shadowed(opc, reg)) {
uint shadow = get_shadow_register(reg);
if (opc_dst_subreg_nonlow(opc)) {
/* We need the whole reg as this opcode references high up */
sz = opnd_size_in_bytes(reg_get_size(reg));
} else
sz = opnd_size_in_bytes(opnd_get_size(opnd));
/* do not combine srcs if checking after */
if (check_srcs_after) {
ASSERT(i == 0 || sz >= comb.opsz, "check-after needs >=-size srcs");
comb.dst = &comb.raw[i*sz]; /* shift the dst in the array */
}
shadow_combine_set_opnd(&comb, opnd, sz);
if (always_defined) {
/* if result defined regardless, don't propagate (is
* equivalent to propagating SHADOW_DEFINED) or check */
} else if (check_definedness || always_check_definedness(&inst, i)) {
check_register_defined(drcontext, reg, &loc, sz, mc, &inst);
if (options.leave_uninit) {
integrate_register_shadow(&comb, i, reg, shadow, pushpop);
}
} else {
/* See above: we only propagate when not checking */
integrate_register_shadow(&comb, i, reg, shadow, pushpop);
}
} /* else always defined */
} else /* always defined */
ASSERT(opnd_is_immed_int(opnd) || opnd_is_pc(opnd), "unexpected opnd");
DOLOG(4, {
int j;
LOG(4, "shadows after src %d ", i);
opnd_disassemble(drcontext, opnd, LOGFILE_GET(drcontext));
LOG(4, ": ");
for (j = 0; j < OPND_SHADOW_ARRAY_LEN; j++)
LOG(4, "%d", comb.raw[j]);
LOG(4, ", eflags: %d\n", comb.eflags);
});
}
/* eflags source */
if (TESTANY(EFLAGS_READ_ARITH, instr_get_eflags(&inst, DR_QUERY_DEFAULT))) {
uint shadow = get_shadow_eflags();
/* for check_srcs_after we leave comb.dst where it last was */
if (always_defined) {
/* if result defined regardless, don't propagate (is
* equivalent to propagating SHADOW_DEFINED) or check */
} else if (check_definedness) {
check_register_defined(drcontext, REG_EFLAGS, &loc, 1, mc, &inst);
if (options.leave_uninit)
integrate_register_shadow(&comb, 0, REG_EFLAGS, shadow, pushpop);
} else {
/* See above: we only propagate when not checking */
integrate_register_shadow(&comb, 0, REG_EFLAGS, shadow, pushpop);
}
} else if (num_srcs == 0) {
/* do not propagate from comb.dst since dst size could be large (i#458)
* (fxsave, etc.)
*/
always_defined = true;
}
if (check_srcs_after)
comb.dst = comb.raw; /* restore */
if (check_srcs_after) {
/* turn back on for dsts */
check_definedness = instr_check_definedness(&inst);
if (check_andor_sources(drcontext, mc, &inst, &comb, decode_pc + instr_sz)) {
if (TESTANY(EFLAGS_WRITE_ARITH,
instr_get_eflags(&inst, DR_QUERY_INCLUDE_ALL))) {
/* We have to redo the eflags propagation. map_src_to_dst() combined
* all the laid-out sources, some of which we made defined in
* check_andor_sources.
*/
comb.eflags = SHADOW_DEFINED;
for (i = 0; i < OPND_SHADOW_ARRAY_LEN; i++)
accum_shadow(&comb.eflags, comb.dst[i]);
}
}
if (check_definedness) {
/* If we need to report undefs we have to go back */
bool all_defined = true;
for (i = 0; i < OPND_SHADOW_ARRAY_LEN; i++) {
if (comb.dst[i] != SHADOW_DEFINED) {
all_defined = false;
break;
}
}
if (!all_defined) {
/* We do not bother to suppress reporting the particular bytes that
* may have been "defined" due to 0/1 in the other operand since
* doing so would require duplicating/extracting all the reporting
* logic above for regs and in handle_mem_ref(): our goto here is
* slightly less ugly.
*/
LOG(4, "and/or not all defined and need to check def: restarting\n");
/* Avoid recursing, and don't do the side-by-side layout this time */
check_srcs_after = false;
for (i = 0; i < OPND_SHADOW_ARRAY_LEN; i++)
comb.dst[i] = SHADOW_DEFINED;
comb.eflags = SHADOW_DEFINED;
goto check_srcs;
}
}
}
num_dsts = num_true_dsts(&inst, mc);
for (i = 0; i < num_dsts; i++) {
opnd = instr_get_dst(&inst, i);
if (opnd_is_memory_reference(opnd)) {
int flags = MEMREF_WRITE;
opnd = adjust_memop(&inst, opnd, true, &sz, &pushpop_stackop);
if (pushpop_stackop)
flags |= MEMREF_PUSHPOP;
if (cpt->mem2fpmm_source != NULL && cpt->mem2fpmm_pc == pc) {
/* i#471 fld;fstp heuristic: fstp's dest was marked bitlevel to
* get us here. Do a special-case propagate.
*/
umbra_shadow_memory_info_t info;
LOG(3, "propagating fld;fstp from "PFX"\n", cpt->mem2fpmm_source);
/* We use a fake movs in handle_mem_ref() (can't just do
* shadow_copy_range() b/c we need to check base reg for
* definedness, check for addressability, etc.)
*/
umbra_shadow_memory_info_init(&info);
shadow_set_byte(&info, cpt->mem2fpmm_dest, cpt->mem2fpmm_prev_shadow);
comb.movs_addr = cpt->mem2fpmm_source;
flags |= MEMREF_MOVS | MEMREF_USE_VALUES;
cpt->mem2fpmm_source = NULL;
} else if (always_defined) {
/* w/o MEMREF_USE_VALUES, handle_mem_ref() will use SHADOW_DEFINED */
} else if (check_definedness) {
flags |= MEMREF_CHECK_DEFINEDNESS;
if (options.leave_uninit)
flags |= MEMREF_USE_VALUES;
/* since checking, we mark as SHADOW_DEFINED (see above) */
} else {
ASSERT(sz <= sizeof(comb.raw), "internal shadow val error");
flags |= MEMREF_USE_VALUES;
}
/* check addressability, and propagate
* we arranged xchg/xadd to not need shifting; nothing else does either.
*/
memop = opnd;
check_mem_opnd(opc, flags, &loc, opnd, sz, mc, i, &comb);
} else if (opnd_is_reg(opnd)) {
reg_id_t reg = opnd_get_reg(opnd);
if (reg_is_shadowed(opc, reg)) {
assign_register_shadow(&comb, i, opnd, reg, pushpop);
}
} else
ASSERT(opnd_is_immed_int(opnd) || opnd_is_pc(opnd), "unexpected opnd");
}
if (TESTANY(EFLAGS_WRITE_ARITH, instr_get_eflags(&inst, DR_QUERY_INCLUDE_ALL))) {
set_shadow_eflags(comb.eflags);
}
LOG(4, "shadow registers after instr:\n");
DOLOG(4, { print_shadow_registers(); });
instr_free(drcontext, &inst);
/* call this last after freeing inst in case it does a synchronous flush */
slow_path_xl8_sharing(&loc, instr_sz, memop, mc);
DOLOG(5, { /* this pollutes the logfile, so it's a pain to have at 4 or lower */
if (pc == decode_pc/*else retpc not in tls3*/) {
/* Test translation when have both args */
/* we want the ultimate target, not whole_bb_spills_enabled()'s
* SPILL_SLOT_5 intermediate target
*/
byte *ret_pc = (byte *) get_own_tls_value(SPILL_SLOT_SLOW_RET);
/* ensure event_restore_state() returns true */
byte *xl8;
cpt->self_translating = true;
xl8 = dr_app_pc_from_cache_pc(ret_pc);
cpt->self_translating = false;
LOG(3, "translation test: cache="PFX", orig="PFX", xl8="PFX"\n",
ret_pc, pc, xl8);
ASSERT(xl8 == pc ||
IF_X86((options.repstr_to_loop &&
/* Depending on -no_fastpath we'll get here for the jecxz
* pointing at the loop, the loop, or the stringop.
*/
(opc_is_stringop(opc) || opc == OP_loop) &&
/* For repstr_to_loop we changed pc */
(xl8 == loc_to_pc(&loc) ||
/* For repstr_to_loop OP_loop, ret_pc is the restore
* code after stringop and before OP_loop*, so we'll get
* post-xl8 pc.
*/
xl8 == decode_next_pc(drcontext, loc_to_pc(&loc)))) ||)
/* ret_pc may be a global reg restore, and for -no_fastpath
* this will use the prior xl8 since there's no meta-xl8 and
* the real app instr is beyond ret_pc.
*/
(instr_at_pc_is_restore(drcontext, ret_pc) &&
pc == decode_next_pc(drcontext, xl8)) ||
/* for native ret we changed pc */
(options.replace_malloc && opc == IF_X86_ELSE(OP_ret, OP_bx) &&
alloc_entering_replace_routine(xl8)),
"xl8 doesn't match");
}
});
return true;
#endif /* !TOOL_DR_HEAPSTAT */
}
/* called from code cache */
static bool
slow_path(app_pc pc, app_pc decode_pc)
{
void *drcontext = dr_get_current_drcontext();
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
bool res;
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL|DR_MC_INTEGER; /* don't need xmm */
dr_get_mcontext(drcontext, &mc);
res = slow_path_with_mc(drcontext, pc, decode_pc, &mc);
#ifdef TOOL_DR_MEMORY
DODEBUG({
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
/* Try to ensure that mem2fpmm_source doesn't "escape" */
ASSERT(cpt->mem2fpmm_source == NULL ||
(pc >= cpt->mem2fpmm_load_pc && pc <= cpt->mem2fpmm_pc),
"mem2fpmm source escaped");
});
#endif
return res;
}
/* Returns whether a single pc can be used for app reporting and
* decoding of the app instr (or, whether a separate decode pc can be
* used b/c there's fixup code for the pc to report in the slowpath).
* The OUT param is either an immed int opnd or an instr opnd that can be
* used as an intpr opnd for decoding.
*/
static bool
instr_shared_slowpath_decode_pc(instr_t *inst, fastpath_info_t *mi,
opnd_t *decode_pc_opnd)
{
app_pc pc = instr_get_app_pc(inst);
app_pc decode_pc = dr_app_pc_for_decoding(pc);
if (!options.shared_slowpath) {
*decode_pc_opnd = OPND_CREATE_INTPTR(decode_pc);
return false;
}
if (mi->bb->fake_xl8_override_instr == inst) {
*decode_pc_opnd = OPND_CREATE_INTPTR(mi->bb->fake_xl8_override_pc);
return true;
} else if (mi->bb->fake_xl8 != NULL) {
*decode_pc_opnd = OPND_CREATE_INTPTR(mi->bb->fake_xl8);
return true;
} else if (pc != decode_pc) {
/* We have to handle DR trampolines so we pass in a separate pc to
* decode from
*/
*decode_pc_opnd = OPND_CREATE_INTPTR(decode_pc);
return false;
} else if (pc == instr_get_raw_bits(inst)) {
/* If it matches the raw bits we know we only need one pc */
*decode_pc_opnd = OPND_CREATE_INTPTR(pc);
return true;
} else {
if (options.replace_malloc && alloc_entering_replace_routine(pc)) {
/* drwrap_replace_native() emulates a push for call site
* replacement via generated instrs whose app pcs do not match
* their code cache forms.
*/
} else {
DOLOG(1, {
if (IF_X86_ELSE(instr_get_opcode(inst) == OP_xchg &&
opnd_is_reg(instr_get_dst(inst, 1)) &&
opnd_get_reg(instr_get_dst(inst, 1)) == DR_REG_XAX &&
opnd_is_base_disp(instr_get_dst(inst, 0)) &&
opnd_get_base(instr_get_dst(inst, 0)) == DR_REG_XAX,
false)) {
/* this is the retaddr clobber from bb_handle_chkstk() */
} else {
void *drcontext = dr_get_current_drcontext();
LOG(1, "unknown generated app instr: ");
instr_disassemble(drcontext, inst, LOGFILE_GET(drcontext));
LOG(1, "\n");
}
});
/* To try and handle any generated app2app we point at the
* cache instr. We'll have to construct fake instrs to point
* at if we end up encountering any mangled instrs.
*/
}
/* drwrap_replace() for x86_64 and ARM uses a jmp through a reg */
ASSERT(instr_is_app(inst) || !instr_is_cti(inst), "assuming non-mangled");
*decode_pc_opnd = opnd_create_instr(inst);
return false;
}
}
bool
instr_can_use_shared_slowpath(instr_t *inst, fastpath_info_t *mi)
{
opnd_t ignore;
return instr_shared_slowpath_decode_pc(inst, mi, &ignore);
}
void
instrument_slowpath(void *drcontext, instrlist_t *bb, instr_t *inst,
fastpath_info_t *mi)
{
opnd_t decode_pc_opnd;
ASSERT(options.pattern == 0, "No slow path for pattern mode");
if (instr_shared_slowpath_decode_pc(inst, mi, &decode_pc_opnd)
IF_ARM(&& false/*NYI: see below*/)) {
#ifdef X86
/* Since the clean call instr sequence is quite long we share
* it among all bbs. Rather than switch to a clean stack we jmp
* there and jmp back. Since we don't nest we can get away
* w/ a single TLS slot. The other is used for the arg. We
* inline the call if the 2nd arg is different.
* Note that having slow_path use dr_redirect_execution() instead
* of coming back here is problematic since when would we execute the
* app instr? Can't do it before since need to emulate addresses,
* and don't have a single-step or other way to have target not be
* executed directly in future.
*/
instr_t *appinst = INSTR_CREATE_label(drcontext);
if (mi == NULL) {
ASSERT(!whole_bb_spills_enabled(), "whole-bb needs tls preserved");
instru_insert_mov_pc(drcontext, bb, inst,
spill_slot_opnd(drcontext, SPILL_SLOT_1),
decode_pc_opnd);
/* XXX: this hardcoded address will be wrong if this
* fragment is shifted, but DR now disables shifting in the
* presence of clients (i#784, DRi#696).
* The old trace API would end up copying this, but the new one
* copies from the app code, so there's no problem there.
* And so long as we're never at a "safe spot" in the lean procedure,
* DR won't relocate us or remove our return point out from underneath us.
*/
instru_insert_mov_pc(drcontext, bb, inst,
spill_slot_opnd(drcontext, SPILL_SLOT_2),
opnd_create_instr(appinst));
PRE(bb, inst,
XINST_CREATE_jump(drcontext, opnd_create_pc(shared_slowpath_entry)));
} else {
/* Don't restore, and put consts into registers if we can, to save space */
scratch_reg_info_t *s1, *s2, *s3;
int r1, r2, r3, ef = 0;
bool spill_eax;
byte *tgt;
s1 = &mi->reg1;
s2 = &mi->reg2;
s3 = &mi->reg3;
/* For whole-bb spilling we sometimes have reg3 in slot1 or slot2 b/c
* we picked ecx as a whole-bb spill: simplest to shuffle here
*/
if (whole_bb_spills_enabled() && mi->reg3.global) {
if (mi->reg3.slot == SPILL_SLOT_1) {
s1 = &mi->reg3;
s3 = &mi->reg1;
} else {
ASSERT(mi->reg3.slot == SPILL_SLOT_2, "spill assumption violated");
s2 = &mi->reg3;
s3 = &mi->reg2;
}
}
ASSERT(s1->slot == SPILL_SLOT_1 && s2->slot == SPILL_SLOT_2, "slot error");
r1 = (s1->dead ? (s1->reg - DR_REG_XAX + SPILL_REG_EAX_DEAD) :
((!s1->used || s1->xchg != REG_NULL) ? SPILL_REG_NONE :
(s1->reg - DR_REG_XAX + SPILL_REG_EAX)));
r2 = (s2->dead ? (s2->reg - DR_REG_XAX + SPILL_REG_EAX_DEAD) :
((!s2->used || s2->xchg != REG_NULL) ? SPILL_REG_NONE :
(s2->reg - DR_REG_XAX + SPILL_REG_EAX)));
if (whole_bb_spills_enabled()) {
/* reg3 just like 1 and 2: can be any reg */
r3 = (s3->dead ? (s3->reg - DR_REG_XAX + SPILL_REG_EAX_DEAD) :
((!s3->used || s3->xchg != REG_NULL) ? SPILL_REG_NONE :
(s3->reg - DR_REG_XAX + SPILL_REG_EAX)));
} else {
/* if reg3 is dead we do not need to restore */
r3 = ((!s3->used || s3->dead || s3->xchg != REG_NULL) ?
SPILL_REG3_NOSPILL : SPILL_REG3_SPILL);
spill_eax = (mi->eax.used && !mi->eax.dead && mi->eax.xchg == REG_NULL);
ef = ((whole_bb_spills_enabled() ||
mi->aflags == EFLAGS_WRITE_ARITH) ? SPILL_EFLAGS_NOSPILL :
((mi->aflags == EFLAGS_WRITE_OF) ?
(spill_eax ? SPILL_EFLAGS_5_EAX : SPILL_EFLAGS_5_NOEAX) :
(spill_eax ? SPILL_EFLAGS_6_EAX : SPILL_EFLAGS_6_NOEAX)));
}
ASSERT(r1 >= 0 && r1 < SPILL_REG_NUM, "shared slowpath index error");
ASSERT(r2 >= 0 && r2 < SPILL_REG_NUM, "shared slowpath index error");
tgt = (whole_bb_spills_enabled() ?
shared_slowpath_entry_global[r1][r2][r3]:
shared_slowpath_entry_local[r1][r2][r3][ef]);
ASSERT(tgt != NULL, "targeting un-generated slowpath");
instru_insert_mov_pc(drcontext, bb, inst,
(r1 == SPILL_REG_NONE) ?
spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_PARAM) :
opnd_create_reg(s1->reg),
decode_pc_opnd);
instru_insert_mov_pc(drcontext, bb, inst,
(r2 == SPILL_REG_NONE) ?
spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_RET) :
opnd_create_reg(s2->reg),
opnd_create_instr(appinst));
PRE(bb, inst, XINST_CREATE_jump(drcontext, opnd_create_pc(tgt)));
}
PRE(bb, inst, appinst);
/* If we entered the slowpath, we've clobbered the reg holding the address to
* share so we have to clear it. Rather than slow_path_xl8_sharing() doing so,
* which requires a 2-step return and a fixed TLS slot to get it back into the
* per-call-site reg, we pay a little in inlined cache size and clear it here.
*/
if (SHARING_XL8_ADDR(mi)) {
instru_insert_mov_pc(drcontext, bb, inst, opnd_create_reg(mi->reg1.reg),
OPND_CREATE_INTPTR(shadow_bitlevel_addr()));
}
#else
/* FIXME i#1726: add ARM port. Some of the above code was
* made cross-platform, but the per-scratch-reg code and some
* of the generated instrs are x86-specific still.
*/
ASSERT_NOT_IMPLEMENTED();
#endif
} else {
app_pc pc = instr_get_app_pc(inst);
if (mi != NULL) {
/* We assume caller did a restore */
}
if (!opnd_same(OPND_CREATE_INTPTR(pc), decode_pc_opnd)) {
LOG(1, "INFO: app "PFX" has separate decode pc\n", pc);
}
dr_insert_clean_call(drcontext, bb, inst,
(void *) slow_path, false, 2,
OPND_CREATE_INTPTR(pc),
decode_pc_opnd);
}
}
bool
is_in_gencode(byte *pc)
{
#ifdef TOOL_DR_HEAPSTAT
ASSERT(false, "should not get here");
#endif
return (pc >= shared_slowpath_region &&
pc < shared_slowpath_region + SHARED_SLOWPATH_SIZE);
}
#ifdef X86 /* XXX i#1726: update for ARM */
static void
shared_slowpath_save_param(void *drcontext, instrlist_t *ilist, int type)
{
if ((type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ||
(type >= SPILL_REG_EAX_DEAD && type <= SPILL_REG_EBX_DEAD)) {
reg_id_t reg = (type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ?
(DR_REG_XAX + (type - SPILL_REG_EAX)) :
(DR_REG_XAX + (type - SPILL_REG_EAX_DEAD));
/* Store from site-specific reg into TLS for clean call param */
PRE(ilist, NULL, INSTR_CREATE_mov_st
(drcontext, spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_PARAM),
opnd_create_reg(reg)));
} /* else param was put straight in tls slot */
}
static void
shared_slowpath_save_retaddr(void *drcontext, instrlist_t *ilist, int type)
{
if ((type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ||
(type >= SPILL_REG_EAX_DEAD && type <= SPILL_REG_EBX_DEAD)) {
reg_id_t reg = (type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ?
(DR_REG_XAX + (type - SPILL_REG_EAX)) :
(DR_REG_XAX + (type - SPILL_REG_EAX_DEAD));
/* Store from site-specific reg into TLS for clean call ret */
PRE(ilist, NULL, INSTR_CREATE_mov_st
(drcontext, spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_RET),
opnd_create_reg(reg)));
} /* else param was put straight in tls slot */
}
static void
shared_slowpath_restore(void *drcontext, instrlist_t *ilist, int type, int slot)
{
if ((type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ||
(type >= SPILL_REG_EAX_DEAD && type <= SPILL_REG_EBX_DEAD)) {
reg_id_t reg = (type >= SPILL_REG_EAX && type <= SPILL_REG_EBX) ?
(DR_REG_XAX + (type - SPILL_REG_EAX)) :
(DR_REG_XAX + (type - SPILL_REG_EAX_DEAD));
/* Restore app value to reg for emulation in slowpath */
PRE(ilist, NULL, INSTR_CREATE_mov_ld
(drcontext, opnd_create_reg(reg), spill_slot_opnd(drcontext, slot)));
} /* else param was put straight in tls slot */
}
#endif
byte *
generate_shared_slowpath(void *drcontext, instrlist_t *ilist, byte *pc)
{
#ifdef X86
int r1, r2, r3, ef;
/* Create our shared slowpath. To save space at the "call" site, we
* restore the spilled registers/eflags here; but to support site-specific
* selection of which registers to use, we have every possible restore
* sequence here. An alternative is to have the C code determine
* which registers/eflags were spilled and swap the registers with the
* tls slots.
* The call site places the single value for both args in reg1
* (this slowpath call is not supported if the values differ) and
* the return pc in reg2 and then jmps to the appropriate restore
* entry point. After restoring and putting the args into spill
* slots, we do a clean call, which redirects afterward and so
* does not return.
*/
shared_slowpath_entry = pc;
dr_insert_clean_call(drcontext, ilist, NULL,
(void *) slow_path, false, 2,
spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_PARAM),
spill_slot_opnd(drcontext, SPILL_SLOT_SLOW_PARAM));
PRE(ilist, NULL,
XINST_CREATE_jump_mem(drcontext, spill_slot_opnd
(drcontext, SPILL_SLOT_SLOW_RET)));
pc = instrlist_encode(drcontext, ilist, pc, false);
instrlist_clear(drcontext, ilist);
for (r1 = 0; r1 < SPILL_REG_NUM; r1++) {
for (r2 = 0; r2 < SPILL_REG_NUM; r2++) {
/* for whole-bb, r3 is not always ecx */
for (r3 = 0; r3 < (whole_bb_spills_enabled() ?
SPILL_REG_NUM : SPILL_REG3_NUM); r3++) {
/* for whole-bb, eflags is never restored here */
for (ef = 0; ef < (whole_bb_spills_enabled() ? 1 : SPILL_EFLAGS_NUM);
ef++) {
if (!whole_bb_spills_enabled() && ef != SPILL_EFLAGS_NOSPILL) {
if (ef == SPILL_EFLAGS_6_EAX ||
ef == SPILL_EFLAGS_6_NOEAX) {
PRE(ilist, NULL, INSTR_CREATE_add
(drcontext, opnd_create_reg(REG_AL),
OPND_CREATE_INT8(0x7f)));
}
PRE(ilist, NULL, INSTR_CREATE_sahf(drcontext));
if (ef == SPILL_EFLAGS_6_EAX ||
ef == SPILL_EFLAGS_5_EAX) {
restore_reg(drcontext, ilist, NULL, DR_REG_XAX,
SPILL_SLOT_EFLAGS_EAX);
}
}
if (whole_bb_spills_enabled()) {
shared_slowpath_restore(drcontext, ilist, r3, SPILL_SLOT_4);
} else if (r3 != SPILL_REG3_NOSPILL) {
restore_reg(drcontext, ilist, NULL, SPILL_REG3_REG,
spill_reg3_slot(ef == SPILL_EFLAGS_NOSPILL,
ef != SPILL_EFLAGS_5_EAX &&
ef != SPILL_EFLAGS_6_EAX,
r1 >= SPILL_REG_EAX_DEAD &&
r1 <= SPILL_REG_EBX_DEAD,
r2 >= SPILL_REG_EAX_DEAD &&
r2 <= SPILL_REG_EBX_DEAD));
}
shared_slowpath_save_retaddr(drcontext, ilist, r2);
shared_slowpath_restore(drcontext, ilist, r2, SPILL_SLOT_2);
shared_slowpath_save_param(drcontext, ilist, r1);
shared_slowpath_restore(drcontext, ilist, r1, SPILL_SLOT_1);
PRE(ilist, NULL,
XINST_CREATE_jump(drcontext,
opnd_create_pc(shared_slowpath_entry)));
if (whole_bb_spills_enabled())
shared_slowpath_entry_global[r1][r2][r3] = pc;
else
shared_slowpath_entry_local[r1][r2][r3][ef] = pc;
pc = instrlist_encode(drcontext, ilist, pc, true);
instrlist_clear(drcontext, ilist);
}
}
}
}
return pc;
#else
/* FIXME i#1726: add ARM port. Some of the above code was made cross-platform, but
* the per-scratch-reg code and some of the generated instrs are x86-specific still.
* We may want to start with just a regular clean call for ARM.
*/
ASSERT_NOT_IMPLEMENTED();
return pc;
#endif
}
void
gencode_init(void)
{
#ifdef TOOL_DR_MEMORY
instrlist_t *ilist;
void *drcontext = dr_get_current_drcontext();
byte *pc;
IF_DEBUG(bool ok;)
#endif
gencode_lock = dr_mutex_create();
#ifdef STATISTICS
next_stats_dump = options.stats_dump_interval;
#endif
#ifdef TOOL_DR_MEMORY
ilist = instrlist_create(drcontext);
shared_slowpath_region = (byte *)
nonheap_alloc(SHARED_SLOWPATH_SIZE,
DR_MEMPROT_READ|DR_MEMPROT_WRITE|DR_MEMPROT_EXEC,
HEAPSTAT_GENCODE);
pc = shared_slowpath_region;
pc = generate_shared_slowpath(drcontext, ilist, pc);
ASSERT(pc - shared_slowpath_region <= SHARED_SLOWPATH_SIZE,
"shared esp slowpath too large");
pc = generate_shared_esp_slowpath(drcontext, ilist, pc);
ASSERT(pc - shared_slowpath_region <= SHARED_SLOWPATH_SIZE,
"shared esp slowpath too large");
pc = generate_shared_esp_fastpath(drcontext, ilist, pc);
ASSERT(pc - shared_slowpath_region <= SHARED_SLOWPATH_SIZE,
"shared esp fastpath too large");
instrlist_clear_and_destroy(drcontext, ilist);
/* now mark as +rx (non-writable) */
IF_DEBUG(ok = )
dr_memory_protect(shared_slowpath_region, SHARED_SLOWPATH_SIZE,
DR_MEMPROT_READ|DR_MEMPROT_EXEC);
ASSERT(ok, "-w failed on shared routines gencode");
DOLOG(2, {
byte *end_pc = pc;
pc = shared_slowpath_region;
LOG(2, "shared_slowpath region:\n");
while (pc < end_pc) {
pc = disassemble_with_info(drcontext, pc, f_global,
true/*show pc*/, true/*show bytes*/);
}
});
#endif
}
void
gencode_exit(void)
{
dr_mutex_destroy(gencode_lock);
#ifdef TOOL_DR_MEMORY
nonheap_free(shared_slowpath_region, SHARED_SLOWPATH_SIZE,
HEAPSTAT_GENCODE);
#endif
}
/* PR 525807: try to handle small malloced stacks */
void
update_stack_swap_threshold(void *drcontext, int new_threshold)
{
IF_DEBUG(bool ok;)
if (stack_swap_threshold_fixed) {
/* If user specifies a threshold we disable our dynamic adjustments */
return;
}
LOG(1, "updating stack swap threshold from "PIFX" to "PIFX"\n",
options.stack_swap_threshold, new_threshold);
dr_mutex_lock(gencode_lock);
if (dr_memory_protect(shared_slowpath_region, SHARED_SLOWPATH_SIZE,
DR_MEMPROT_READ|DR_MEMPROT_WRITE|DR_MEMPROT_EXEC)) {
esp_fastpath_update_swap_threshold(drcontext, new_threshold);
IF_DEBUG(ok = )
dr_memory_protect(shared_slowpath_region, SHARED_SLOWPATH_SIZE,
DR_MEMPROT_READ|DR_MEMPROT_EXEC);
ASSERT(ok, "-w failed on shared routines gencode");
options.stack_swap_threshold = new_threshold;
} else
ASSERT(false, "+w failed on shared routines gencode");
dr_mutex_unlock(gencode_lock);
}
#ifdef TOOL_DR_MEMORY
static uint
memref_idx(uint flags, uint i)
{
return (TEST(MEMREF_SINGLE_BYTE, flags) ? 0 :
(TEST(MEMREF_SINGLE_WORD, flags) ? (i % 2) :
(TEST(MEMREF_SINGLE_DWORD, flags) ? (i %4 ) : i)));
}
/* handle_mem_ref checks addressability and if necessary checks
* definedness and adjusts addressability
* returns true if no errors were found
*/
bool
handle_mem_ref_internal(uint flags, app_loc_t *loc, app_pc addr, size_t sz,
dr_mcontext_t *mc,
/* these 2 are required for MEMREF_USE_VALUES */
int opnum, shadow_combine_t *comb DR_PARAM_INOUT)
{
uint i;
bool allgood = true;
/* report ranges of errors instead of individual bytes */
app_pc bad_addr = NULL, bad_end = NULL;
/* i#580: can't use NULL as sentinel b/c will not report as unaddr */
bool found_bad_addr = false;
uint bad_type = SHADOW_DEFINED; /* i.e., no error */
#ifdef STATISTICS
bool was_special = options.shadowing ?
shadow_get_special(addr, NULL) : false;
bool exception = false;
#endif
umbra_shadow_memory_info_t info;
app_pc stack_base = NULL;
size_t stack_size = 0;
bool handled_push_addr = false;
bool is_write =
/* ADDR is assumed to be for writes only (i#517) */
TESTANY(MEMREF_WRITE | MEMREF_CHECK_ADDRESSABLE, flags) &&
!TEST(MEMREF_IS_READ, flags);
if (options.pattern != 0)
return pattern_handle_mem_ref(loc, addr, sz, mc, is_write);
umbra_shadow_memory_info_init(&info);
ASSERT(options.shadowing, "shadowing disabled");
LOG(3, "memref: %s @"PFX" "PFX" "PIFX" bytes (pre-dword 0x%02x 0x%02x)%s\n",
TEST(MEMREF_WRITE, flags) ? (TEST(MEMREF_PUSHPOP, flags) ? "push" : "write") :
(TEST(MEMREF_PUSHPOP, flags) ? "pop" : "read"), loc_to_print(loc), addr, sz,
shadow_get_dword(&info, addr), shadow_get_dword(&info, addr+4),
was_special ? " (was special)" : "");
ASSERT(addr + sz > addr, "address overflow"); /* no overflow */
/* xref PR 466036: a very large size and a bogus address can take an
* extremely long time here, as we query the stack bounds for every
* single byte: now we cache them but will still be slow.
*/
/* note that gap compiled by cl has an 18MB rep stos (PR 502506) */
ASSERT(TEST(MEMREF_ABORT_AFTER_UNADDR, flags) ||
sz < 32*1024*1024, "suspiciously large size");
ASSERT(!TEST(MEMREF_USE_VALUES, flags) || comb != NULL,
"internal invalid parameters");
/* if no uninit, should only write to shadow mem for push/pop */
ASSERT(options.check_uninitialized ||
!TEST(MEMREF_WRITE, flags) ||
TEST(MEMREF_PUSHPOP, flags), "invalid flags");
#ifdef STATISTICS
if (TEST(MEMREF_WRITE, flags)) {
if (TEST(MEMREF_PUSHPOP, flags))
STATS_INC(push_slowpath);
else
STATS_INC(write_slowpath);
} else {
if (TEST(MEMREF_PUSHPOP, flags))
STATS_INC(pop_slowpath);
else
STATS_INC(read_slowpath);
}
#endif
for (i = 0; i < sz; i++) {
uint shadow = shadow_get_byte(&info, addr + i);
ASSERT(shadow <= 3, "internal error");
if (shadow == SHADOW_UNADDRESSABLE) {
if (TEST(MEMREF_PUSHPOP, flags) &&
(!TEST(MEMREF_WRITE, flags) || BEYOND_TOS_REDZONE_SIZE > 0)) {
ELOG(0, "ERROR: "PFX" popping unaddressable memory: possible Dr. Memory "
"bug\n", loc_to_print(loc));
if (options.pause_at_unaddressable)
wait_for_user("popping unaddressable memory!");
}
/* XXX: stack ranges: right now we assume that a push makes memory
* addressable, but really should check if in stack range
*/
if (TEST(MEMREF_PUSHPOP, flags) && TEST(MEMREF_WRITE, flags)) {
/* Push without stack redzone */
ASSERT(!TEST(MEMREF_MOVS, flags), "internal movs error");
shadow_set_byte(&info, addr + i, TEST(MEMREF_USE_VALUES, flags) ?
comb->dst[memref_idx(flags, i)] : SHADOW_DEFINED);
/* We shouldn't get here for BEYOND_TOS_REDZONE_SIZE > 0 */
if (BEYOND_TOS_REDZONE_SIZE > 0) {
shadow_set_byte(&info, addr + i - BEYOND_TOS_REDZONE_SIZE,
SHADOW_UNDEFINED);
}
} else {
/* We check stack bounds here and cache to avoid
* check_undefined_exceptions having to do it over and over (did
* show up on pc sampling at one point). We assume that
* mcontext contains the app's esp for all callers (including
* our custom clean calls).
*/
bool addr_on_stack = false;
if (stack_base == NULL)
stack_size = allocation_size((app_pc)mc->xsp, &stack_base);
LOG(4, "comparing %08x %08x %08x %08x\n",
addr+i, stack_base, stack_base+stack_size, mc->xsp);
/* We used to also check addr+<xsp but if we mess up our TOS tracking
* we end up not reporting any unaddr, as if addr_on_stack is false
* then we consider the stack itself an "unknown region" (i#1501).
* The risk of going to full stack_size is that our memquery might
* see a guard page or something else up there?
*/
if (addr+i >= stack_base && addr+i < stack_base+stack_size)
addr_on_stack = true;
if (!check_unaddressable_exceptions(is_write, loc,
addr + i, sz, addr_on_stack, mc)) {
bool new_bad = true;
if (found_bad_addr) {
if (bad_type != SHADOW_UNADDRESSABLE) {
ASSERT(bad_type == SHADOW_UNDEFINED,
"internal report error");
report_undefined_read
(loc, bad_addr, bad_end + 1 - bad_addr,
addr, addr + sz, mc);
} else if (bad_end < addr + i - 1 ||
(TEST(MEMREF_ABORT_AFTER_UNADDR, flags) &&
bad_end + 1 - bad_addr >= MEMREF_ABORT_AFTER_SIZE)) {
report_unaddressable_access
(loc, bad_addr, bad_end + 1 - bad_addr,
is_write ? DR_MEMPROT_WRITE : DR_MEMPROT_READ,
addr, addr + sz, mc);
if (TEST(MEMREF_ABORT_AFTER_UNADDR, flags)) {
found_bad_addr = false; /* avoid double-report */
break;
}
} else
new_bad = false;
}
if (new_bad) {
found_bad_addr = true;
bad_type = SHADOW_UNADDRESSABLE;
bad_addr = addr + i;
} /* else extend current bad */
bad_end = addr + i;
/* We follow Memcheck's lead and set to defined to avoid too
* many subsequent errors, for undefined. However, for unaddr,
* we leave it that way (to avoid our own asserts if it's on
* the stack, and to report later but different accesses to it) --
* which works out b/c combine_shadows() will propagate it as
* though it were defined (i#1476).
*/
if (addr_on_stack) {
LOG(2, "unaddressable beyond TOS: leaving unaddressable\n");
} else if (bad_type != SHADOW_UNADDRESSABLE) {
shadow_set_byte(&info, addr+i, SHADOW_DEFINED);
}
allgood = false;
}
#ifdef STATISTICS
else {
exception = true;
LOG(3, "unaddr exception for "PFX"\n", addr+i);
}
#endif
}
} else if (!TESTANY(MEMREF_WRITE | MEMREF_CHECK_ADDRESSABLE, flags)) {
if (shadow == SHADOW_UNDEFINED) {
/* Must check for exceptions even if not reporting, since
* may alter value of shadow */
if (!check_undefined_exceptions(TEST(MEMREF_PUSHPOP, flags),
is_write, loc, addr + i, sz, &shadow,
mc, &i) &&
TEST(MEMREF_CHECK_DEFINEDNESS, flags)) {
bool new_bad = true;
if (found_bad_addr) {
if (bad_type != SHADOW_UNDEFINED) {
ASSERT(bad_type == SHADOW_UNADDRESSABLE,
"internal report error");
report_unaddressable_access
(loc, bad_addr, bad_end + 1 - bad_addr,
is_write ? DR_MEMPROT_WRITE : DR_MEMPROT_READ,
addr, addr + sz, mc);
if (TEST(MEMREF_ABORT_AFTER_UNADDR, flags)) {
found_bad_addr = false; /* avoid double-report */
break;
}
} else if (bad_end < addr + i - 1) {
report_undefined_read
(loc, bad_addr, bad_end + 1 - bad_addr,
addr, addr + sz, mc);
} else
new_bad = false;
}
if (new_bad) {
found_bad_addr = true;
bad_type = SHADOW_UNDEFINED;
bad_addr = addr + i;
} /* else extend current bad */
bad_end = addr + i;
allgood = false;
/* Set to defined to avoid duplicate errors */
if (!options.leave_uninit)
shadow_set_byte(&info, addr+i, SHADOW_DEFINED);
}
#ifdef STATISTICS
else
exception = true;
#endif
} else if (TEST(MEMREF_CHECK_DEFINEDNESS, flags) &&
shadow == SHADOW_DEFINED_BITLEVEL) {
allgood = false;
ASSERT(false, "bitlevel NOT YET IMPLEMENTED");
}
if (TEST(MEMREF_PUSHPOP, flags)) {
/* Pop */
if (BEYOND_TOS_REDZONE_SIZE > 0) {
shadow_set_byte(&info, addr + i, SHADOW_UNDEFINED);
shadow_set_byte(&info, addr + i - BEYOND_TOS_REDZONE_SIZE,
SHADOW_UNADDRESSABLE);
} else
shadow_set_byte(&info, addr + i, SHADOW_UNADDRESSABLE);
}
} else if (!TEST(MEMREF_CHECK_ADDRESSABLE, flags)) {
uint newval;
if (TEST(MEMREF_PUSHPOP, flags) &&
(BEYOND_TOS_REDZONE_SIZE == 0 ||
shadow_get_byte(&info, addr + i - BEYOND_TOS_REDZONE_SIZE) !=
SHADOW_UNADDRESSABLE)) {
if (!handled_push_addr) {
/* only call once: don't want to mark push target as unaddr,
* so each byte will trigger here: avoid extra warnings in logs
*/
handled_push_addr =
handle_push_addressable(loc, addr + i, addr, sz, mc);
}
}
if (TEST(MEMREF_PUSHPOP, flags) && TEST(MEMREF_WRITE, flags) &&
BEYOND_TOS_REDZONE_SIZE > 0) {
/* Push with stack redzone */
ASSERT(!TEST(MEMREF_MOVS, flags), "internal movs error");
shadow_set_byte(&info, addr + i, TEST(MEMREF_USE_VALUES, flags) ?
comb->dst[memref_idx(flags, i)] : SHADOW_DEFINED);
shadow_set_byte(&info, addr + i - BEYOND_TOS_REDZONE_SIZE,
SHADOW_UNDEFINED);
} else {
if (TEST(MEMREF_MOVS, flags)) {
ASSERT(TEST(MEMREF_USE_VALUES, flags), "internal movs error");
ASSERT(memref_idx(flags, i) == i, "internal movs error");
newval = shadow_get_byte(&info, comb->movs_addr + i);
} else {
newval = TEST(MEMREF_USE_VALUES, flags) ?
comb->dst[memref_idx(flags, i)] : SHADOW_DEFINED;
}
if (shadow == SHADOW_DEFINED_BITLEVEL ||
newval == SHADOW_DEFINED_BITLEVEL) {
ASSERT(false, "bitlevel NOT YET IMPLEMENTED");
} else {
if (shadow == newval) {
LOG(4, "store @"PFX" to "PFX" w/ already-same-val "PIFX"\n",
loc_to_print(loc), addr+i, newval);
} else {
LOG(4, "store @"PFX" to "PFX" val="PIFX"\n",
loc_to_print(loc), addr + i, newval);
shadow_set_byte(&info, addr + i, newval);
}
}
}
}
if (!TEST(MEMREF_WRITE, flags) && TEST(MEMREF_USE_VALUES, flags)) {
ASSERT(!TEST(MEMREF_MOVS, flags), "internal movs error");
/* combine with current value */
map_src_to_dst(comb, opnum, memref_idx(flags, i), shadow);
}
if (MAP_4B_TO_1B) {
/* only need to process each 4-byte address region once */
bool is_bad = (bad_end == addr+i);
if (POINTER_OVERFLOW_ON_ADD(addr, 4))
break;
i = ((ptr_uint_t)ALIGN_FORWARD(addr + i + 1, 4) - (ptr_uint_t)addr);
if (is_bad)
bad_end = addr + (i > sz ? sz : i) - 1;
}
}
#ifdef STATISTICS
/* check whether should have hit fast path */
if (sz > 1 && !ALIGNED(addr, sz) && loc->type != APP_LOC_SYSCALL) {
if (sz == 8 && ALIGNED(addr, 4)) {
/* we allow 8-aligned-to-4, but if off block end we'll come here */
if (((ptr_uint_t)addr & 0xffff) == 0xfffc)
STATS_INC(slowpath_8_at_border);
} else
STATS_INC(slowpath_unaligned);
DOLOG(3, {
char buf[MAX_SYMBOL_LEN + MAX_FILENAME_LEN*2/*extra for PRINT_ABS_ADDRESS*/];
size_t sofar = 0;
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
print_address(buf, BUFFER_SIZE_BYTES(buf), &sofar, loc_to_pc(loc),
NULL, true/*for log*/);
NULL_TERMINATE_BUFFER(buf);
LOG(1, "unaligned slow @"PFX" %s "PFX" "PIFX
" bytes (pre 0x%02x 0x%02x)%s %s ",
loc_to_print(loc),
TEST(MEMREF_WRITE, flags) ?
(TEST(MEMREF_PUSHPOP, flags) ? "push" : "write") :
(TEST(MEMREF_PUSHPOP, flags) ? "pop" : "read"),
addr, sz, shadow_get_dword(&info, addr),
shadow_get_dword(&info, addr+4),
was_special ? " (was special)" : "", buf);
disassemble_with_info(dr_get_current_drcontext(), loc_to_pc(loc), f_global,
false/*!show pc*/, true/*show bytes*/);
});
}
if (allgood && !was_special && (sz == 1 || ALIGNED(addr, 4)) && !exception &&
loc->type != APP_LOC_SYSCALL /* not a system call */) {
LOG(3, "\tin slow path for unknown reason @"PFX" "PFX"\n", loc_to_pc(loc), addr);
STATS_INC(slow_instead_of_fast);
}
#endif
if (found_bad_addr) {
if (bad_type == SHADOW_UNDEFINED)
report_undefined_read(loc, bad_addr, bad_end + 1 - bad_addr,
addr, addr + sz, mc);
else {
ASSERT(bad_type == SHADOW_UNADDRESSABLE, "internal report error");
report_unaddressable_access
(loc, bad_addr, bad_end + 1 - bad_addr,
is_write ? DR_MEMPROT_WRITE : DR_MEMPROT_READ, addr, addr + sz, mc);
}
}
return allgood;
}
/* handle_mem_ref checks addressability and if necessary checks
* definedness and adjusts addressability
* returns true if no errors were found
*/
bool
handle_mem_ref(uint flags, app_loc_t *loc, app_pc addr, size_t sz, dr_mcontext_t *mc)
{
ASSERT(!TEST(MEMREF_USE_VALUES, flags), "using values requires shadow_combine_t");
return handle_mem_ref_internal(flags, loc, addr, sz, mc, 0, NULL);
}
#endif /* TOOL_DR_MEMORY */
bool
check_mem_opnd(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;
#ifdef TOOL_DR_MEMORY
int i;
#endif
IF_X86(ASSERT(opc != OP_lea, "lea should not get here"));
#ifdef TOOL_DR_MEMORY
if (options.check_uninitialized) {
/* First check definedness of base+index regs */
for (i = 0; i < opnd_num_regs_used(opnd); i++) {
reg_id_t reg = opnd_get_reg_used(opnd, i);
if (!reg_is_segment(reg) &&
!is_shadow_register_defined(get_shadow_register(reg))) {
/* FIXME: report which bytes within reg via container params? */
report_undefined_read
(loc, (app_pc)(ptr_int_t)reg,
opnd_size_in_bytes(reg_get_size(reg)), NULL, NULL, mc);
/* Set to defined to avoid duplicate errors */
register_shadow_mark_defined(reg, opnd_size_in_bytes(reg_get_size(reg)));
}
}
}
#endif
if (check_mem_opnd_arch(opc, flags, loc, opnd, sz, mc, opnum, comb))
return true;
addr = opnd_compute_address(opnd, mc);
if (opc == OP_pop && !TEST(MEMREF_PUSHPOP, flags) &&
opnd_is_base_disp(opnd) && opnd_uses_reg(opnd, DR_REG_XSP)) {
/* XXX i#1502: we probably want a solution coming from DR, but for
* now we fix this inside DrMem.
*/
if (opnd_get_base(opnd) == DR_REG_XSP)
addr += sizeof(void*);
else if (opnd_get_index(opnd) == DR_REG_XSP) {
addr += opnd_get_scale(opnd)*sizeof(void*);
} else {
tls_util_t *pt = PT_GET(dr_get_current_drcontext());
ELOGPT(0, pt, "ERROR: unhandled pop into base-disp using esp: ");
opnd_disassemble(dr_get_current_drcontext(), opnd, pt->f);
ELOGPT(0, pt, "\n");
ASSERT(false, "unhandled pop into base-disp using esp");
}
}
if (sz == 0)
return true;
#ifdef TOOL_DR_MEMORY
return handle_mem_ref_internal(flags, loc, addr, sz, mc, opnum, comb);
#else
return handle_mem_ref(flags, loc, addr, sz, mc);
#endif
}
bool
check_mem_opnd_nouninit(uint opc, uint flags, app_loc_t *loc, opnd_t opnd, uint sz,
dr_mcontext_t *mc)
{
ASSERT(!TEST(MEMREF_USE_VALUES, flags), "using values requires shadow_combine_t");
return check_mem_opnd(opc, flags, loc, opnd, sz, mc, 0, NULL);
}
bool
check_register_defined(void *drcontext, reg_id_t reg, app_loc_t *loc, size_t sz,
dr_mcontext_t *mc, instr_t *inst)
{
#ifdef TOOL_DR_MEMORY
uint shadow = (reg == REG_EFLAGS) ? get_shadow_eflags() : get_shadow_register(reg);
ASSERT(CHECK_UNINITS(), "shouldn't be called");
if (reg != REG_EFLAGS && sz < opnd_size_in_bytes(reg_get_size(reg))) {
/* only check sub-reg piece */
shadow &= (1 << (sz*2)) - 1;
}
if (!is_shadow_register_defined(shadow)) {
if (!check_undefined_reg_exceptions(drcontext, loc, reg, mc, inst)) {
/* FIXME: report which bytes within reg via container params? */
report_undefined_read(loc, (app_pc)(ptr_int_t)reg, sz, NULL, NULL, mc);
if (reg == REG_EFLAGS) {
/* now reset to avoid complaining on every branch from here on out */
set_shadow_eflags(SHADOW_DWORD_DEFINED);
} else {
/* Set to defined to avoid duplicate errors */
register_shadow_mark_defined(reg, sz);
}
}
}
/* check again, since exception may have marked as defined */
shadow = (reg == REG_EFLAGS) ? get_shadow_eflags() : get_shadow_register(reg);
return is_shadow_register_defined(shadow);
#else
return true;
#endif
}
#ifdef TOOL_DR_MEMORY
/***************************************************************************
* Module bounds
*
* i#412: Mark stack frames as defined in rsaenh.dll to avoid false positives
* during random number generation.
*
* We assume that there are no concurrent rsaenh.dll loads, but there may be
* other threads in the bb event. To make sure they never observe any wide
* intervals such as [0, old_end) during unload, we set the base to POINTER_MAX.
* This means that no matter the order of updates, the reader will always
* observe the old rsaenh.dll interval or an empty interval.
*
* XXX: Between ntdll, msvcr*, msvcp*, and rsaenh, we have a lot of dlls whose
* bounds we cache. Refactor this into a modbounds.c module.
*/
#ifdef WINDOWS
static app_pc rsaenh_base = (app_pc) POINTER_MAX;
static app_pc rsaenh_end = NULL;
#endif /* WINDOWS */
void
slowpath_module_load(void *drcontext, const module_data_t *mod, bool loaded)
{
#ifdef WINDOWS
if (_stricmp("rsaenh.dll", dr_module_preferred_name(mod)) == 0) {
rsaenh_base = mod->start;
rsaenh_end = mod->end;
}
#endif /* WINDOWS */
}
void
slowpath_module_unload(void *drcontext, const module_data_t *mod)
{
#ifdef WINDOWS
if (_stricmp("rsaenh.dll", dr_module_preferred_name(mod)) == 0) {
rsaenh_base = (app_pc) POINTER_MAX;
rsaenh_end = NULL;
}
#endif /* WINDOWS */
}
bool
should_mark_stack_frames_defined(app_pc pc)
{
#ifdef WINDOWS
return (pc >= rsaenh_base && pc < rsaenh_end);
#else
return false;
#endif
}
/***************************************************************************
* Unit tests
*/
#ifdef BUILD_UNIT_TESTS
int
main(int argc, char *argv[])
{
void *drcontext = dr_standalone_init();
slowpath_unit_tests_arch(drcontext);
/* add more tests here */
dr_printf("success\n");
return 0;
}
#endif
#endif /* TOOL_DR_MEMORY */