blob: cff8ba0a68cc8e90fbc75a42ccaeb286f63e9ce9 [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.
*/
#include "dr_api.h"
#include "drmemory.h"
#include "slowpath.h"
#include "report.h"
#include "shadow.h"
#include "stack.h"
#include "syscall.h"
#include "alloc.h"
#include "heap.h"
#include "redblack.h"
#include "leak.h"
#include "memlayout.h"
#include "alloc_drmem.h"
#ifdef UNIX
# ifdef MACOS
# include <sys/syscall.h>
# define _XOPEN_SOURCE 700 /* required to get POSIX, etc. defines out of ucontext.h */
# define __need_struct_ucontext64 /* seems to be missing from Mac headers */
# elif defined(LINUX)
# include "sysnum_linux.h"
# endif
# include <signal.h>
# include <ucontext.h>
/* 32-bit plain=736, rt=892; 64-bit plain=800, rt=440 */
# define MAX_SIGNAL_FRAME_SIZE 1024
#else
# include "stack.h"
#endif
#include "pattern.h"
/* PR 465174: share allocation site callstacks. We do not rely on
* global malloc synchronization and instead use the
* hashtable_{,un}lock() functions plus reference counts in the
* payloads.
*/
#define ASTACK_TABLE_HASH_BITS 8
static hashtable_t alloc_stack_table;
#ifdef UNIX
/* PR 418629: to determine stack bounds accurately we track anon mmaps */
static rb_tree_t *mmap_tree;
static void *mmap_tree_lock; /* maybe rbtree should support internal synch */
#endif
#ifdef STATISTICS
uint alloc_stack_count;
#endif
#ifdef WINDOWS
app_pc addr_RtlLeaveCrit; /* for i#689 */
#endif
static bool process_exiting;
#ifdef X64
# define Xax Rax
# define Xbx Rbx
# define Xcx Rcx
# define Xdx Rdx
# define Xsp Rsp
# define Xbp Rbp
# define Xsi Rsi
# define Xdi Rdi
#else
# define Xax Eax
# define Xbx Ebx
# define Xcx Ecx
# define Xdx Edx
# define Xsp Esp
# define Xbp Ebp
# define Xsi Esi
# define Xdi Edi
#endif
/***************************************************************************
* DELAYED-FREE LIST
*/
/* A FIFO implemented by an array since we have a fixed size equal
* to options.delay_frees.
* We store the address that should be passed to free() (i.e., it
* includes the redzone).
*/
typedef struct _delay_free_t {
app_pc addr; /* includes redzone */
#ifdef WINDOWS
/* We assume the only flag even at Rtl level is HEAP_NO_SERIALIZE so we only have
* to record the Heap (xref PR 502150).
* This is also used for the block type for _dbg routines.
*/
ptr_int_t auxarg;
#endif
size_t real_size; /* includes redzones */
bool has_redzone;
packed_callstack_t *pcs; /* i#205 for reporting where freed */
} delay_free_t;
/* We need a separate free queue per malloc routine (PR 476805) */
typedef struct _delay_free_info_t {
delay_free_t *delay_free_list;
/* Head of FIFO array */
int delay_free_head;
/* If FIFO is full, equals options.delay_frees; else, equals
* one past the furthest index that has been filled.
*/
int delay_free_fill;
/* We have a max delayed free bytes to avoid running out of memory by
* delaying one or two giant frees
*/
size_t delay_free_bytes;
} delay_free_info_t;
/* We could do per-thread free lists but could strand frees in idle threads;
* plus, already impacting performance plenty so global synch ok.
* We could do per-malloc-routine lock as well: but we stick w/ global
* for simplicity so we can use for delay_free_tree as well.
*/
static void *delay_free_lock;
/* Interval tree for looking up whether an address is on the list (PR 535568).
* Shared across all the queues since should be no overlap.
*/
static rb_tree_t *delay_free_tree;
#define DELAY_FREE_FULL(info) (info->delay_free_fill == options.delay_frees)
#ifdef STATISTICS
uint delayed_free_bytes; /* includes redzones */
#endif
/***************************************************************************/
static void
alloc_callstack_free(void *p);
static byte *
next_defined_ptrsz(byte *start, byte *end);
static byte *
end_of_defined_region(byte *start, byte *end);
static bool
is_register_defined(void *drcontext, reg_id_t reg);
void
alloc_drmem_init(void)
{
alloc_options_t alloc_ops = {0,};
alloc_ops.track_allocs = options.track_allocs;
alloc_ops.track_heap = options.track_heap;
alloc_ops.redzone_size = options.redzone_size;
alloc_ops.size_in_redzone = options.size_in_redzone;
alloc_ops.record_allocs = true; /* used to only need for -count_leaks */
/* shadow mode assumes everything is unaddr and so doesn't need to know
* padding b/c it will naturally be unaddr;
* while pattern mode assumes everything is addr and has to know exact
* bounds to mark unaddr
*/
alloc_ops.get_padded_size = (options.pattern == 0) ? false : true;
alloc_ops.replace_realloc = options.replace_realloc && INSTRUMENT_MEMREFS();
#ifdef WINDOWS
alloc_ops.disable_crtdbg = options.disable_crtdbg && INSTRUMENT_MEMREFS();
#endif
alloc_ops.prefer_msize = options.prefer_msize;
alloc_ops.cache_postcall = options.use_symcache && options.use_symcache_postcall;
/* We can't disable operator interception if !options.check_delete_mismatch
* b/c of msvc debug delete reading headers. For -replace_malloc we can,
* but we still want to replace operators to ensure we get our redzones
* with debug msvc.
*/
alloc_ops.intercept_operators =
(options.replace_malloc ? true : INSTRUMENT_MEMREFS());
alloc_ops.conservative = options.conservative;
/* replace vs wrap */
alloc_ops.replace_malloc = options.replace_malloc;
alloc_ops.external_headers = false; /* i#879: NYI */
alloc_ops.shared_redzones = (options.pattern == 0);
alloc_ops.delay_frees = options.delay_frees;
alloc_ops.delay_frees_maxsz = options.delay_frees_maxsz;
#ifdef WINDOWS
alloc_ops.skip_msvc_importers = options.skip_msvc_importers;
#endif
alloc_ops.global_lock = false; /* we don't need it => can't call malloc_lock() */
alloc_ops.use_symcache = options.use_symcache;
#ifdef WINDOWS
alloc_ops.replace_nosy_allocs = options.replace_nosy_allocs;
#endif
alloc_init(&alloc_ops, sizeof(alloc_ops));
hashtable_init_ex(&alloc_stack_table, ASTACK_TABLE_HASH_BITS, HASH_CUSTOM,
false/*!str_dup*/, false/*using external synch*/,
alloc_callstack_free,
(uint (*)(void*)) packed_callstack_hash,
(bool (*)(void*, void*)) packed_callstack_cmp);
#ifdef UNIX
mmap_tree = rb_tree_create(NULL);
mmap_tree_lock = dr_mutex_create();
#endif
leak_init(!options.leaks_only && options.check_uninitialized,
options.check_leaks_on_destroy,
options.midchunk_new_ok,
options.midchunk_inheritance_ok,
options.midchunk_string_ok,
options.midchunk_size_ok,
options.show_reachable,
IF_WINDOWS_(options.check_encoded_pointers)
next_defined_ptrsz,
end_of_defined_region,
is_register_defined);
memlayout_init();
if (options.delay_frees > 0) {
delay_free_lock = dr_mutex_create();
delay_free_tree = rb_tree_create(NULL);
}
#ifdef WINDOWS /* for i#689 */
ASSERT(ntdll_base != NULL, "init ordering problem");
addr_RtlLeaveCrit = (app_pc)
dr_get_proc_address((module_handle_t)ntdll_base, "RtlLeaveCriticalSection");
#endif
}
void
alloc_drmem_exit(void)
{
process_exiting = true;
leak_exit();
alloc_exit(); /* must be before deleting alloc_stack_table */
hashtable_delete_with_stats(&alloc_stack_table, "alloc stack table");
#ifdef UNIX
rb_tree_destroy(mmap_tree);
dr_mutex_destroy(mmap_tree_lock);
#endif
if (options.delay_frees > 0) {
rb_tree_destroy(delay_free_tree);
dr_mutex_destroy(delay_free_lock);
}
}
/***************************************************************************
* MMAP TABLE
*
* PR 418629: to determine stack bounds accurately we track mmaps
*/
#ifdef UNIX
static void
mmap_tree_add(byte *base, size_t size)
{
dr_mutex_lock(mmap_tree_lock);
rb_node_t *node = rb_insert(mmap_tree, base, size, NULL);
if (node != NULL) {
/* merge overlap */
app_pc merge_base, merge_end;
size_t merge_size;
rb_node_fields(node, &merge_base, &merge_size, NULL);
rb_delete(mmap_tree, node);
merge_end = (base + size > merge_base + merge_size) ?
base + size : merge_base + merge_size;
merge_base = (base < merge_base) ? base : merge_base;
LOG(2, "mmap add: merged "PFX"-"PFX" with existing => "PFX"-"PFX"\n",
base, base+size, merge_base, merge_end);
node = rb_insert(mmap_tree, merge_base, merge_end - merge_base, NULL);
ASSERT(node == NULL, "mmap tree error");
}
dr_mutex_unlock(mmap_tree_lock);
}
static bool
mmap_tree_remove(byte *base, size_t size)
{
dr_mutex_lock(mmap_tree_lock);
bool res = false;
rb_node_t *node = rb_overlaps_node(mmap_tree, base, base+size);
/* we don't know whether anon or not so ok to not be there */
while (node != NULL) {
/* FIXME: should we create a general data struct for interval tree that
* does not merge adjacent, but handles removing or adding subsets/overlaps?
* Getting similar to vm_areas, heap.c => PR 210669 as Extension for clients
* to use too?
*/
app_pc node_base;
size_t node_size;
rb_node_fields(node, &node_base, &node_size, NULL);
rb_delete(mmap_tree, node);
if (node_base < base) {
node = rb_insert(mmap_tree, node_base, base - node_base, NULL);
ASSERT(node == NULL, "mmap tree error");
}
if (node_base + node_size > base + size) {
node = rb_insert(mmap_tree, base + size, (node_base + node_size) -
(base + size), NULL);
ASSERT(node == NULL, "mmap tree error");
}
res = true;
/* handle overlapping multiple regions */
node = rb_overlaps_node(mmap_tree, base, base+size);
}
dr_mutex_unlock(mmap_tree_lock);
return res;
}
bool
mmap_anon_lookup(byte *addr, byte **start DR_PARAM_OUT, size_t *size DR_PARAM_OUT)
{
dr_mutex_lock(mmap_tree_lock);
bool res = false;
rb_node_t *node = rb_in_node(mmap_tree, addr);
if (node != NULL) {
rb_node_fields(node, start, size, NULL);
res = true;
}
dr_mutex_unlock(mmap_tree_lock);
return res;
}
#endif
/***************************************************************************
* EVENTS FOR COMMON/ALLOC.C
*/
void
alloc_callstack_lock(void)
{
hashtable_lock(&alloc_stack_table);
}
void
alloc_callstack_unlock(void)
{
hashtable_unlock(&alloc_stack_table);
}
void
alloc_callstack_free(void *p)
{
/* For -replace_malloc, we need to force-remove here. With wrapping, we rely
* on the malloc hashtable exist to free all references to these callstacks.
* For replacing, there's no reason to iterate the heap just to clean these up.
*/
packed_callstack_destroy(p);
}
void
shared_callstack_free(packed_callstack_t *pcs)
{
uint count;
if (pcs == NULL)
return;
/* We need to synchronize removal from the table w/ additions */
hashtable_lock(&alloc_stack_table);
count = packed_callstack_free(pcs);
LOG(4, "%s: freed pcs "PFX" => refcount %d\n", __FUNCTION__, pcs, count);
ASSERT(count != 0, "refcount should not hit 0 in malloc_table");
if (count == 1) {
/* One ref left, which must be the alloc_stack_table.
* packed_callstack_free will be called by hashtable_remove
* to dec refcount to 0 and do the actual free.
*/
hashtable_remove(&alloc_stack_table, (void *)pcs);
}
hashtable_unlock(&alloc_stack_table);
}
void
client_malloc_data_free(void *data)
{
packed_callstack_t *pcs = (packed_callstack_t *) data;
ASSERT(pcs != NULL || !options.count_leaks, "malloc data must exist");
shared_callstack_free(pcs);
}
/* Be sure to pass the same max_frames for all callstacks that we want
* a comparison to. Currently we use a separate one for malloc vs
* free, but we expect them to never match anyway.
*/
static packed_callstack_t *
get_shared_callstack(packed_callstack_t *existing_data, dr_mcontext_t *mc,
app_pc post_call, uint max_frames)
{
/* XXX i#75: when the app has a ton of mallocs that are quickly freed,
* we spend a lot of time building and tearing down callstacks
* (xref my original setup of not showing leak callstacks by default
* which was for this reason and to save space: but for usability
* it's better to have leak callstacks by default).
* We could just record addresses and not modules, and only on
* module unload or app exit walk the malloc table and fill
* in the module info (and if too many unloads, switch to the
* every-alloc scheme).
*/
packed_callstack_t *pcs;
if (existing_data != NULL)
pcs = (packed_callstack_t *) existing_data;
else {
app_loc_t loc;
pc_to_loc(&loc, post_call);
packed_callstack_record(&pcs, mc, &loc, max_frames);
/* our malloc and free callstacks use post-call as the top frame when wrapping */
if (!options.replace_malloc)
packed_callstack_first_frame_retaddr(pcs);
}
/* XXX i#246: store last malloc callstack outside of hashtable,
* and only add to hashtable on next malloc, so that if freed
* right away we avoid the hashtable lookup+cmp+insert+remove
* costs
*/
/* Synchronization: we no longer rely on malloc_lock(), as we
* don't enable a global lock for -replace_malloc: i#949. Thus we
* must hold the hashtable lock across the lookup and ref count inc.
* shared_callstack_free() grabs the hashtable lock before the final
* remove, ensuring pcs doesn't disappear underneath us.
*/
hashtable_lock(&alloc_stack_table);
pcs = packed_callstack_add_to_table(&alloc_stack_table, pcs
_IF_STATS(&alloc_stack_count));
LOG(4, "%s: created pcs "PFX"\n", __FUNCTION__, pcs);
hashtable_unlock(&alloc_stack_table);
return pcs;
}
void *
client_add_malloc_pre(malloc_info_t *mal, dr_mcontext_t *mc, app_pc post_call)
{
if (!options.malloc_callstacks && !options.count_leaks &&
!options.track_origins_unaddr)
return NULL;
return (void *)
get_shared_callstack((packed_callstack_t *)mal->client_data, mc, post_call,
options.malloc_max_frames);
}
void
client_add_malloc_post(malloc_info_t *mal)
{
/* nothing to do */
}
void
client_remove_malloc_pre(malloc_info_t *mal)
{
/* nothing to do: client_malloc_data_free() does the work */
}
void
client_remove_malloc_post(malloc_info_t *mal)
{
/* nothing to do */
}
void
client_invalid_heap_arg(app_pc pc, app_pc target, dr_mcontext_t *mc, const char *routine,
bool is_free)
{
app_loc_t loc;
char msg[64];
pc_to_loc(&loc, pc);
dr_snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), " to %s", routine);
NULL_TERMINATE_BUFFER(msg);
report_invalid_heap_arg(&loc, target, mc, msg, is_free);
}
void
client_mismatched_heap(app_pc pc, app_pc target, dr_mcontext_t *mc,
const char *alloc_routine, const char *free_routine,
const char *action, void *client_data, bool C_vs_CPP)
{
app_loc_t loc;
char msg[128];
packed_callstack_t *pcs = NULL;
if (C_vs_CPP && !options.check_delete_mismatch)
return;
#ifdef WINDOWS
if (!C_vs_CPP && !options.check_heap_mismatch)
return;
#endif
pc_to_loc(&loc, pc);
/* i#642: We want to report the callstack where it was allocated. But, we want
* no-leak mode to be as fast as possible, so we don't record per-malloc
* callstacks just for this feature. Instead we try to report the allocator and
* free routines involved.
*/
pcs = (packed_callstack_t *) client_data;
dr_snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
": allocated with %s, %s with %s",
alloc_routine, action, free_routine);
NULL_TERMINATE_BUFFER(msg);
report_mismatched_heap(&loc, target, mc, msg, pcs);
}
void
client_handle_malloc(void *drcontext, malloc_info_t *mal, dr_mcontext_t *mc)
{
/* For calloc via malloc, post-malloc marks as undefined, and we should
* see the memset which should then mark as defined.
* But when calloc allocates memory itself, the memset happens
* while the memory is still unaddressable, and those writes are
* suppressed => zeroed should be true and we mark as defined here.
* Plus, for calloc via mmap it's simpler to not have the mmap handler
* mark as defined and to leave as unaddressable and to mark as
* defined here (xref PR 531619).
*/
if (!mal->zeroed && options.track_origins_unaddr &&
!mal->pre_us && mal->has_redzone) {
byte **ptr;
byte *rz_start = mal->base - options.redzone_size;
byte *end = mal->base + mal->request_size;
LOG(2, "set value "PFX" at "PFX"-"PFX" in allocated block\n",
rz_start, mal->base, end);
/* Must set before pattern_handle_malloc, so it is ok to overflow
* to the redzone after the block.
* In pattern mode, the redzone will be overwriten by pattern
* value later in pattern_handle_malloc.
*/
for (ptr = (byte **)mal->base; ptr < (byte **)end; ptr++)
*ptr = rz_start;
}
if (options.shadowing) {
uint val = mal->zeroed ? SHADOW_DEFINED : SHADOW_UNDEFINED;
shadow_set_range(mal->base, mal->base + mal->request_size, val);
}
if (options.pattern != 0) {
pattern_handle_malloc(mal);
}
report_malloc(mal->base, mal->base + mal->request_size,
mal->realloc ? "realloc" : "malloc", mc);
leak_handle_alloc(drcontext, mal->base, mal->request_size);
memlayout_handle_alloc(drcontext, mal->base, mal->request_size);
}
void
client_handle_realloc(void *drcontext, malloc_info_t *old_mal,
malloc_info_t *new_mal, bool for_reuse, dr_mcontext_t *mc)
{
/* XXX i#69: wrapping the app's realloc is racy: old region could
* have been malloc'd again by now! We could synchronize all
* malloc/free calls w/ our own locks. The real routines have
* locks already, so shouldn't be any perf impact. Instead, we
* replace the app's realloc w/ a sequence of equivalent calls.
* This also solves PR 493888: realloc-freed memory not delayed
* with rest of delayed free queue.
*/
ASSERT(!options.replace_realloc || options.leaks_only, "shouldn't come here");
/* Copy over old allocation's shadow values. If new region is bigger, mark
* the extra space at the end as undefined. PR 486049.
*/
if (options.shadowing) {
if (new_mal->request_size > old_mal->request_size) {
if (new_mal->base != old_mal->base)
shadow_copy_range(old_mal->base, new_mal->base, old_mal->request_size);
shadow_set_range(new_mal->base + old_mal->request_size,
new_mal->base + new_mal->request_size,
new_mal->zeroed ? SHADOW_DEFINED : SHADOW_UNDEFINED);
} else {
if (new_mal->base != old_mal->base)
shadow_copy_range(old_mal->base, new_mal->base, new_mal->request_size);
}
/* If the new region is after the old region, overlap or not, compute how
* much of the front of the old region needs to be marked unaddressable
* and do so. This can include the whole old region.
*/
if (new_mal->base > old_mal->base) {
shadow_set_range(old_mal->base,
/* it can overlap */
(new_mal->base < old_mal->base + old_mal->request_size) ?
new_mal->base : old_mal->base + old_mal->request_size,
SHADOW_UNADDRESSABLE);
}
/* If the new region is before the old region, overlap or not, compute how
* much of the end of the old region needs to be marked unaddressable
* and do so. This can include the whole old region. PR 486049.
* Note: this 'if' can't be an else of the above 'if' because there is a
* case where the new region is fully subsumed by the old one.
*/
if (new_mal->base + new_mal->request_size <
old_mal->base + old_mal->request_size) {
app_pc start;
if (new_mal->base + new_mal->request_size < old_mal->base)/* no overlap */
start = old_mal->base;
else { /* old & new regions overlap */
start = new_mal->base + new_mal->request_size;
if (MAP_4B_TO_1B) {
/* XXX i#650: granularity won't let us catch an error
* prior to next 4-aligned word in padding
*/
start = (app_pc) ALIGN_FORWARD(start, 4);
}
}
shadow_set_range(start, old_mal->base + old_mal->request_size,
SHADOW_UNADDRESSABLE);
}
}
if (options.pattern != 0) {
pattern_handle_realloc(old_mal, new_mal, for_reuse);
}
report_malloc(old_mal->base, old_mal->base + old_mal->request_size,
"realloc-old", mc);
report_malloc(new_mal->base, new_mal->base + new_mal->request_size,
"realloc-new", mc);
leak_handle_alloc(drcontext, new_mal->base, new_mal->request_size);
}
void
client_handle_alloc_failure(size_t request_size, app_pc pc, dr_mcontext_t *mc)
{
app_loc_t loc;
pc_to_loc(&loc, pc);
#ifdef LINUX
LOG(1, "heap allocation failed on sz="PIFX"! heap="PFX"-"PFX"\n",
request_size, get_heap_start(), get_brk(false/*want full extent*/));
# ifdef STATISTICS
LOG(1, "\tdelayed=%u\n", delayed_free_bytes);
/* FIXME: if delayed frees really are a problem, should we free
* them all here and re-try the malloc?
*/
# endif
#endif
report_warning(&loc, mc, "heap allocation failed", NULL, 0, false);
}
void
client_handle_realloc_null(app_pc pc, dr_mcontext_t *mc)
{
/* realloc with NULL is guaranteed to be properly handled,
* but we report a warning in case unintentional by the app.
* Windows note: if using libc, at least for msvcr80.dll,
* libc redirects realloc(NULL,) to malloc() so the realloc
* does not show up at the Rtl level that we monitor.
*/
if (options.warn_null_ptr) {
app_loc_t loc;
pc_to_loc(&loc, pc);
report_warning(&loc, mc, "realloc() called with NULL pointer", NULL, 0, false);
}
}
void *
client_add_malloc_routine(app_pc pc)
{
/* We assume no lock is needed on creation */
if (options.delay_frees > 0) {
delay_free_info_t *info = (delay_free_info_t *)
global_alloc(sizeof(*info), HEAPSTAT_MISC);
info->delay_free_list = (delay_free_t *)
global_alloc(options.delay_frees * sizeof(*info->delay_free_list),
HEAPSTAT_MISC);
info->delay_free_head = 0;
info->delay_free_fill = 0;
info->delay_free_bytes = 0;
return (void *) info;
} else {
return NULL;
}
}
void
client_remove_malloc_routine(void *client_data)
{
/* We assume no lock is needed on destroy */
if (options.delay_frees > 0) {
delay_free_info_t *info = (delay_free_info_t *) client_data;
int i;
ASSERT(info != NULL, "invalid param");
for (i = 0; i < info->delay_free_fill; i++) {
if (info->delay_free_list[i].addr != NULL) {
shared_callstack_free(info->delay_free_list[i].pcs);
}
}
global_free(info->delay_free_list,
options.delay_frees * sizeof(*info->delay_free_list), HEAPSTAT_MISC);
global_free(info, sizeof(*info), HEAPSTAT_MISC);
}
}
#ifdef DEBUG
static bool
print_free_tree(rb_node_t *node, void *data)
{
app_pc start;
size_t size;
rb_node_fields(node, &start, &size, NULL);
LOG(1, "\tfree tree entry: "PFX"-"PFX"\n", start, start+size);
return true;
}
#endif
/* Retrieves the fields for the free queue entry at idx (base and
* auxarg), adjusts the delay_free_bytes count, and removes the
* next-to-free entry from the rbtree. Does not change the head
* pointer. Caller must hold lock.
*/
static app_pc
next_to_free(delay_free_info_t *info, int idx _IF_WINDOWS(ptr_int_t *auxarg DR_PARAM_OUT),
const char *reason)
{
app_pc pass_to_free = NULL;
pass_to_free = info->delay_free_list[idx].addr;
#ifdef WINDOWS
if (auxarg != NULL)
*auxarg = info->delay_free_list[idx].auxarg;
#endif
if (pass_to_free != NULL) {
rb_node_t *node = rb_find(delay_free_tree, pass_to_free);
if (node != NULL) {
DOLOG(2, {
byte *start;
size_t size;
rb_node_fields(node, &start, &size, NULL);
LOG(2, "deleting from delay_free_tree "PFX": "PFX"-"PFX"\n",
pass_to_free, start, start + size);
});
rb_delete(delay_free_tree, node);
} else {
DOLOG(1, { rb_iterate(delay_free_tree, print_free_tree, NULL); });
ASSERT(false, "delay_free_tree inconsistent");
}
info->delay_free_bytes -= info->delay_free_list[idx].real_size;
STATS_ADD(delayed_free_bytes,
-(int)info->delay_free_list[idx].real_size);
LOG(2, "%s: freeing "PFX"-"PFX
IF_WINDOWS(" auxarg="PFX) "\n", reason, pass_to_free,
pass_to_free + info->delay_free_list[idx].real_size
_IF_WINDOWS(auxarg == NULL ? 0 : *auxarg));
if (options.pattern != 0) {
/* pattern_handle_real_free only cares about redzone bounds */
malloc_info_t mal = {sizeof(info), pass_to_free,
info->delay_free_list[idx].real_size,
info->delay_free_list[idx].real_size,
false/*!pre_us*/, false/*redzone already in bounds*/,
/* rest 0 */};
pattern_handle_real_free(&mal, true /* delayed */);
}
}
shared_callstack_free(info->delay_free_list[idx].pcs);
info->delay_free_list[idx].pcs = NULL;
return pass_to_free;
}
/* Returns the value to pass to free(). Return "tofree" for no change.
* The auxarg param is DR_PARAM_INOUT so it can be changed as well.
*/
app_pc
client_handle_free(malloc_info_t *mal, byte *tofree, dr_mcontext_t *mc,
app_pc free_routine, void *routine_set_data, bool for_reuse
_IF_WINDOWS(ptr_int_t *auxarg DR_PARAM_INOUT))
{
report_malloc(mal->base, mal->base + mal->request_size, "free", mc);
if (options.shadowing) {
shadow_set_range(mal->base, mal->base + mal->request_size,
SHADOW_UNADDRESSABLE);
}
ASSERT(for_reuse || options.replace_malloc, "wrap free is always for reuse");
if (INSTRUMENT_MEMREFS() && !options.replace_malloc && options.delay_frees > 0) {
/* PR 406762: delay frees to catch more errors. We put
* this to-be-freed memory in a delay FIFO and leave it as
* unaddressable. One the FIFO fills up we substitute the
* oldest free for this one.
* We don't bother to free the FIFO entries at exit time; we
* simply exclude from our leak report.
*/
delay_free_info_t *info = (delay_free_info_t *) routine_set_data;
app_pc pass_to_free = NULL;
#ifdef WINDOWS
ptr_int_t pass_auxarg;
bool full;
#endif
uint idx;
size_t rz_sz = options.redzone_size;
byte *rz_start = mal->base - (mal->has_redzone ? rz_sz : 0);
size_t tot_sz = mal->pad_size + (mal->has_redzone ? rz_sz*2 : 0);
ASSERT(info != NULL, "invalid param");
ASSERT(rz_start == tofree, "tofree should equal start of redzone");
dr_mutex_lock(delay_free_lock);
if (tot_sz > options.delay_frees_maxsz) {
/* we have to free this one, it's too big */
LOG(2, "malloc size %d is larger than max delay %d so freeing immediately\n",
tot_sz, options.delay_frees_maxsz);
dr_mutex_unlock(delay_free_lock);
if (options.pattern != 0)
pattern_handle_real_free(mal, false);
return tofree;
}
/* Store real base and real size: i.e., including redzones (PR 572716) */
info->delay_free_bytes += tot_sz;
if (info->delay_free_bytes > options.delay_frees_maxsz) {
int head_start = info->delay_free_head;
int idx = info->delay_free_head;
LOG(2, "total delayed %d larger than max delay %d\n",
info->delay_free_bytes, options.delay_frees_maxsz);
/* we can't invoke the app's free() routine safely
* so we look for a single free that's bigger than this one:
* if none, we have to free this one.
* XXX: either need call-app-routine support in DR (though
* still have potential deadlock problems since holding lock here)
* or switch to replacing malloc&co.
*/
do {
/* XXX: we could end up doing a linear walk on every free.
* we can also end up always freeing immediately once the
* queue gets full of small objects and the app is freeing large
* objects. not ideal!
*/
if (info->delay_free_list[idx].addr != NULL &&
info->delay_free_list[idx].real_size >= tot_sz) {
LOG(2, "freeing delayed idx=%d "PFX" w/ size=%d (head=%d, fill=%d)\n",
idx, info->delay_free_list[idx].addr,
info->delay_free_list[idx].real_size,
info->delay_free_head, info->delay_free_fill);
pass_to_free = next_to_free(info, idx _IF_WINDOWS(&pass_auxarg),
"exceeded delay_frees_maxsz");
ASSERT(info->delay_free_bytes <= options.delay_frees_maxsz,
"cannot happen");
info->delay_free_list[idx].addr = NULL;
break;
}
idx++;
if (idx >= info->delay_free_fill)
break;
if (idx >= options.delay_frees)
idx = 0;
} while (idx != head_start);
if (pass_to_free == NULL) {
LOG(2, "malloc size %d larger than any entry + over size limit\n",
tot_sz);
info->delay_free_bytes -= tot_sz;
dr_mutex_unlock(delay_free_lock);
if (options.pattern != 0) {
pattern_handle_real_free(mal, false);
}
return tofree;
}
}
LOG(2, "inserting into delay_free_tree (queue idx=%d): "PFX
"-"PFX" %d bytes redzone=%d\n",
DELAY_FREE_FULL(info) ? info->delay_free_head : info->delay_free_fill,
rz_start, rz_start + tot_sz, tot_sz, mal->has_redzone);
if (DELAY_FREE_FULL(info)) {
IF_WINDOWS(full = true;)
if (pass_to_free == NULL) {
pass_to_free = next_to_free(info, info->delay_free_head
_IF_WINDOWS(&pass_auxarg),
"delayed free queue full");
}
idx = info->delay_free_head;
info->delay_free_head++;
if (info->delay_free_head >= options.delay_frees)
info->delay_free_head = 0;
} else {
LOG(2, "delayed free queue not full: delaying %d-th free of "PFX"-"PFX
IF_WINDOWS(" auxarg="PFX) "\n",
info->delay_free_fill, rz_start, rz_start + tot_sz
_IF_WINDOWS((auxarg==NULL) ? 0:*auxarg));
ASSERT(info->delay_free_fill <= options.delay_frees - 1, "internal error");
IF_WINDOWS(full = false;)
idx = info->delay_free_fill;
info->delay_free_fill++;
/* Rather than try to engineer a return, we continue on w/
* pass_to_free as NULL which free() is guaranteed to handle
*/
}
rb_insert(delay_free_tree, rz_start, tot_sz,
(void *)&info->delay_free_list[idx]);
info->delay_free_list[idx].addr = rz_start;
#ifdef WINDOWS
/* should we be doing safe_read() and safe_write()? */
if (auxarg != NULL) {
info->delay_free_list[idx].auxarg = *auxarg;
if (full)
*auxarg = pass_auxarg;
} else {
info->delay_free_list[idx].auxarg = 0;
if (full)
ASSERT(pass_auxarg == 0, "whether using auxarg should be consistent");
}
#endif
info->delay_free_list[idx].real_size = tot_sz;
info->delay_free_list[idx].has_redzone = mal->has_redzone;
if (options.delay_frees_stack) {
info->delay_free_list[idx].pcs =
get_shared_callstack(NULL, mc, free_routine, options.free_max_frames);
} else
info->delay_free_list[idx].pcs = NULL;
STATS_ADD(delayed_free_bytes, (uint)tot_sz);
dr_mutex_unlock(delay_free_lock);
if (options.pattern != 0)
pattern_handle_delayed_free(mal);
return pass_to_free;
}
if (options.pattern != 0) {
if (options.replace_malloc && !for_reuse)
pattern_handle_delayed_free(mal);
else
pattern_handle_real_free(mal, false);
}
return tofree; /* no change */
}
void
client_handle_free_reuse(void *drcontext, malloc_info_t *mal, dr_mcontext_t *mc)
{
if (options.pattern != 0) {
/* for delayed=true (final param), pattern wants bounds w/ redzones */
pattern_handle_real_free(mal, true);
}
}
void
client_new_redzone(app_pc start, size_t size)
{
if (options.pattern != 0)
pattern_new_redzone(start, size);
/* else, shadow already unaddr b/c this is always inside a free (coalesce, split) */
}
void *
client_malloc_data_to_free_list(void *cur_data, dr_mcontext_t *mc, app_pc post_call)
{
packed_callstack_t *pcs = (packed_callstack_t *) cur_data;
ASSERT(options.replace_malloc, "should not be called");
ASSERT(pcs != NULL || !options.count_leaks, "malloc data must exist");
shared_callstack_free(pcs);
/* replace malloc callstack with free callstack */
if (options.delay_frees_stack) {
return (void *)
get_shared_callstack(NULL, mc, post_call, options.free_max_frames);
} else {
/* XXX: could keep the malloc callstack and report that, if labeled properly */
return NULL;
}
}
void *
client_malloc_data_free_split(void *cur_data)
{
packed_callstack_t *pcs = (packed_callstack_t *) cur_data;
ASSERT(options.replace_malloc, "should not be called");
if (pcs != NULL) {
ASSERT(options.delay_frees_stack, "should be NULL");
packed_callstack_add_ref(pcs);
}
return pcs;
}
#ifdef WINDOWS
/* i#264: client needs to clean up any data related to allocs inside this heap */
void
client_handle_heap_destroy(void *drcontext, HANDLE heap, void *client_data)
{
delay_free_info_t *info = (delay_free_info_t *) client_data;
int i, num_removed = 0;
if (options.delay_frees == 0)
return;
ASSERT(info != NULL, "invalid param");
dr_mutex_lock(delay_free_lock);
for (i = 0; i < info->delay_free_fill; i++) {
if (info->delay_free_list[i].addr != NULL &&
info->delay_free_list[i].auxarg == (ptr_int_t)heap) {
/* not worth shifting the array around: just invalidate */
rb_node_t *node = rb_find(delay_free_tree, info->delay_free_list[i].addr);
LOG(3, "removing delayed free "PFX"-"PFX" from destroyed heap "PFX"\n",
info->delay_free_list[i].addr,
info->delay_free_list[i].addr +
info->delay_free_list[i].real_size, heap);
if (node != NULL)
rb_delete(delay_free_tree, node);
else
ASSERT(false, "delay_free_tree inconsistent");
info->delay_free_list[i].addr = NULL;
shared_callstack_free(info->delay_free_list[i].pcs);
info->delay_free_list[i].pcs = NULL;
num_removed++;
}
}
dr_mutex_unlock(delay_free_lock);
LOG(2, "removed %d delayed frees from destroyed heap "PFX"\n",
num_removed, heap);
}
#endif
bool
overlaps_delayed_free(byte *start, byte *end,
byte **free_start DR_PARAM_OUT, /* app base */
byte **free_end DR_PARAM_OUT, /* app request size */
packed_callstack_t **pcs DR_PARAM_OUT,
bool delayed_only)
{
bool res = false;
rb_node_t *node;
malloc_info_t info;
info.struct_size = sizeof(info);
if (options.delay_frees == 0)
return false;
if (options.replace_malloc) {
/* replacement allocator is tracking all delayed frees, not us */
bool found;
if (delayed_only) {
found = alloc_replace_overlaps_delayed_free(start, end, &info);
} else {
found = alloc_replace_overlaps_any_free(start, end, &info);
}
if (found) {
if (free_start != NULL)
*free_start = info.base;
if (free_end != NULL)
*free_end = info.base + info.request_size;
/* There can be a race where this client_data is freed (due to
* the delay-free or freed chunk being re-used), but our alloc_stack_table
* refcount keeps it alive for the process lifetime. So I see no
* reason to hold a lock, and esp not to clone it here.
*/
if (pcs != NULL)
*pcs = (packed_callstack_t *) info.client_data;
} else
found = false;
return found;
}
dr_mutex_lock(delay_free_lock);
LOG(3, "overlaps_delayed_free "PFX"-"PFX"\n", start, end);
DOLOG(3, { rb_iterate(delay_free_tree, print_free_tree, NULL); });
node = rb_overlaps_node(delay_free_tree, start, end);
if (node != NULL) {
app_pc real_base;
size_t size;
delay_free_t *info;
size_t redsz;
res = true;
rb_node_fields(node, &real_base, &size, (void **)&info);
ASSERT(info != NULL, "invalid free tree info");
redsz = (info->has_redzone ? options.redzone_size : 0);
LOG(3, "\toverlap real base: "PFX", size: %d, redzone: %d\n",
real_base, size, redsz);
if (free_start != NULL)
*free_start = real_base + redsz;
/* we didn't store the requested size or padded size so we include
* padding in free_end
*/
if (free_end != NULL)
*free_end = real_base + size - redsz;
if (pcs != NULL) {
if (info->pcs == NULL)
*pcs = NULL;
else
*pcs = packed_callstack_clone(info->pcs);
}
}
dr_mutex_unlock(delay_free_lock);
return res;
}
void
client_handle_mmap(void *drcontext, app_pc base, size_t size, bool anon)
{
#ifdef WINDOWS
if (options.shadowing) {
if (anon) {
/* XXX: we could pass in_heap_routine in as a bool if the overhead
* of the CLS retrieval shows up
*/
if (!alloc_in_heap_routine(drcontext))
shadow_set_range(base, base+size, SHADOW_DEFINED);
else {
/* FIXME PR 575260: should we do what we do on linux and leave
* unaddr? I haven't yet studied what Windows Heap behavior is
* for very large allocations. For now marking entire
* as undefined and ignoring headers.
*/
shadow_set_range(base, base+size, SHADOW_UNDEFINED);
}
} else
mmap_walk(base, size, IF_WINDOWS_(NULL) true/*add*/);
}
#else
if (anon) {
/* Kernel sets to 0 but for malloc we want to treat as undefined
* if a single large malloc chunk or as unaddressable if a new
* malloc arena. For calloc, or for non-alloc, we want defined.
* We assume that post-malloc or post-calloc will take care of
* marking however much of the mmap has been parceled out,
* so we leave the region as unaddressable here, which handles
* both the extra-large headers for single large chunks and
* new arenas gracefully and without races (xref PR 427601, PR
* 531619).
*/
if (!alloc_in_heap_routine(drcontext) && options.shadowing)
shadow_set_range(base, base+size, SHADOW_DEFINED);
/* PR 418629: to determine stack bounds accurately we track mmaps */
mmap_tree_add(base, size);
} else if (options.shadowing) {
/* mapping a file: if an image need to walk sub-regions.
* FIXME: on linux though the sub-regions have their own
* mmaps: wait for those?
*/
mmap_walk(base, size, true/*add*/);
}
#endif
LOG(2, "mmap %s "PFX"-"PFX"\n", anon ? "anon" : "file",
base, base+size);
}
void
client_handle_munmap(app_pc base, size_t size, bool anon)
{
#ifdef WINDOWS
if (options.shadowing) {
if (anon)
shadow_set_range(base, base+size, SHADOW_UNADDRESSABLE);
else
mmap_walk(base, size, IF_WINDOWS_(NULL) false/*remove*/);
}
#else
/* anon not known to common/alloc.c so we see whether in the anon table */
if (mmap_tree_remove(base, size)) {
if (options.shadowing)
shadow_set_range(base, base+size, SHADOW_UNADDRESSABLE);
} else if (options.shadowing)
mmap_walk(base, size, IF_WINDOWS_(NULL) false/*remove*/);
#endif
LOG(2, "munmap %s "PFX"-"PFX"\n", anon ? "anon" : "file",
base, base+size);
}
void
client_handle_munmap_fail(app_pc base, size_t size, bool anon)
{
#ifdef WINDOWS
/* FIXME: need to restore shadow values by storing on pre-syscall */
if (options.shadowing)
mmap_walk(base, size, IF_WINDOWS_(NULL) true/*add*/);
#else
if (anon) {
/* FIXME: we need to store the shadow values in pre so we
* can restore here. We should also work that into our
* race handling model. Xref malloc race handling: but
* that relies on detecting failures ahead of time.
*/
if (options.shadowing)
shadow_set_range(base, base+size, SHADOW_DEFINED);
mmap_tree_add(base, size);
} else if (options.shadowing)
mmap_walk(base, size, true/*add*/);
#endif
}
#ifdef UNIX
void
client_handle_mremap(app_pc old_base, size_t old_size, app_pc new_base, size_t new_size,
bool image)
{
bool shrink = (new_size < old_size);
bool found;
if (options.shadowing) {
shadow_copy_range(old_base, new_base, shrink ? new_size : old_size);
if (shrink) {
shadow_set_range(old_base+new_size, old_base+old_size,
SHADOW_UNADDRESSABLE);
} else {
shadow_set_range(new_base+old_size, new_base+new_size,
image ? SHADOW_DEFINED : SHADOW_UNDEFINED);
}
}
found = mmap_tree_remove(old_base, old_size);
if (found) {
/* an anon region */
mmap_tree_add(new_base, new_size);
}
}
#endif
#ifdef WINDOWS
static void
handle_Ki(void *drcontext, app_pc pc, byte *new_xsp, bool is_cb)
{
/* The kernel has placed some data on the stack. We assume we're
* on the same thread stack. FIXME: check those assumptions by checking
* default stack bounds.
*/
app_pc sp = new_xsp;
TEB *teb = get_TEB();
app_pc base_esp = teb->StackBase;
app_pc stop_esp = NULL;
umbra_shadow_memory_info_t info;
if (!options.shadowing || !options.check_stack_bounds)
return;
umbra_shadow_memory_info_init(&info);
if (sp < base_esp && base_esp - sp < TYPICAL_STACK_MIN_SIZE)
stop_esp = base_esp;
ASSERT(ALIGNED(sp, 4), "stack not aligned");
while ((stop_esp == NULL || sp < stop_esp) &&
/* if not on main stack, we could walk off into an adjacent
* free space: should do mem query!
*/
shadow_get_byte(&info, sp) == SHADOW_UNADDRESSABLE) {
shadow_set_byte(&info, sp, SHADOW_DEFINED);
if (MAP_4B_TO_1B)
sp += 4; /* 4 bytes map to one so skip to next */
else
sp++;
if (sp - new_xsp >= TYPICAL_STACK_MIN_SIZE) {
ASSERT(false, "kernel-placed data on stack too large: error?");
break; /* abort */
}
}
ASSERT(ALIGNED(sp, 4), "stack not aligned");
LOG(2, "Ki routine "PFX": marked stack "PFX"-"PFX" as defined\n",
pc, new_xsp, sp);
if (is_cb) {
/* drmgr already pushed a new context */
cls_drmem_t *cpt_parent = (cls_drmem_t *)
drmgr_get_parent_cls_field(drcontext, cls_idx_drmem);
ASSERT(cpt_parent != NULL, "drmgr should have pushed context already");
if (cpt_parent == NULL)
return; /* don't crash in release build */
LOG(3, "cb: cpt_parent is "PFX", cpt is "PFX"\n",
cpt_parent, drmgr_get_cls_field(drcontext, cls_idx_drmem));
cpt_parent->pre_callback_esp = sp;
}
}
static void
handle_callback(void *drcontext)
{
LOG(2, "Entering windows callback handler\n");
syscall_handle_callback(drcontext);
}
static void
handle_cbret(void *drcontext, const dr_kernel_xfer_info_t *xfer_info)
{
umbra_shadow_memory_info_t info;
byte *sp = (byte *) xfer_info->source_mcontext->xsp;
cls_drmem_t *cpt_parent = (cls_drmem_t *)
drmgr_get_parent_cls_field(drcontext, cls_idx_drmem);
if (cpt_parent == NULL) /* DR took over in middle of callback */
return;
if (!options.shadowing)
return;
syscall_handle_cbret(drcontext);
if (!options.check_stack_bounds)
return;
ASSERT(cpt_parent->pre_callback_esp == (byte *)xfer_info->target_xsp,
"cb xsp mismatch");
LOG(2, "cbret: marking stack "PFX"-"PFX" as unaddressable\n",
sp, cpt_parent->pre_callback_esp);
LOG(3, "cbret: cpt_parent is "PFX", cpt is "PFX"\n",
cpt_parent, drmgr_get_cls_field(drcontext, cls_idx_drmem));
umbra_shadow_memory_info_init(&info);
for (; sp < cpt_parent->pre_callback_esp; sp++)
shadow_set_byte(&info, sp, SHADOW_UNADDRESSABLE);
}
static void
handle_exception(void *drcontext)
{
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
cpt->heap_critsec = NULL;
}
static void
handle_continue(void *drcontext)
{
/* We rely on this running *before* alloc.c's so is_in_seh() is correct. */
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
if (is_in_seh(drcontext)) {
cpt->heap_critsec = NULL;
} /* else it was an APC */
}
#endif /* WINDOWS */
void
client_stack_alloc(byte *start, byte *end, bool defined)
{
if (options.shadowing &&
(options.check_uninitialized || options.check_stack_bounds)) {
shadow_set_range(start, end, defined ? SHADOW_DEFINED : SHADOW_UNDEFINED);
if (BEYOND_TOS_REDZONE_SIZE > 0) {
shadow_set_range(start - BEYOND_TOS_REDZONE_SIZE,
end - BEYOND_TOS_REDZONE_SIZE, SHADOW_UNDEFINED);
}
}
}
void
client_stack_dealloc(byte *start, byte *end)
{
if (options.shadowing &&
(options.check_uninitialized || options.check_stack_bounds)) {
if (BEYOND_TOS_REDZONE_SIZE > 0) {
shadow_set_range(start, end, SHADOW_UNDEFINED);
shadow_set_range(start - BEYOND_TOS_REDZONE_SIZE,
end - BEYOND_TOS_REDZONE_SIZE, SHADOW_UNADDRESSABLE);
} else
shadow_set_range(start, end, SHADOW_UNADDRESSABLE);
}
if (options.shadowing && options.check_uninitialized)
register_shadow_set_ptrsz(DR_REG_PTR_RETURN, SHADOW_PTRSZ_DEFINED);
}
/* Non-interpreted code about to write to app-visible memory */
bool
client_write_memory(byte *start, size_t size, dr_mcontext_t *mc)
{
if (options.shadowing) {
app_loc_t loc;
pc_to_loc(&loc, mc->pc);
return handle_mem_ref(MEMREF_WRITE, &loc, start, size, mc);
}
return true;
}
/* Non-interpreted code about to read to app-visible memory */
bool
client_read_memory(byte *start, size_t size, dr_mcontext_t *mc)
{
if (options.shadowing) {
app_loc_t loc;
pc_to_loc(&loc, mc->pc);
return handle_mem_ref(MEMREF_CHECK_ADDRESSABLE|MEMREF_IS_READ, &loc,
start, size, mc);
}
return true;
}
#ifdef DEBUG
void
client_print_callstack(void *drcontext, dr_mcontext_t *mc, app_pc pc)
{
print_callstack_to_file(drcontext, mc, pc, INVALID_FILE/*use pt->f*/,
options.callstack_max_frames);
}
#endif
/***************************************************************************
* SIGNALS AND SYSTEM CALLS
*/
#ifdef WINDOWS
static void
adjust_stack_to_context(dr_mcontext_t *mc, reg_t cxt_xsp _IF_DEBUG(const char *prefix))
{
if (cxt_xsp < mc->xsp) {
if (mc->xsp - cxt_xsp < options.stack_swap_threshold) {
shadow_set_range((byte *) cxt_xsp, (byte *) mc->xsp, SHADOW_UNDEFINED);
LOG(2, "%s: marked stack "PFX"-"PFX" as undefined\n",
prefix, cxt_xsp, mc->xsp);
} else
LOG(2, "%s: assuming stack swap "PFX" => "PFX"\n", prefix, mc->xsp, cxt_xsp);
} else if (cxt_xsp - mc->xsp < options.stack_swap_threshold) {
shadow_set_range((byte *) mc->xsp, (byte *) cxt_xsp, SHADOW_UNADDRESSABLE);
LOG(2, "%s: marked stack "PFX"-"PFX" as unaddressable\n",
prefix, mc->xsp, cxt_xsp);
} else
LOG(2, "%s: assuming stack swap "PFX" => "PFX"\n", prefix, mc->xsp, cxt_xsp);
}
#endif
void
client_pre_syscall(void *drcontext, int sysnum)
{
#ifdef WINDOWS
DWORD cxt_flags;
reg_t cxt_xsp;
#endif
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL|DR_MC_INTEGER; /* don't need xmm */
if (!options.shadowing)
return;
dr_get_mcontext(drcontext, &mc);
#ifdef WINDOWS
if (!options.check_stack_bounds)
return;
if (sysnum == sysnum_continue) {
/* XXX: we could move this to the kernel xfer event */
CONTEXT *cxt = (CONTEXT *) dr_syscall_get_param(drcontext, 0);
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
if (cxt != NULL &&
safe_read(&cxt->ContextFlags, sizeof(cxt_flags), &cxt_flags) &&
safe_read(&cxt->Xsp, sizeof(cxt_xsp), &cxt_xsp)) {
/* FIXME: what if the syscall fails? */
if (TESTALL(CONTEXT_CONTROL/*2 bits so ALL*/, cxt_flags)) {
register_shadow_set_ptrsz(REG_XSP,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xsp));
# ifndef X64
register_shadow_set_ptrsz(REG_XBP,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xbp));
# endif
}
if (TESTALL(CONTEXT_INTEGER/*2 bits so ALL*/, cxt_flags)) {
register_shadow_set_ptrsz(REG_XAX,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xax));
register_shadow_set_ptrsz(REG_XCX,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xcx));
register_shadow_set_ptrsz(REG_XDX,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xdx));
register_shadow_set_ptrsz(REG_XBX,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xbx));
# ifdef X64
register_shadow_set_ptrsz(REG_XBP,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xbp));
# endif
register_shadow_set_ptrsz(REG_XSI,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xsi));
register_shadow_set_ptrsz(REG_XDI,
shadow_get_ptrsz(&info, (app_pc)&cxt->Xdi));
}
/* Mark stack AFTER reading cxt since cxt may be on stack! */
if (TESTALL(CONTEXT_CONTROL/*2 bits so ALL*/, cxt_flags)) {
adjust_stack_to_context(&mc, cxt_xsp _IF_DEBUG("NtContinue"));
}
} else {
WARN("WARNING: NtContinue: failed to adjust stack\n");
}
} else if (sysnum == sysnum_setcontext) {
/* FIXME PR 575434: we need to know whether the thread is in this
* process or not, and then get its current context so we can
* change the esp between old and new values and set the register
* shadow values.
*/
ASSERT(false, "NtSetContextThread NYI");
} else if (sysnum == sysnum_RaiseException) {
/* i#87: the kernel will place the args to KiUserExceptionDispatcher on
* the stack at the stack pointer in the CONTEXT, not the current stack
* pointer.
*/
CONTEXT *cxt = (CONTEXT *) dr_syscall_get_param(drcontext, 1);
if (cxt != NULL &&
safe_read(&cxt->ContextFlags, sizeof(cxt_flags), &cxt_flags) &&
TESTALL(CONTEXT_CONTROL/*2 bits so ALL*/, cxt_flags) &&
safe_read(&cxt->Xsp, sizeof(cxt_xsp), &cxt_xsp)) {
/* FIXME: what if the syscall fails? */
adjust_stack_to_context(&mc, cxt_xsp _IF_DEBUG("NtRaiseException"));
} else {
WARN("WARNING: NtRaiseException: failed to adjust stack\n");
}
}
#else
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
if (sysnum == SYS_sigaltstack) {
/* PR 406333: linux signal delivery */
stack_t stk;
cpt->prev_sigaltstack = cpt->sigaltstack;
cpt->prev_sigaltsize = cpt->sigaltsize;
if (safe_read((void *)syscall_get_param(drcontext, 0), sizeof(stk), &stk)) {
if (stk.ss_flags == SS_DISABLE) {
cpt->sigaltstack = NULL;
cpt->sigaltsize = 0;
/* Mark the old stack as addressable in case used as data now? */
} else {
/* We want the base (== highest addr) */
cpt->sigaltstack = ((byte *) stk.ss_sp) + stk.ss_size;
cpt->sigaltsize = stk.ss_size;
ASSERT((cpt->sigaltstack < (byte*)mc.xsp ||
(ptr_int_t)cpt->sigaltstack - cpt->sigaltsize - mc.xsp >
options.stack_swap_threshold) &&
(cpt->sigaltstack > (byte*)mc.xsp ||
mc.xsp - ((ptr_int_t)cpt->sigaltstack + cpt->sigaltsize) >
options.stack_swap_threshold),
"sigaltstack within swap threshold of esp");
/* We assume this memory will not be used for any other data */
LOG(2, "marking sigaltstack "PFX"-"PFX" unaddressable\n",
stk.ss_sp, cpt->sigaltstack);
shadow_set_range((app_pc)stk.ss_sp, cpt->sigaltstack,
options.check_stack_bounds ? SHADOW_DEFINED :
SHADOW_UNADDRESSABLE);
}
LOG(2, "new sigaltstack "PFX"\n", cpt->sigaltstack);
} else {
LOG(2, "WARNING: can't read sigaltstack param "PFX"\n",
syscall_get_param(drcontext, 0));
}
}
#endif /* WINDOWS */
}
void
client_post_syscall(void *drcontext, int sysnum)
{
#ifdef UNIX
ptr_int_t result = dr_syscall_get_result(drcontext);
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
if (!options.shadowing)
return;
if (sysnum == SYS_sigaltstack) {
if (result != 0) {
/* We can't query the OS since DR is hiding the real sigaltstack,
* so we record the prev value
*/
cpt->sigaltstack = cpt->prev_sigaltstack;
cpt->sigaltsize = cpt->prev_sigaltsize;
LOG(2, "sigaltstack failed, reverting to "PFX"\n", cpt->sigaltstack);
}
}
#endif
}
#ifdef UNIX
dr_signal_action_t
event_signal_alloc(void *drcontext, dr_siginfo_t *info)
{
if (options.shadowing) {
/* no longer trying to store the interrupted xsp b/c it
* gets too complicated (xref PR 620746)
*/
LOG(2, "signal interrupted app at xsp="PFX"\n", info->mcontext->xsp);
}
return DR_SIGNAL_DELIVER;
}
static void
handle_signal_delivery(void *drcontext, reg_t dst_xsp)
{
/* PR 406333: linux signal delivery.
* Need to know extent of frame: could record xsp in signal event,
* and record SYS_sigaltstack.
* However, we can't tie together the signal event and handler
* invocation, b/c of ignored and default actions, nested signals,
* pseudo-nested signals (new signals coming in after the frame
* copy but before executing the handler), etc. (xref PR 620746).
* The most robust solution would be to have DR provide an event
* "adjust_app_stack_for_signal" and call it whenever copying a
* frame to the app stack or processing a sigreturn, providing us
* w/ the old and new xsp: but that doesn't fit w/ the rest of the
* DR API, so we're walking unaddr until we hit either addr,
* the max frame size, or the top of the alt stack.
* There is a pathological case where the app gets a signal while
* at the very base of a stack and we could walk off onto
* adjacent memory: we ignore that.
*/
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
byte *sp, *stop;
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
ASSERT(options.shadowing, "shadowing disabled");
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL; /* only need xsp */
dr_get_mcontext(drcontext, &mc);
sp = (byte *)mc.xsp;
stop = sp + MAX_SIGNAL_FRAME_SIZE;
if (cpt->sigaltstack != NULL &&
cpt->sigaltstack > (app_pc) mc.xsp &&
(size_t)(cpt->sigaltstack - (app_pc) mc.xsp) < cpt->sigaltsize &&
stop > cpt->sigaltstack)
stop = cpt->sigaltstack;
/* XXX: we could probably assume stack alignment at start of handler */
while (sp < stop && shadow_get_byte(&info, sp) == SHADOW_UNADDRESSABLE) {
/* Assume whole frame is defined (else would need DR to identify
* which parts are padding).
*/
shadow_set_byte(&info, sp, SHADOW_DEFINED);
sp++;
}
if (BEYOND_TOS_REDZONE_SIZE > 0) {
shadow_set_range((byte *)mc.xsp - BEYOND_TOS_REDZONE_SIZE, (byte *)mc.xsp,
SHADOW_UNDEFINED);
}
LOG(2, "signal handler: marked new frame defined "PFX"-"PFX"\n", mc.xsp, sp);
}
static void
handle_signal_return(void *drcontext, const dr_mcontext_t *src_mc, byte *new_sp)
{
ASSERT(options.shadowing && options.check_stack_bounds, "incorrectly called");
ASSERT(src_mc != NULL && TEST(DR_MC_CONTROL, src_mc->flags),
"src_mc should always exist for sigreturn");
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
byte *sp = (byte *)src_mc->xsp;
byte *unaddr_top = NULL;
if (new_sp > sp && (size_t)(new_sp - sp) < MAX_SIGNAL_FRAME_SIZE) {
unaddr_top = new_sp;
} else if (cpt->sigaltstack != NULL && cpt->sigaltstack > sp &&
(size_t)(cpt->sigaltstack - sp) < cpt->sigaltsize) {
/* transitioning from sigaltstack to regular stack */
unaddr_top = cpt->sigaltstack;
} else {
LOG(2, "at sigreturn but new sp "PFX" irregular vs "PFX"\n", new_sp, sp);
}
if (unaddr_top != NULL) {
LOG(2, "at sigreturn: marking frame "PFX"-"PFX" unaddressable\n",
sp, unaddr_top);
shadow_set_range(sp, unaddr_top, SHADOW_UNADDRESSABLE);
}
}
#endif /* UNIX */
void
event_kernel_xfer(void *drcontext, const dr_kernel_xfer_info_t *info)
{
#ifdef UNIX
if (!options.shadowing || !options.check_stack_bounds)
return;
if (info->type == DR_XFER_SIGNAL_DELIVERY)
handle_signal_delivery(drcontext, info->target_xsp);
else if (info->type == DR_XFER_SIGNAL_RETURN)
handle_signal_return(drcontext, info->source_mcontext, (byte *)info->target_xsp);
#else
if (info->type == DR_XFER_CALLBACK_DISPATCHER) {
handle_Ki(drcontext, info->target_pc, (byte*)info->target_xsp, true);
handle_callback(drcontext);
}
else if (info->type == DR_XFER_CALLBACK_RETURN)
handle_cbret(drcontext, info);
else if (info->type == DR_XFER_APC_DISPATCHER ||
info->type == DR_XFER_RAISE_DISPATCHER)
handle_Ki(drcontext, info->target_pc, (byte*)info->target_xsp, false);
else if (info->type == DR_XFER_EXCEPTION_DISPATCHER) {
handle_exception(drcontext);
handle_Ki(drcontext, info->target_pc, (byte*)info->target_xsp, false);
}
else if (info->type == DR_XFER_CONTINUE)
handle_continue(drcontext);
#endif
}
/***************************************************************************
* ADDRESSABILITY
*/
#ifdef X86 /* replacement should avoid needing to port this to ARM */
static bool
is_rawmemchr_pattern(void *drcontext, bool write, app_pc pc, app_pc next_pc,
app_pc addr, uint sz, instr_t *inst, bool *now_addressable DR_PARAM_OUT)
{
/* PR 406535: glibc's rawmemchr does some bit tricks that can end
* up using unaddressable or undefined values. The erroneous load
* is one of these:
* +0 8b 08 mov (%eax) -> %ecx
* +0 8b 48 04 mov 0x4(%eax),%ecx
* +0 8b 48 08 mov 0x08(%eax) -> %ecx
* +0 8b 48 0c mov 0x0c(%eax) -> %ecx
* followed by the magic constant:
* +2 ba ff fe fe fe mov $0xfefefeff -> %edx
* followed by an add, or an xor and then an add, and then a jcc:
* +7 01 ca add %ecx %edx -> %edx
* +9 73 59 jnb $0x009041c7
* since the particular registers and mem sources vary, we don't do
* raw bit comparisons and instead do high-level operand comparisons.
* in fact, we try to also match very similar patterns in strcat,
* strlen, strrchr, and memchr.
*
* strchr and strchrnul have an xor in between the load and the magic
* constant which we also match:
* +0 8b 08 mov (%eax),%ecx
* +2 31 d1 xor %edx,%ecx
* +4 bf ff fe fe fe mov $0xfefefeff,%edi
*
* on Windows we have __from_strstr_to_strchr in intel/strchr.asm:
* +11 8b 0a mov (%edx) -> %ecx
* +13 bf ff fe fe 7e mov $0x7efefeff -> %edi
*
* Newer glibc has this in an internal routine:
* 0xf764542f: mov (%eax),%ecx <===== unaddr
* 0xf7645431: add $0x4,%eax
* 0xf7645434: sub %ecx,%edx
* 0xf7645436: add $0xfefefeff,%ecx
*
* xref PR 485131: propagate partial-unaddr on loads? but would still
* complain on the jnb.
*
* FIXME: share code w/ check_undefined_reg_exceptions() in slowpath.c.
*/
instr_t next;
app_pc dpc = next_pc;
bool match = false;
instr_init(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return false;
/* We want to only allow the end of the search to be suppressed, to
* avoid suppressing a real positive, so only unaligned addresses.
*/
if (!ALIGNED(addr, 4) &&
instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_reg(instr_get_dst(inst, 0)) &&
opnd_get_size(instr_get_dst(inst, 0)) == OPSZ_PTR) {
if (instr_valid(&next) &&
instr_get_opcode(&next) == OP_xor &&
opnd_is_reg(instr_get_src(&next, 0)) &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_size(instr_get_dst(&next, 0)) == OPSZ_PTR) {
/* Skip the strchr/strchnul xor */
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
goto is_rawmemchr_pattern_done;
}
if (instr_valid(&next) &&
instr_get_opcode(&next) == OP_add &&
opnd_is_immed_int(instr_get_src(&next, 0)) &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_size(instr_get_dst(&next, 0)) == OPSZ_PTR) {
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
goto is_rawmemchr_pattern_done;
if (instr_valid(&next) &&
instr_get_opcode(&next) == OP_sub &&
opnd_is_reg(instr_get_src(&next, 0)) &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_size(instr_get_dst(&next, 0)) == OPSZ_PTR) {
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
goto is_rawmemchr_pattern_done;
}
}
if (instr_valid(&next) &&
(instr_get_opcode(&next) == OP_mov_imm ||
instr_get_opcode(&next) == OP_add) &&
(opnd_get_immed_int(instr_get_src(&next, 0)) == 0xfefefeff ||
opnd_get_immed_int(instr_get_src(&next, 0)) == 0x7efefeff) &&
opnd_is_reg(instr_get_dst(&next, 0))) {
STATS_INC(strmem_unaddr_exception);
*now_addressable = false;
match = true;
}
}
is_rawmemchr_pattern_done:
instr_free(drcontext, &next);
return match;
}
#endif /* X86 */
bool
is_alloca_pattern(void *drcontext, app_pc pc, app_pc next_pc, instr_t *inst,
bool *now_addressable DR_PARAM_OUT)
{
/* Check for alloca probes to trigger guard pages.
* So far we've seen just a handful of different sequences:
UNADDRESSABLE ACCESS: pc @0x0040db67 reading 0x0012ef80
UNADDRESSABLE ACCESS: pc @0x0040db67 reading 0x0012ef81
UNADDRESSABLE ACCESS: pc @0x0040db67 reading 0x0012ef82
UNADDRESSABLE ACCESS: pc @0x0040db67 reading 0x0012ef83
UNADDRESSABLE ACCESS: pc @0x0040db74 reading 0x0012ed48
UNADDRESSABLE ACCESS: pc @0x0040db74 reading 0x0012ed49
UNADDRESSABLE ACCESS: pc @0x0040db74 reading 0x0012ed4a
UNADDRESSABLE ACCESS: pc @0x0040db74 reading 0x0012ed4b
hello!_alloca_probe+0xc [intel\chkstk.asm @ 76]:
76 0040db5c 81e900100000 sub ecx,0x1000
77 0040db62 2d00100000 sub eax,0x1000
79 0040db67 8501 test [ecx],eax
81 0040db69 3d00100000 cmp eax,0x1000
82 0040db6e 73ec jnb hello!_alloca_probe+0xc (0040db5c)
hello!_alloca_probe+0x20 [intel\chkstk.asm @ 85]:
85 0040db70 2bc8 sub ecx,eax
86 0040db72 8bc4 mov eax,esp
88 0040db74 8501 test [ecx],eax
90 0040db76 8be1 mov esp,ecx
92 0040db78 8b08 mov ecx,[eax]
93 0040db7a 8b4004 mov eax,[eax+0x4]
95 0040db7d 50 push eax
97 0040db7e c3 ret
ntdll!_alloca_probe+0x15:
7d61042d f7d8 neg eax
7d61042f 03c4 add eax,esp
7d610431 83c004 add eax,0x4
7d610434 8500 test [eax],eax
7d610436 94 xchg eax,esp
7d610437 8b00 mov eax,[eax]
7d610439 50 push eax
7d61043a c3 ret
in this instance the probe goes 4 bytes into the stack instead
of extending it, and then after shortening esp reads beyond TOS
to move the retaddr to the new TOS!
memref: read @0x7d610434 0x0007e2f0 0x4
esp adjust esp=0x0007e2ec => 0x0007e2f0
set range 0x0007e2ec-0x0007e2f0 => 0x0
memref: read @0x7d610437 0x0007e2ec 0x4
UNADDRESSABLE ACCESS: pc @0x7d610437 reading 0x0007e2ec
though this also occurs as ntdll!_chkstk where the probe does go beyond TOS:
depends on value of eax == amount checking/probing by
cygwin1!alloca:
610fc670 51 push ecx
610fc671 89e1 mov ecx,esp
610fc673 83c108 add ecx,0x8
610fc676 3d00100000 cmp eax,0x1000
610fc67b 7210 jb cygwin1!alloca+0x1d (610fc68d)
610fc67d 81e900100000 sub ecx,0x1000
610fc683 830900 or dword ptr [ecx],0x0
610fc686 2d00100000 sub eax,0x1000
610fc68b ebe9 jmp cygwin1!alloca+0x6 (610fc676)
610fc68d 29c1 sub ecx,eax
610fc68f 830900 or dword ptr [ecx],0x0
610fc692 89e0 mov eax,esp
610fc694 89cc mov esp,ecx
610fc696 8b08 mov ecx,[eax]
610fc698 8b4004 mov eax,[eax+0x4]
610fc69b ffe0 jmp eax
gap.exe:
00444bf2 2d00100000 sub eax,0x1000
00444bf7 8500 test [eax],eax
00444bf9 ebe9 jmp gap+0x44be4 (00444be4)
00444bfb cc int 3
0:000> U 00444be4
00444be4 3bc8 cmp ecx,eax
00444be6 720a jb gap+0x44bf2 (00444bf2)
Compaq Visual Fortran alloca (i#449) is very similar to the
_alloca_probe sequences above, but uses edi or sometimes esi:
0040108a 0507000000 add eax,0x7
0040108f 25f8ffffff and eax,0xfffffff8
00401094 3d00100000 cmp eax,0x1000
00401099 7e14 jle A+0x10af (004010af)
0040109b 81ef00100000 sub edi,0x1000
004010a1 2d00100000 sub eax,0x1000
004010a6 8507 test [edi],eax
004010a8 3d00100000 cmp eax,0x1000
004010ad 7fec jg A+0x109b (0040109b)
004010af 2bf8 sub edi,eax
004010b1 8507 test [edi],eax
004010b3 8be7 mov esp,edi
We also see handle!_chkstk:
00a6cc52 2d00100000 sub eax,0x1000
00a6cc57 8500 test [eax],eax
00a6cc59 ebe9 jmp handle!_chkstk+0x14 (00a6cc44)
the pattern is the same as gap.exe.
x64:
varstack!__chkstk+0x30 [f:\dd\vctools\crt\crtw32\startup\amd64\chkstk.asm @ 108]:
00007ff7`a8071710 4d8d9b00f0ffff lea r11,[r11-1000h]
00007ff7`a8071717 41c60300 mov byte ptr [r11],0
00007ff7`a807171b 4d3bd3 cmp r10,r11
*/
/* For now we do an exact pattern match but of course this
* won't generalize well for other versions of alloca: OTOH we
* don't want any false negatives.
*/
#ifdef X86
instr_t next;
app_pc dpc = next_pc;
bool match = false;
byte prev_byte;
instr_init(drcontext, &next);
if (instr_get_opcode(inst) == OP_test &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
/* base varies: I've seen eax, ecx, edi, esi */
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(inst, 0)) == 0 &&
opnd_get_disp(instr_get_src(inst, 0)) == 0 &&
opnd_is_reg(instr_get_src(inst, 1)) &&
opnd_get_reg(instr_get_src(inst, 1)) == REG_EAX) {
reg_id_t test_base = opnd_get_base(instr_get_src(inst, 0));
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return match;
if (instr_valid(&next) &&
((instr_get_opcode(&next) == OP_cmp &&
opnd_is_reg(instr_get_src(&next, 0)) &&
opnd_get_reg(instr_get_src(&next, 0)) == REG_EAX &&
opnd_is_immed_int(instr_get_src(&next, 1))) ||
((instr_get_opcode(&next) == OP_mov_ld ||
instr_get_opcode(&next) == OP_mov_st) &&
opnd_is_reg(instr_get_src(&next, 0)) &&
opnd_get_reg(instr_get_src(&next, 0)) == test_base &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_reg(instr_get_dst(&next, 0)) == REG_ESP) ||
(instr_get_opcode(&next) == OP_xchg &&
opnd_is_reg(instr_get_src(&next, 0)) &&
opnd_get_reg(instr_get_src(&next, 0)) == REG_ESP) ||
(instr_get_opcode(&next) == OP_jmp ||
instr_get_opcode(&next) == OP_jmp_short))) {
match = true;
/* this is a probe to commit the page: does not change range of
* stack pointer
*/
*now_addressable = false;
}
}
/* ntdll!_chkstk retaddr shift */
else if (instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
opnd_get_base(instr_get_src(inst, 0)) == REG_EAX &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(inst, 0)) == 0 &&
opnd_get_disp(instr_get_src(inst, 0)) == 0 &&
opnd_is_reg(instr_get_dst(inst, 0)) &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_EAX &&
/* prev instr is "xchg esp, eax" */
safe_read(pc-1, sizeof(prev_byte), &prev_byte) &&
prev_byte == 0x94) {
match = true;
/* do NOT mark addressable as the next instr, a push, will do so */
*now_addressable = false;
}
/* cygwin alloca */
else if (instr_get_opcode(inst) == OP_or &&
opnd_is_base_disp(instr_get_dst(inst, 0)) &&
opnd_get_base(instr_get_dst(inst, 0)) == REG_ECX &&
opnd_get_index(instr_get_dst(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_dst(inst, 0)) == 0 &&
opnd_get_disp(instr_get_dst(inst, 0)) == 0 &&
opnd_is_immed_int(instr_get_src(inst, 0)) &&
opnd_get_immed_int(instr_get_src(inst, 0)) == 0) {
/* or of memory with 0 unusual enough that we look only at that instr */
match = true;
/* this is a probe to commit the page: does not change range of
* stack pointer
*/
*now_addressable = false;
}
# ifdef X64
else if (instr_get_opcode(inst) == OP_mov_st &&
opnd_is_base_disp(instr_get_dst(inst, 0)) &&
opnd_get_base(instr_get_dst(inst, 0)) == DR_REG_R11 &&
opnd_get_index(instr_get_dst(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_dst(inst, 0)) == 0 &&
opnd_get_disp(instr_get_dst(inst, 0)) == 0 &&
opnd_is_immed_int(instr_get_src(inst, 0)) &&
opnd_get_immed_int(instr_get_src(inst, 0)) == 0 &&
/* prev instr is "lea r11,[r11-1000h]" */
safe_read(pc-1, sizeof(prev_byte), &prev_byte) &&
prev_byte == 0xff) {
match = true;
/* do NOT mark addressable as the next instr, a push, will do so */
*now_addressable = false;
}
# endif
instr_free(drcontext, &next);
return match;
#elif defined(ARM)
/* FIXME i#1726: add ARM patterns */
return false;
#endif
}
#ifdef X86 /* replacement should avoid needing to port this to ARM */
static bool
is_strlen_pattern(void *drcontext, bool write, app_pc pc, app_pc next_pc,
app_pc addr, uint sz, instr_t *inst, bool *now_addressable DR_PARAM_OUT)
{
/* Check for intel\strlen.asm case where it reads 4 bytes for efficiency:
* it only does so if aligned, so no danger of touching next page, and
* though it does look at the extra bytes the string should terminate
* in the valid bytes. So, while ugly, technically it's an ok bug to suppress.
* hello!strlen+0x30 [F:\SP\vctools\crt_bld\SELF_X86\crt\src\intel\strlen.asm @ 81]:
* 81 00405f80 8b01 mov eax,[ecx]
* 82 00405f82 bafffefe7e mov edx,0x7efefeff
* 83 00405f87 03d0 add edx,eax
* 84 00405f89 83f0ff xor eax,0xffffffff
* 85 00405f8c 33c2 xor eax,edx
* 86 00405f8e 83c104 add ecx,0x4
* 87 00405f91 a900010181 test eax,0x81010100
* 88 00405f96 74e8 jz hello!strlen+0x30 (00405f80)
* hello!strlen+0x48 [F:\SP\vctools\crt_bld\SELF_X86\crt\src\intel\strlen.asm @ 90]:
* 90 00405f98 8b41fc mov eax,[ecx-0x4]
* 91 00405f9b 84c0 test al,al
* 92 00405f9d 7432 jz hello!strlen+0x81 (00405fd1)
*
* variant:
* gap+0x4516e:
* 0044516e bafffefe7e mov edx,0x7efefeff
* 00445173 8b06 mov eax,[esi]
* 00445175 03d0 add edx,eax
* 00445177 83f0ff xor eax,0xffffffff
* 0044517a 33c2 xor eax,edx
* 0044517c 8b16 mov edx,[esi]
* 0044517e 83c604 add esi,0x4
* 00445181 a900010181 test eax,0x81010100
*/
instr_t next;
app_pc dpc = next_pc;
bool match = false;
instr_init(drcontext, &next);
/* FIXME PR 406718: for this, and exceptions below, we should ensure that only
* the final byte(s) are unaddressable, and not allow middle bytes or
* any other real positive to slip through
*/
if (!ALIGNED(addr, 4) &&
instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
opnd_get_base(instr_get_src(inst, 0)) == REG_ECX &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(inst, 0)) == 0 &&
(opnd_get_disp(instr_get_src(inst, 0)) == 0 ||
opnd_get_disp(instr_get_src(inst, 0)) == -4) &&
opnd_is_reg(instr_get_dst(inst, 0)) &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_EAX) {
int raw = *(int *)dpc;
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return match;
if (instr_valid(&next) &&
(raw == 0x3274c084 /*84c0 7432*/ ||
(instr_get_opcode(&next) == OP_mov_imm &&
opnd_is_immed_int(instr_get_src(&next, 0)) &&
opnd_get_immed_int(instr_get_src(&next, 0)) == 0x7efefeff &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_reg(instr_get_dst(&next, 0)) == REG_EDX))) {
match = true;
STATS_INC(strlen_exception);
*now_addressable = false;
}
}
/* strlen variation:
* gap+0x4516e:
* 0044516e bafffefe7e mov edx,0x7efefeff
* 00445173 8b06 mov eax,[esi]
* 00445175 03d0 add edx,eax
* 00445177 83f0ff xor eax,0xffffffff
* 0044517a 33c2 xor eax,edx
* 0044517c 8b16 mov edx,[esi]
* 0044517e 83c604 add esi,0x4
* 00445181 a900010181 test eax,0x81010100
*/
else if (!ALIGNED(addr, 4) &&
instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
opnd_get_base(instr_get_src(inst, 0)) == REG_ESI &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(inst, 0)) == 0 &&
opnd_get_disp(instr_get_src(inst, 0)) == 0 &&
opnd_is_reg(instr_get_dst(inst, 0)) &&
(opnd_get_reg(instr_get_dst(inst, 0)) == REG_EAX ||
opnd_get_reg(instr_get_dst(inst, 0)) == REG_EDX)) {
int raw;
if (safe_read(pc - 4, sizeof(int), &raw) &&
(raw == 0x7efefeff || raw == 0xc233fff0 /*f0ff 33c2*/)) {
match = true;
STATS_INC(strlen_exception);
*now_addressable = false;
}
}
instr_free(drcontext, &next);
return match;
}
static bool
is_strcpy_pattern(void *drcontext, bool write, app_pc pc, app_pc next_pc,
app_pc addr, uint sz, instr_t *inst, bool *now_addressable DR_PARAM_OUT)
{
instr_t next;
app_pc dpc = next_pc;
bool match = false;
instr_init(drcontext, &next);
/* Check for cygwin1!strcpy case where it reads 4 bytes for efficiency:
* it only does so if aligned, like strlen above.
* cygwin1!strcpy:
* 610deb60 55 push ebp
* 610deb61 89e5 mov ebp,esp
* 610deb63 8b550c mov edx,[ebp+0xc]
* 610deb66 57 push edi
* 610deb67 8b7d08 mov edi,[ebp+0x8]
* 610deb6a 89d0 mov eax,edx
* 610deb6c 56 push esi
* 610deb6d 09f8 or eax,edi
* 610deb6f 53 push ebx
* 610deb70 a803 test al,0x3
* 610deb72 89f9 mov ecx,edi
* 610deb74 753a jnz cygwin1!strcpy+0x50 (610debb0)
* 610deb76 89fe mov esi,edi
* 610deb78 89d3 mov ebx,edx
* 610deb7a eb0c jmp cygwin1!strcpy+0x28 (610deb88)
* 610deb7c 8d742600 lea esi,[esi]
* 610deb80 890e mov [esi],ecx
* 610deb82 83c304 add ebx,0x4
* 610deb85 83c604 add esi,0x4
* 610deb88 8b0b mov ecx,[ebx]
* 610deb8a 89ca mov edx,ecx
* 610deb8c 8d81fffefefe lea eax,[ecx+0xfefefeff]
* 610deb92 f7d2 not edx
* 610deb94 21d0 and eax,edx
* 610deb96 a980808080 test eax,0x80808080
* 610deb9b 74e3 jz cygwin1!strcpy+0x20 (610deb80)
*/
if (!ALIGNED(addr, 4) &&
instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
opnd_get_base(instr_get_src(inst, 0)) == REG_EBX &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(inst, 0)) == 0 &&
opnd_get_disp(instr_get_src(inst, 0)) == 0 &&
opnd_is_reg(instr_get_dst(inst, 0)) &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_ECX) {
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return match;
if (instr_valid(&next)) {
instr_reset(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return match;
if (instr_valid(&next) &&
instr_get_opcode(&next) == OP_lea &&
opnd_get_base(instr_get_src(&next, 0)) == REG_ECX &&
opnd_get_index(instr_get_src(&next, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(&next, 0)) == 0 &&
opnd_get_disp(instr_get_src(&next, 0)) == 0xfefefeff &&
opnd_is_reg(instr_get_dst(&next, 0)) &&
opnd_get_reg(instr_get_dst(&next, 0)) == REG_EAX) {
match = true;
STATS_INC(strcpy_exception);
*now_addressable = false;
}
}
}
instr_free(drcontext, &next);
return match;
}
#endif /* X86 */
static bool
is_prefetch(void *drcontext, bool write, app_pc pc, app_pc next_pc,
app_pc addr, uint sz, instr_t *inst, bool *now_addressable DR_PARAM_OUT,
app_loc_t *loc, dr_mcontext_t *mc)
{
/* i#585: prefetch should not raise an unaddr error, only a warning */
if (instr_is_prefetch(inst)) {
if (options.check_prefetch) {
char msg[64];
dr_snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
"prefetching unaddressable memory "PFX"-"PFX,
addr, addr+sz);
NULL_TERMINATE_BUFFER(msg);
/* include instruction= line for max info and suppress flexibility */
report_warning(loc, mc, msg, addr, sz, true);
}
*now_addressable = false;
return true;
}
return false;
}
#ifdef WINDOWS
static bool
is_heap_seh(void *drcontext, bool write, app_pc pc, app_pc next_pc,
app_pc addr, uint sz, instr_t *inst, bool *now_addressable DR_PARAM_OUT,
app_loc_t *loc, dr_mcontext_t *mc)
{
/* i#689: Rtl*Heap SEH finalizer reads Heap to unlock the Heap's critsec.
* There's no good way to find the finalizer, or auto-suppress, so we
* pattern-match the code, which seems fairly stable:
*
* xp64:
* ntdll!RtlAllocateHeap+0xe87:
* 7d629bb6 ffb778050000 push dword ptr [edi+0x578]
* 7d629bbc e81656ffff call ntdll!RtlLeaveCriticalSection (7d61f1d7)
* 7d629bc1 e98c7affff jmp ntdll!RtlAllocateHeap+0xe92 (7d621652)
*
* win7:
* ntdll!RtlpAllocateHeap+0xe7f:
* 770024f8 ffb3cc000000 push dword ptr [ebx+0xcc]
* 770024fe e86dfdfdff call ntdll!RtlLeaveCriticalSection (76fe2270)
* 77002503 e9ad17ffff jmp ntdll!RtlpAllocateHeap+0xe8a (76ff3cb5)
*/
cls_drmem_t *cpt = (cls_drmem_t *) drmgr_get_cls_field(drcontext, cls_idx_drmem);
bool match = false;
if (!is_in_seh(drcontext) || !is_in_heap_region(addr))
return false;
/* If in SEH and addr is in heap region and addr matches the critsec
* we recorded earlier on pattern-matched code that looks like a
* finalizer for Rtl*Heap, allow the ref. We could further check that
* pc is inside RtlLeaveCriticalSection as that routine is contiguous
* but that makes it more fragile IMHO.
*/
if (addr >= (byte*)cpt->heap_critsec &&
addr < (byte*)cpt->heap_critsec + sizeof(*cpt->heap_critsec)) {
LOG(2, "SEH Heap CritSec field exception: "PFX" accessing "PFX"\n", pc, addr);
return true;
}
/* See if code looks like Rtl*Heap finalizer */
if (instr_get_opcode(inst) == OP_push &&
opnd_is_memory_reference(instr_get_src(inst, 0))) {
instr_t next;
app_pc dpc = next_pc;
instr_init(drcontext, &next);
if (!safe_decode(drcontext, dpc, &next, &dpc))
return false;
if (instr_is_call(&next) &&
opnd_is_pc(instr_get_target(&next)) &&
opnd_get_pc(instr_get_target(&next)) == addr_RtlLeaveCrit) {
byte *ptr = opnd_compute_address(instr_get_src(inst, 0), mc);
if (safe_read(ptr, sizeof(cpt->heap_critsec), &cpt->heap_critsec)) {
LOG(2, "SEH Heap CritSec exception: "PFX" accessing "PFX"\n", pc, addr);
match = true;
}
}
instr_free(drcontext, &next);
}
return match;
}
#endif /* WINDOWS */
/* i#91: For some apps there are so many alloca probes that it's a perf hit to
* come to the slowpath, so we note the address, flush the fragment, and ignore
* unaddrs there in the future. To prevent false negatives if the code changes,
* we check if the alloca pattern still matches in the bb creation event.
*/
static void
add_alloca_exception(void *drcontext, app_pc pc)
{
bool success;
STATS_INC(alloca_exception);
success = hashtable_add(&ignore_unaddr_table, pc, (void *)1);
LOG(2, "adding "PFX" to ignore_unaddr_table from thread "SZFMT
", exists: %d\n", pc, dr_get_thread_id(drcontext), !success);
if (!success) {
/* This can happen on concurrent execution prior to the flush.
* In fact, with the delayed flush, these message tend to clutter
* the log, so downgrading.
*/
LOG(1, "ignore_unaddr_table entry came to slowpath: likely no problem"
" (delay flush just hasn't started yet)\n");
} else {
success = dr_delay_flush_region(pc, 1, 0, NULL);
ASSERT(success, "ignore_unaddr_table flush failed");
}
}
static bool
is_ok_unaddressable_pattern(bool write, app_loc_t *loc, app_pc addr, uint sz,
dr_mcontext_t *mc)
{
void *drcontext = dr_get_current_drcontext();
app_pc pc, dpc;
instr_t inst;
bool match = false, now_addressable = false;
bool unreadable_ok = false;
if (loc->type != APP_LOC_PC) /* ignore syscalls (PR 488793) */
return false;
pc = loc_to_pc(loc);
instr_init(drcontext, &inst);
if (!safe_decode(drcontext, pc, &inst, &dpc))
return false;
ASSERT(instr_valid(&inst), "unknown suspect instr");
if (!match) {
match = is_alloca_pattern(drcontext, pc, dpc, &inst, &now_addressable);
if (match) {
/* it's ok for the target addr to be unreadable if stack guard page (i#538) */
unreadable_ok = true;
add_alloca_exception(drcontext, pc);
}
}
#ifdef X86 /* replacement should avoid needing to port this to ARM */
if (!match) {
match = is_strlen_pattern(drcontext, write, pc, dpc, addr, sz,
&inst, &now_addressable);
}
if (!match) {
match = is_strcpy_pattern(drcontext, write, pc, dpc, addr, sz,
&inst, &now_addressable);
}
if (!match) {
match = is_rawmemchr_pattern(drcontext, write, pc, dpc, addr, sz,
&inst, &now_addressable);
}
#endif
if (!match) {
match = is_prefetch(drcontext, write, pc, dpc, addr, sz,
&inst, &now_addressable, loc, mc);
if (match)
unreadable_ok = true;
}
#ifdef WINDOWS
if (!match) {
match = is_heap_seh(drcontext, write, pc, dpc, addr, sz,
&inst, &now_addressable, loc, mc);
}
#endif
if (match) {
/* PR 503779: be sure to not do this readability check before
* the heap header/tls checks, else we have big perf hits!
* Needs to be on a rare path.
*/
if (!unreadable_ok && !dr_memory_is_readable(addr, 1)) {
/* matched pattern, but target is actually unreadable! */
return false;
}
LOG(3, "matched is_ok_unaddressable_pattern\n");
}
if (now_addressable) {
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
shadow_set_byte(&info, addr, SHADOW_UNDEFINED);
}
instr_free(drcontext, &inst);
return match;
}
#ifdef UNIX
/* Until we have a private loader, we have to have exceptions for the
* loader reading our own libraries. Xref PR
*/
static bool
is_loader_exception(app_loc_t *loc, app_pc addr, uint sz)
{
/* Allow the loader to read .dynamic section of DR or DrMem libs.
* Also allow lib itself to access its own lib.
*/
bool res = false;
if (is_in_client_or_DR_lib(addr)) {
app_pc pc = loc_to_pc(loc);
module_data_t *data = dr_lookup_module(pc);
if (data != NULL) {
const char *modname = dr_module_preferred_name(data);
if (modname != NULL &&
(strncmp(modname, IF_MACOS_ELSE("dyld", "ld-linux"),
IF_MACOS_ELSE(4, 8)) == 0 ||
/* i#1703: dyld also accesses DR through these two libs */
IF_MACOS(strcmp(modname, "libmacho.dylib") == 0 ||
strcmp(modname, "libobjc.A.dylib") == 0 ||
strcmp(modname, "libdyld.dylib") == 0 ||)
is_in_client_or_DR_lib(pc))) {
/* If this happens too many times we may want to go back to
* marking our libs as defined and give up on catching wild
* app writes to those regions
*/
STATS_INC(loader_DRlib_exception);
res = true;
LOG(2, "ignoring unaddr for loader accessing DR/DrMem lib\n");
}
else if (modname != NULL &&
strncmp(modname, "libgcc_s.so", 11) == 0) {
/* C++ exception unwind using dl_iterate_phdr examines our libs
* (PR 623701). Will go away once we have our own private loader.
*/
STATS_INC(cppexcept_DRlib_exception);
res = true;
LOG(2, "ignoring unaddr for C++ exception accessing DR/DrMem lib\n");
}
dr_free_module_data(data);
}
}
return res;
}
#endif /* UNIX */
bool
check_unaddressable_exceptions(bool write, app_loc_t *loc, app_pc addr, uint sz,
bool addr_on_stack, dr_mcontext_t *mc)
{
void *drcontext = dr_get_current_drcontext();
#ifdef WINDOWS
TEB *teb = get_TEB();
/* We can't use teb->ProcessEnvironmentBlock b/c i#249 points it at private PEB */
PEB *peb = get_app_PEB();
#endif
bool addr_in_heap = is_in_heap_region(addr);
/* It's important to handle the very-common heap-header w/o translating
* loc's pc field which is a perf hit
*/
if (addr_in_heap && alloc_in_heap_routine(drcontext)) {
/* FIXME: ideally we would know exactly which fields were header
* fields and which ones were ok to write to, to avoid heap corruption
* by bugs in heap routines (and avoid allowing bad reads by other
* ntdll routines like memcpy).
* For glibc we do know the header size, but on an alloc the block
* is not yet in our malloc table (it is on a free).
*/
DOLOG(3, {
if (options.shadowing) {
umbra_shadow_memory_info_t info;
umbra_shadow_memory_info_init(&info);
LOG(3, "ignoring unaddr %s by heap routine "PFX" to "PFX
" tls=%d shadow=0x%x\n",
write ? "write" : "read", loc_to_print(loc), addr,
get_shadow_inheap(), shadow_get_byte(&info, addr));
} else {
LOG(3, "ignoring unaddr %s by heap routine "PFX" to "PFX" \n",
write ? "write" : "read", loc_to_print(loc), addr);
}
});
/* with options.check_ignore_unaddr, we should only come here if:
* 1) !get_shadow_inheap() -- but should be on for all alloc routines
* 2) part of unaligned word access -- but unlikely for alloc routine
* 3) access used shared xl8 and went off end of shadow block
*/
STATS_INC(heap_header_exception);
/* leave as unaddressable */
return true;
}
#ifdef WINDOWS
/* For TLS, rather than proactively track sets and unsets, we check
* on fault for whether set and we never mark as addressable.
* FIXME i#537: for performance we should proactively track so we can mark
* as addressable. Should just watch the API and let people who
* bypass to set the bits themselves deal w/ the false positives instead
* of adding checks to all writes to catch tls bitmask writes.
*/
if ((addr >= (app_pc)&teb->TlsSlots[0] && addr < (app_pc)&teb->TlsSlots[64]) ||
(teb->TlsExpansionSlots != NULL &&
addr >= (app_pc)teb->TlsExpansionSlots &&
addr < (app_pc)teb->TlsExpansionSlots +
TLS_EXPANSION_BITMAP_SLOTS*sizeof(byte))) {
#ifdef DEBUG
umbra_shadow_memory_info_t info;
#endif
bool tls_ok = false;
if (addr >= (app_pc)&teb->TlsSlots[0] && addr < (app_pc)&teb->TlsSlots[64]) {
uint slot = (addr - (app_pc)&teb->TlsSlots[0]) / sizeof(void*);
LOG(3, "checking unaddressable TLS slot "PFX" => %d\n",
addr, slot);
tls_ok = ((peb->TlsBitmap->Buffer[slot/32] & (1 << (slot % 32))) != 0);
} else {
uint slot = (addr - (app_pc)teb->TlsExpansionSlots) / sizeof(void*);
ASSERT(peb->TlsExpansionBitmap != NULL, "TLS mismatch");
LOG(3, "checking unaddressable expansion TLS slot "PFX" => %d\n",
addr, slot);
tls_ok = ((peb->TlsExpansionBitmap->Buffer[slot/32] & (1 << (slot % 32)))
!= 0);
}
#ifdef DEBUG
umbra_shadow_memory_info_init(&info);
#endif
LOG(3, "%s unaddr %s by "PFX" to TLS slot "PFX" shadow=%x\n",
tls_ok ? "ignoring" : "reporting", write ? "write" : "read",
loc_to_print(loc), addr, shadow_get_byte(&info, addr));
STATS_INC(tls_exception);
/* We leave as unaddressable since we're not tracking the unset so we
* can't safely mark as addressable */
return tls_ok;
}
#else
if (is_loader_exception(loc, addr, sz)) {
return true;
}
#endif
if (is_ok_unaddressable_pattern(write, loc, addr, sz, mc)) {
return true;
} else if (options.shadowing &&
options.define_unknown_regions && !addr_in_heap &&
(!options.check_stack_bounds || !addr_on_stack)
/* i#579: leave kernel regions as unaddr */
IF_WINDOWS(&& addr < get_highest_user_address())) {
/* i#352 (and old PR 464106): handle memory allocated by other
* processes by treating as fully defined, without any UNADDR.
* This is Windows and there are cases where csrss allocates
* things like activation contexts.
*
* XXX: limit this to MEM_MAPPED/MEM_IMAGE and for MEM_PRIVATE mark as
* uninit, or maybe just a louder warning, or old idea of reporting
* initial unaddr (though that seems silly), since could be a heap alloc
* we missed or some other allocation (PR 464106 mentions gdi32 bitmaps)
* that should start out uninit?
*/
app_pc base;
size_t sz = allocation_size(addr, &base);
if (sz > 0 && base != NULL) {
LOG(1, "WARNING: unknown region " PFX " => " PFX "-" PFX
": marking as defined\n", addr, base, base+sz);
ASSERT(!dr_memory_is_dr_internal(addr) &&
!dr_memory_is_in_client(addr),
"App is using tool's memory: please report this!");
/* There can be reserved-only regions inside, which can be quite large,
* so be sure to skip them (i#2184).
*/
mmap_walk(base, sz, IF_WINDOWS_(NULL) true/*add*/);
return true;
}
}
return false;
}
/***************************************************************************
* HEAP REGION
*/
#ifdef WINDOWS
void
client_remove_malloc_on_destroy(HANDLE heap, byte *start, byte *end)
{
leak_remove_malloc_on_destroy(heap, start, end);
}
#endif
void
handle_new_heap_region(app_pc start, app_pc end, dr_mcontext_t *mc)
{
report_heap_region(true/*add*/, start, end, mc);
}
void
handle_removed_heap_region(app_pc start, app_pc end, dr_mcontext_t *mc)
{
report_heap_region(false/*remove*/, start, end, mc);
}
/***************************************************************************
* LEAK CHECKING
*/
void
client_found_leak(app_pc start, app_pc end, size_t indirect_bytes,
bool pre_us, bool reachable,
bool maybe_reachable, void *client_data,
bool count_reachable, bool show_reachable)
{
packed_callstack_t *pcs = (packed_callstack_t *) client_data;
if (!options.count_leaks) {
ASSERT(false, "shouldn't get here");
return;
}
report_leak(true, start, end - start, indirect_bytes, pre_us, reachable,
maybe_reachable, SHADOW_UNKNOWN, pcs, count_reachable, show_reachable);
}
static byte *
next_defined_ptrsz(byte *start, byte *end)
{
return shadow_next_ptrsz((byte *)ALIGN_FORWARD(start, sizeof(void*)),
end, SHADOW_DEFINED);
}
static byte *
end_of_defined_region(byte *start, byte *end)
{
byte *res;
if (shadow_check_range(start, end - start, SHADOW_DEFINED, &res, NULL, NULL))
res = end;
return res;
}
static bool
is_register_defined(void *drcontext, reg_id_t reg)
{
return is_shadow_register_defined(get_thread_shadow_register(drcontext, reg));
}
void
check_reachability(bool at_exit)
{
/* no leak scan if we do not memory alloc (could have bailed for PR 574018) */
if (!options.track_allocs)
return;
if (!options.count_leaks)
return;
if (!options.leak_scan)
return;
leak_scan_for_leaks(at_exit);
}
/***************************************************************************
* malloc table iterate data
*/
typedef struct _malloc_iter_data_t {
/* query [addr..addr + size) */
byte *addr;
size_t size;
/* alloc block info if found */
app_pc start;
app_pc end;
app_pc real_end;
packed_callstack_t *alloc_pcs;
bool pre_us;
/* found block in malloc table */
bool found;
} malloc_iter_data_t;
/***************************************************************************/
/* iterate callback for finding block overlapping with [addr, addr + size) */
static bool
malloc_iterate_cb(malloc_info_t *mal, void *iter_data)
{
malloc_iter_data_t *data = (malloc_iter_data_t *) iter_data;
byte *rz_start = mal->base - (mal->has_redzone ? options.redzone_size : 0);
size_t tot_sz = mal->pad_size + (mal->has_redzone ? options.redzone_size*2 : 0);
ASSERT(iter_data != NULL, "invalid iteration data");
ASSERT(mal != NULL, "invalid params");
LOG(4, "malloc iter: "PFX"-"PFX"%s\n", mal->base, mal->base + mal->request_size,
mal->pre_us ? ", pre-us" : "");
ASSERT(!data->found, "the iteration should be short-circuited");
if (data->addr < rz_start + tot_sz &&
(data->addr + data->size) > rz_start) {
data->start = mal->base;
data->end = mal->base + mal->request_size;
data->real_end = rz_start + tot_sz;
data->alloc_pcs = (packed_callstack_t *) mal->client_data;
data->pre_us = mal->pre_us;
data->found = true;
return false; /* stop iteration */
}
return true; /* continue iteration */
}
/* XXX: this could be used in report_heap_info() in report.c when we don't have
* shadow info, to find overlapping malloc, and it could be adapted to find
* the nearest neighbor.
*/
static bool
region_overlap_with_malloc_block(malloc_iter_data_t *iter_data)
{
ASSERT(iter_data != NULL, "invalid iteration data");
/* expensive hashtable walk */
LOG(2, "expensive lookup for region_overlap_with_malloc_block@["
PFX".."PFX")\n", iter_data->addr, iter_data->addr + iter_data->size);
malloc_iterate(malloc_iterate_cb, iter_data);
return iter_data->found;
}
/* check if region [addr, addr + size) overlaps with any malloc redzone
* or padding.
* - if overlaps, return true and fill all the passed in parameters,
* - otherwise, return false and NO parameters is filled.
*/
bool
region_in_redzone(byte *addr, size_t size,
packed_callstack_t **alloc_pcs DR_PARAM_OUT,
app_pc *app_start DR_PARAM_OUT,
app_pc *app_end DR_PARAM_OUT,
app_pc *redzone_start DR_PARAM_OUT,
app_pc *redzone_end DR_PARAM_OUT)
{
malloc_iter_data_t iter_data = {addr, size, NULL, NULL, NULL, false, false};
if (options.replace_malloc) {
/* Faster than full iteration, in presence of multiple arenas */
malloc_info_t mal;
mal.struct_size = sizeof(mal);
if (alloc_replace_overlaps_malloc(addr, addr + size, &mal)) {
iter_data.start = mal.base;
iter_data.end = mal.base + mal.request_size;
iter_data.real_end = mal.base + mal.pad_size +
(mal.has_redzone ? options.redzone_size*2 : 0);
iter_data.alloc_pcs = (packed_callstack_t *) mal.client_data;
iter_data.pre_us = false; /* we don't know, but won't match bounds checks */
iter_data.found = true;
}
} else if (region_overlap_with_malloc_block(&iter_data)) {
ASSERT(iter_data.found, "should be set already");
}
if (iter_data.found) {
LOG(3, "%s "PFX"-"PFX": match "PFX"-"PFX"-"PFX", checking redzones\n",
__FUNCTION__, addr, addr+size, iter_data.start, iter_data.end,
iter_data.real_end);
if (iter_data.pre_us)
return false; /* pre_us */
/* in head redzone */
/* For -replace_malloc w/ shared redzones, we'll pick head over tail:
* shouldn't matter.
*/
if (addr < iter_data.start &&
addr + size > iter_data.start - options.redzone_size) {
if (app_start != NULL)
*app_start = iter_data.start;
if (app_end != NULL)
*app_end = iter_data.end;
if (redzone_start != NULL)
*redzone_start = iter_data.start - options.redzone_size;
if (redzone_end != NULL)
*redzone_end = iter_data.start;
if (alloc_pcs != NULL)
*alloc_pcs = iter_data.alloc_pcs;
LOG(3, "\tin pre-redzone\n");
return true;
}
/* in tail redzone */
if (addr < iter_data.real_end && addr + size > iter_data.end) {
if (app_start != NULL)
*app_start = iter_data.start;
if (app_end != NULL)
*app_end = iter_data.end;
if (redzone_start != NULL)
*redzone_start = iter_data.end;
if (redzone_end != NULL)
*redzone_end = iter_data.real_end;
if (alloc_pcs != NULL)
*alloc_pcs = iter_data.alloc_pcs;
LOG(3, "\tin post-redzone\n");
return true;
}
}
return false;
}