blob: c8b6c5a009836921448f44ee2ced9038a42c4cd4 [file] [edit]
/* **********************************************************
* Copyright (c) 2011-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.
*/
/***************************************************************************
* STACK ADJUSTMENT HANDLING
*/
#include "dr_api.h"
#include "drmemory.h"
#include "slowpath.h"
#include "spill.h"
#include "fastpath.h"
#include "stack.h"
#include "stack_arch.h"
#include "shadow.h"
#include "heap.h"
#include "alloc.h"
#include "alloc_drmem.h"
#ifdef STATISTICS
uint adjust_esp_executions;
uint adjust_esp_fastpath;
uint stack_swaps;
uint stack_swap_triggers;
uint push_addressable;
uint push_addressable_heap;
uint push_addressable_mmap;
uint zero_loop_aborts_fault;
uint zero_loop_aborts_thresh;
#endif
/***************************************************************************
* STACK SWAP THRESHOLD ADJUSTMENTS
*
* If our -stack_swap_threshold is too big or too small we can easily have
* false positives and/or false negatives so we try to handle unknown
* stack regions and different sizes of stacks and of stack allocations
* and deallocations. Xref PR 525807.
*/
/* number of swap triggers that aren't really swaps before we increase
* the swap threshold
*/
#define MAX_NUMBER_NON_SWAPS 16
/* we use the stack_swap_threshold for other parts of the code like
* callstacks and Ki handling so don't let it get too small:
* though now we use TYPICAL_STACK_MIN_SIZE for Ki and
* a hardcoded constant for callstacks so making smaller:
*/
#define MIN_SWAP_THRESHOLD 2048
void
check_stack_size_vs_threshold(void *drcontext, size_t stack_size)
{
/* It's better to have the threshold too small than too big, since
* over-detecting swaps is much better than under-detecting
* because we have a nice control point for verifying a swap.
*/
if (stack_size < options.stack_swap_threshold) {
/* If the app is near base of stack and swaps not to base of
* adjacent-higher stack but to near its lowest addr then we
* could have a quite small delta so go pretty small.
* check_stack_swap() will bring it back up if there are a
* lot of large allocs.
* I originally based this on the stack size but really
* it only depends on how close adjacent stacks are and how
* near the end of the stack they get. Now I just drop
* to the min and count on check_stack_swap() to bring back up
* if necessary.
*/
size_t new_thresh = MIN_SWAP_THRESHOLD;
if (new_thresh < MIN_SWAP_THRESHOLD)
new_thresh = MIN_SWAP_THRESHOLD;
if (new_thresh < options.stack_swap_threshold)
update_stack_swap_threshold(drcontext, new_thresh);
}
}
/* Retrieves the bounds for the malloc or mmap region containing addr.
* If addr is in a small malloc this routine will fail.
*/
static bool
get_stack_region_bounds(byte *addr, byte **base DR_PARAM_OUT, size_t *size DR_PARAM_OUT)
{
if (is_in_heap_region(addr)) {
return malloc_large_lookup(addr, base, size);
} else {
#if defined(UNIX) && defined(TOOL_DR_MEMORY)
/* see notes in handle_clone(): OS query not good enough */
if (mmap_anon_lookup(addr, base, size))
return true;
#endif
return dr_query_memory(addr, base, size, NULL);
}
}
static bool
check_stack_swap(byte *cur_xsp, byte *new_xsp)
{
/* We check whether this is really a stack swap. If it is we don't need to
* do anything. If it is not we need to handle as an alloc or dealloc to
* avoid false positives and false negatives. We also consider increasing
* the threshold but it's easier to handle when too small than when too
* large. Xref PR 525807.
*/
byte *stack_start;
size_t stack_size;
STATS_INC(stack_swap_triggers);
ASSERT(options.check_stack_bounds, "shouldn't be called");
if (get_stack_region_bounds(cur_xsp, &stack_start, &stack_size)) {
LOG(3, "stack bounds "PFX" "PFX"-"PFX"\n", cur_xsp,
stack_start, stack_start + stack_size);
if (new_xsp >= stack_start && new_xsp < stack_start + stack_size) {
static int num_non_swaps;
LOG(1, "stack adjust "PFX" to "PFX" @"PFX"is really intra-stack adjust\n",
cur_xsp, new_xsp,
/* retaddr for shared slowpath */
get_own_tls_value(esp_spill_slot_base(true)));
/* Reluctantly increase the threshold linearly: better too small */
if (num_non_swaps++ > MAX_NUMBER_NON_SWAPS) {
num_non_swaps = 0;
update_stack_swap_threshold(dr_get_current_drcontext(),
options.stack_swap_threshold + PAGE_SIZE);
}
return false;
}
} else
LOG(1, "WARNING: cannot determine stack bounds for "PFX"\n", cur_xsp);
LOG(1, "stack swap "PFX" => "PFX"\n", cur_xsp, new_xsp);
STATS_INC(stack_swaps);
/* If don't know stack bounds: better to treat as swap, smaller chance
* of false positives and better to have false negs than tons of pos
*/
/* FIXME PR 542004: instead of waiting for push of addr memory and
* handle_push_addressable(), we should mark below new_xsp as unaddr here:
* but are we sure the app is using this as a stack? It's possible it's in
* an optimized loop and it's using xsp as a general-purpose register.
*/
return true;
}
bool
handle_push_addressable(app_loc_t *loc, app_pc addr, app_pc start_addr,
size_t sz, dr_mcontext_t *mc)
{
/* To detect unknown stacks, and attempt to prevent a too-large
* stack swap threshold, when we see a push of addressable memory
* we check whether we should act. Xref PR 525807.
* FIXME PR 542004: check on all esp adjusts for
* addressable memory.
* Note that a too-large stack swap threshold should usually
* happen only for swaps between unknown stacks that were
* allocated together and are similar sizes, so the unknown stack
* handling's adjustment of the threshold is the only mechanism
* here. Swapping from a known stack to a nearby unknown stack of
* a smaller size is not going to be detected: fortunately it's
* rare, and we can tell users to use the -stack_swap_threshold
* for those cases. Risks include false positives and negatives.
*/
bool handled = false;
ASSERT(options.check_stack_bounds, "shouldn't be called");
STATS_INC(push_addressable);
/* We provide an option to disable if our handling isn't working
* and we just want to get some performance and don't care about
* false positives/negatives and have already tuned the stack swap
* threshold.
*/
if (options.check_push) {
byte *stack_start;
size_t stack_size;
#if defined(STATISTICS) || defined(DEBUG)
bool is_heap = false;
#endif
/* we want to do two things:
* 1) mark beyond-TOS as unaddressable
* 2) make sure -stack_swap_threshold is small enough: malloc-based
* stacks are often small (PR 525807). our check_stack_swap()
* handles a too-small threshold.
*/
if (is_in_heap_region(addr)) {
#if defined(STATISTICS) || defined(DEBUG)
is_heap = true;
#endif
LOG(1, "WARNING: "PFX" is treating heap memory "PFX" as a stack!\n",
loc_to_print(loc), addr);
} else {
LOG(1, "WARNING: "PFX" is treating mmap memory "PFX" as a stack!\n",
loc_to_print(loc), addr);
}
if (get_stack_region_bounds(addr, &stack_start, &stack_size)) {
LOG(1, "assuming %s "PFX"-"PFX" is a stack\n",
is_heap ? "large malloc" : "mmap",
stack_start, stack_start + stack_size);
#ifdef STATISTICS
if (is_heap)
STATS_INC(push_addressable_heap);
else
STATS_INC(push_addressable_mmap);
#endif
handled = true;
/* We don't nec. know the stack bounds since some apps malloc
* a struct that has some fields and then a stack, so we do one
* page at a time. Alternatives include (PR 542004):
* - have an API where the app tells us its stack bounds: or if
* constant could just be a runtime option
* - stop if hit a defined shadow value before the page size. can
* only do this on 1st time to rule out stale stack values that
* can happen if swaps include rollbacks (e.g., swap to base like
* DR does w/ dstack, or longjmp from sigaltstack). thus would
* need to remember every stack (and remove from data struct on
* dealloc).
*/
shadow_set_range((addr - PAGE_SIZE < stack_start) ? stack_start :
/* stop at start_addr: don't mark what's being
* pushed as unaddr!
*/
(addr - PAGE_SIZE), start_addr, SHADOW_UNADDRESSABLE);
if (BEYOND_TOS_REDZONE_SIZE > 0) {
size_t redzone_sz = BEYOND_TOS_REDZONE_SIZE;
if (start_addr - BEYOND_TOS_REDZONE_SIZE < stack_start)
redzone_sz = start_addr - stack_start;
shadow_set_range(start_addr - redzone_sz, start_addr, SHADOW_UNDEFINED);
}
check_stack_size_vs_threshold(dr_get_current_drcontext(), stack_size);
} else {
ELOG(0, "ERROR: "PFX" pushing addressable memory: possible Dr. Memory bug\n",
loc_to_print(loc));
if (options.pause_at_unaddressable)
wait_for_user("pushing addressable memory!");
}
}
return handled;
}
/***************************************************************************/
bool
instr_writes_esp(instr_t *inst)
{
int i;
for (i = 0; i < instr_num_dsts(inst); i++) {
opnd_t opnd = instr_get_dst(inst, i);
if (opnd_is_reg(opnd) && opnd_uses_reg(opnd, DR_REG_XSP)) {
/* opnd_uses_reg checks for sub-reg SP */
return true;
}
}
return false;
}
/* PR 447537: adjust_esp's shared fast and slow paths */
byte *shared_esp_slowpath_shadow;
byte *shared_esp_slowpath_defined;
byte *shared_esp_slowpath_zero;
/* Indexed by:
* - sp_action
* - eflags_live
* - esp_adjust_t
* There is no shared fast path for stack zeroing, we always do that inline.
*/
byte *
shared_esp_fastpath[SP_ADJUST_ACTION_FASTPATH_MAX+1][2][ESP_ADJUST_FAST_LAST+1];
/* N.B.: mcontext is not in consistent app state, for efficiency.
* esp is guaranteed to hold app value, though.
*/
void
handle_esp_adjust(esp_adjust_t type, reg_t val/*either relative delta, or absolute*/,
sp_adjust_action_t sp_action)
{
ptr_int_t delta = (ptr_int_t) val;
void *drcontext = dr_get_current_drcontext();
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL; /* only need xsp */
STATS_INC(adjust_esp_executions);
dr_get_mcontext(drcontext, &mc);
if (type == ESP_ADJUST_ABSOLUTE ||
type == ESP_ADJUST_ABSOLUTE_POSTPOP) {
LOG(3, "esp adjust absolute esp="PFX" => "PFX"\n", mc.xsp, val);
delta = val - mc.xsp;
/* Treat as a stack swap (vs ebp->esp, etc.) if a large change */
if ((delta > options.stack_swap_threshold ||
delta < -options.stack_swap_threshold) &&
check_stack_swap((byte *)mc.xsp, (byte *)val)) {
/* Stack swap: nothing to do */
return;
}
} else if (type == ESP_ADJUST_AND) {
ptr_int_t newval = mc.xsp & val;
delta = newval - mc.xsp;
LOG(3, "esp adjust and mask="PIFX" esp="PFX" delta="SZFMT"\n",
val, mc.xsp, delta);
if ((delta > options.stack_swap_threshold ||
delta < -options.stack_swap_threshold) &&
check_stack_swap((byte *)mc.xsp, (byte *)newval)) {
/* Stack swap: nothing to do */
return;
}
} else {
if (type == ESP_ADJUST_NEGATIVE)
delta = -delta;
/* We assume a swap would not happen w/ a relative adjustment */
if (delta > options.stack_swap_threshold ||
delta < -options.stack_swap_threshold) {
LOG(1, "WARNING: relative stack adjustment %d > swap threshold\n",
delta);
}
if (type == ESP_ADJUST_RET_IMMED)
mc.xsp += 4; /* pop of retaddr happens first */
LOG(3, "esp adjust relative esp="PFX" delta=%d\n", mc.xsp, delta);
}
if (delta != 0) {
if (sp_action == SP_ADJUST_ACTION_ZERO) {
if (BEYOND_TOS_REDZONE_SIZE > 0) {
/* FIXME i#1205: zeroing conflicts w/ redzone: NYI */
ASSERT_NOT_IMPLEMENTED();
}
if (delta < 0) {
/* zero out newly allocated stack space to avoid stale
* pointers from misleading our leak scan (PR 520916).
* yes, I realize it may not be perfectly transparent.
*/
memset((app_pc)(mc.xsp + delta), 0, -delta);
}
} else {
app_pc sp = (app_pc)mc.xsp - BEYOND_TOS_REDZONE_SIZE;
shadow_set_range(delta > 0 ? sp : (sp + delta),
delta > 0 ? (sp + delta) : sp,
(delta > 0 ? SHADOW_UNADDRESSABLE :
((sp_action == SP_ADJUST_ACTION_DEFINED) ?
SHADOW_DEFINED : SHADOW_UNDEFINED)));
if (BEYOND_TOS_REDZONE_SIZE > 0) {
sp = (app_pc)mc.xsp + delta;
if (type == ESP_ADJUST_ABSOLUTE_POSTPOP) {
/* Don't undo the pop portion of OP_leave, which already happened
* due to instru ordering.
*/
sp += sizeof(void*);
}
shadow_set_range(sp - BEYOND_TOS_REDZONE_SIZE, sp,
(sp_action == SP_ADJUST_ACTION_DEFINED) ?
SHADOW_DEFINED : SHADOW_UNDEFINED);
}
}
}
}
int
esp_spill_slot_base(sp_adjust_action_t sp_action)
{
/* for whole-bb, we can end up using 1-3 for whole-bb and 4-5 for
* the required ecx+edx for these shared routines
* FIXME: opt: we should we xchg w/ whole-bb in
* instrument_esp_adjust_fastpath() like we do for esp slowpath,
* and thus make use of a global eax: then should have at most
* slot 4 used so can always use 5 here
*/
if (whole_bb_spills_enabled())
return SPILL_SLOT_6;
else if (sp_action != SP_ADJUST_ACTION_ZERO) {
/* we don't have shared_esp_fastpath, and instrument slowpath only
* uses slots 1 and 2
*/
return SPILL_SLOT_3;
} else
return SPILL_SLOT_5;
}
/* N.B.: mcontext is not in consistent app state, for efficiency.
* esp is guaranteed to hold app value, though.
*/
void
handle_esp_adjust_shared_slowpath(reg_t val/*either relative delta, or absolute*/,
sp_adjust_action_t sp_action)
{
/* Rather than force gen code to pass another arg we derive the type */
esp_adjust_t type;
/* Get the return address from this slowpath call */
app_pc pc = (app_pc) get_own_tls_value(esp_spill_slot_base(sp_action));
instr_t inst;
void *drcontext = dr_get_current_drcontext();
/* We decode forward past eflags and register restoration, none of which
* should reference esp. The next instr is the app instr.
*/
instr_init(drcontext, &inst);
while (true) {
pc = decode(drcontext, pc, &inst);
ASSERT(instr_valid(&inst), "unknown suspect instr");
if (instr_writes_esp(&inst)) {
#ifdef X64
/* Handle DR's rip-rel mangling where we'll have 2 steps:
* 48bc706808a8f77f0000 mov rsp,offset varstack!stack1 (00007ff7`a8086870)
* 488b2424 mov rsp,qword ptr [rsp]
*/
ptr_int_t ignored;
if (instr_is_mov_constant(&inst, &ignored)) {
bool skip = false;
instr_t next;
instr_init(drcontext, &next);
decode(drcontext, pc, &next);
if (instr_writes_esp(&next))
skip = true;
instr_free(drcontext, &next);
if (skip) {
instr_reset(drcontext, &inst);
continue;
}
}
#endif
/* ret gets mangled: we'll skip the ecx save and hit the pop */
type = get_esp_adjust_type(&inst, true/*mangled*/);
ASSERT(needs_esp_adjust(&inst, sp_action) ||
type == ESP_ADJUST_RET_IMMED, "found wrong esp-using instr");
handle_esp_adjust(type, val, sp_action);
break;
}
if (instr_is_cti(&inst)) {
ASSERT(false, "somehow missed app esp-adjust instr");
break;
}
instr_reset(drcontext, &inst);
}
instr_free(drcontext, &inst);
/* paranoid: if didn't find the esp-adjust instr just skip the adjust call */
}
static app_pc
generate_shared_esp_slowpath_helper(void *drcontext, instrlist_t *ilist, app_pc pc,
sp_adjust_action_t sp_action)
{
/* PR 447537: adjust_esp's shared_slowpath.
* On entry:
* - scratch1 holds the val arg
* - scratch2 holds the return address
* Need retaddr in persistent storage: slot5 is guaranteed free.
*/
PRE(ilist, NULL, XINST_CREATE_store
(drcontext, spill_slot_opnd(drcontext, esp_spill_slot_base(sp_action)),
opnd_create_reg(ESP_SLOW_SCRATCH2)));
dr_insert_clean_call(drcontext, ilist, NULL,
(void *)handle_esp_adjust_shared_slowpath, false, 2,
opnd_create_reg(ESP_SLOW_SCRATCH1), OPND_CREATE_INT32(sp_action));
PRE(ilist, NULL, XINST_CREATE_jump_mem
(drcontext, spill_slot_opnd(drcontext, esp_spill_slot_base(sp_action))));
pc = instrlist_encode(drcontext, ilist, pc, false);
instrlist_clear(drcontext, ilist);
return pc;
}
app_pc
generate_shared_esp_slowpath(void *drcontext, instrlist_t *ilist, app_pc pc)
{
shared_esp_slowpath_shadow = pc;
pc = generate_shared_esp_slowpath_helper(drcontext, ilist, pc,
SP_ADJUST_ACTION_SHADOW);
shared_esp_slowpath_defined = pc;
pc = generate_shared_esp_slowpath_helper(drcontext, ilist, pc,
SP_ADJUST_ACTION_DEFINED);
shared_esp_slowpath_zero = pc;
pc = generate_shared_esp_slowpath_helper(drcontext, ilist, pc,
SP_ADJUST_ACTION_ZERO);
return pc;
}
app_pc
generate_shared_esp_fastpath(void *drcontext, instrlist_t *ilist, app_pc pc)
{
/* PR 447537: adjust_esp's shared fastpath
* On entry:
* - ecx holds the val arg
* - edx holds the return address
* Uses slot5 and slot6.
* We have multiple versions for {sp_action,eflags,adjust-type}.
*/
int eflags_live;
sp_adjust_action_t sp_action;
esp_adjust_t type;
if (!options.esp_fastpath)
return pc;
ASSERT(ESP_ADJUST_FAST_FIRST == 0, "esp enum error");
/* No shared_esp_fastpath gencode for zeroing. */
for (sp_action = 0; sp_action <= SP_ADJUST_ACTION_FASTPATH_MAX; sp_action++) {
for (eflags_live = 0; eflags_live < 2; eflags_live++) {
for (type = ESP_ADJUST_FAST_FIRST; type <= ESP_ADJUST_FAST_LAST; type++) {
shared_esp_fastpath[sp_action][eflags_live][type] = pc;
generate_shared_esp_fastpath_helper
(drcontext, ilist, eflags_live, sp_action, type);
pc = instrlist_encode(drcontext, ilist, pc, true);
instrlist_clear(drcontext, ilist);
}
}
}
return pc;
}
/* Instrument an esp modification that is not also a read or write
* Returns whether instrumented
*/
bool
instrument_esp_adjust(void *drcontext, instrlist_t *bb, instr_t *inst, bb_info_t *bi,
sp_adjust_action_t sp_action)
{
/* i#677: We don't need -esp_fastpath gencode for insert_zeroing_loop(). */
if (options.esp_fastpath || sp_action == SP_ADJUST_ACTION_ZERO)
return instrument_esp_adjust_fastpath(drcontext, bb, inst, bi, sp_action);
else
return instrument_esp_adjust_slowpath(drcontext, bb, inst, bi, sp_action);
}