blob: da99594e07239cb11506b0660efee0adf3f7d8ed [file] [log] [blame]
/* **********************************************************
* 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.
*/
/***************************************************************************
* callstack.c: callstack recording
*/
#include "dr_api.h"
#include "drmgr.h"
#ifdef HAVE_LIBUNWIND_H
# include "drcallstack.h"
#endif
#include "callstack.h"
#include "utils.h"
#include "redblack.h"
#include "drsyms.h"
#include "drsyscall.h"
#ifdef UNIX
# include <string.h>
# include <errno.h>
#endif
#include <limits.h>
/* Options all have 0 as default value */
static callstack_options_t ops;
#ifdef WINDOWS
# define FP_PREFIX ""
#else
# define FP_PREFIX "\t"
#endif
#define LINE_PREFIX " "
#ifdef STATISTICS
static uint callstack_walks;
static uint callstacks_symbolized;
static uint find_next_fp_scans;
static uint find_next_fp_cache_hits;
static uint find_next_fp_strings;
static uint find_next_fp_string_structs;
static uint cstack_is_retaddr_tgt_mismatch;
static uint symbol_names_truncated;
static uint cstack_is_retaddr;
static uint cstack_is_retaddr_backdecode;
static uint cstack_is_retaddr_unreadable;
static uint cstack_is_retaddr_unseen;
#endif
/* Cached frame pointer values to avoid repeated scans (i#1186) */
typedef struct _fpscan_cache_entry {
byte *input_fp;
byte *output_fp;
app_pc retaddr;
} fpscan_cache_entry;
/* XXX: perhaps this should be based on the max frames, though if someone
* asks for a ton of frames and optimizes his app with FPO he can't expect
* great performance.
*/
#define FPSCAN_CACHE_ENTRIES 16
typedef struct _tls_callstack_t {
char *errbuf; /* buffer for atomic writes to global logfile */
size_t errbufsz;
byte *page_buf; /* buffer for app stack safe read */
app_pc stack_lowest_frame; /* optimization for recording callstacks */
/* Optimization for Linux main thread, where normal-looking but
* non-fp values can end up with a too-high stack_lowest_frame,
* causing us to keep scanning into the argv/envp/auxv area. Thus
* for the main thread we store the retaddr of the call from the
* executable entry point (_start). Xref i#1186.
*/
app_pc stack_lowest_retaddr;
/* Optimization for FPO-optimized apps */
fpscan_cache_entry fpcache[FPSCAN_CACHE_ENTRIES];
uint fpcache_idx;
} tls_callstack_t;
static int tls_idx_callstack = -1;
/****************************************************************************
* Binary callstacks for storing callstacks of allocation sites.
* Print-format callstacks take up too much room (PR 424179).
* We do NOT store the frame pointers, to save space. They are
* rarely needed in allocation site analysis.
*/
typedef union {
app_pc addr;
/* We need space for more than a 24-bit syscall number (size of modoffs)
* and for a string identifying the param (PR 525269). We just can't
* fit that inline easily, and since syscalls are rare enough, we
* have a pointer to out-of-line storage.
*/
syscall_loc_t *sysloc;
} frame_loc_t;
/* Packed binary callstack */
/* i#954: using packed data structure so we can use memcmp for comparison */
START_PACKED_STRUCTURE
struct _packed_frame_t {
frame_loc_t loc;
/* Modules can move around, with the same module being at two
* different locations, so we must store both the name (which is a
* pointer into a never-removed-from module name hashtable) and
* the offset. We pack further using an array of names so we can
* store an index here that is a single byte (if we hit >
* 256 libraries we switch to full_frame_t) that shares a dword
* with the module offset. That
* limits the offset to 16MB. For modules larger than that, we have
* extra entries that are adjacent in the modname array. The
* hashtable holds the index of the first such entry.
*/
uint modoffs : 24;
/* For syscalls, we use index 0. We do not store the syscall #
* (it won't fit in modoffs) but rely on loc.sysloc.
* For non-module addresses, we use index MAX_MODNAMES_STORED.
*/
uint modname_idx : 8;
} END_PACKED_STRUCTURE;
typedef struct _packed_frame_t packed_frame_t;
/* Hashtable entry is the master entry. modname_array and full frame field
* point at same entry.
*/
typedef struct _modname_info_t {
/* Both strings are strdup-ed */
const char *name; /* "preferred" name */
const char *path; /* name with full path */
/* Index into modname_array, if one of the first MAX_MODOFFS_STORED module
* names; else -1
*/
int index;
/* i#446: Unique module id for postprocessing. */
uint id;
/* i#589: don't show module! for executable or other modules */
bool hide_modname;
/* Avoid repeated warnings about symbols */
bool warned_no_syms;
/* Whether to abort an fp walk out of this module (i#703) */
bool abort_fp_walk;
/* i#1310: support user data */
void *user_data;
} modname_info_t;
/* When the number of modules hits the max for our 8-bit index we
* have to switch to these frames
*/
/* i#954: using packed data structure so we can use memcmp for comparison */
START_PACKED_STRUCTURE
struct _full_frame_t {
frame_loc_t loc;
size_t modoffs;
/* For syscalls, we use MODNAME_INFO_SYSCALL and loc.sysloc.
* For non-module addresses, we use NULL.
*/
modname_info_t *modname;
} END_PACKED_STRUCTURE;
typedef struct _full_frame_t full_frame_t;
/* used to indicate syscall for full_frame_t (NULL indicates not in a module) */
static const modname_info_t MODNAME_INFO_SYSCALL;
#define MAX_MODOFFS_STORED (0x00ffffff)
struct _packed_callstack_t {
/* share callstacks to save space (PR 465174) */
uint refcount;
/* variable-length to save space */
ushort num_frames;
/* whether frames are packed_frame_t or full_frame_t */
bool is_packed:1;
/* whether first frame is a retaddr (in which case we subtract 1 when printing line) */
bool first_is_retaddr:1;
/* whether first frame is a syscall (invariant: later frames never are) */
bool first_is_syscall:1;
union {
packed_frame_t *packed;
full_frame_t *full;
} frames;
};
/* multiplexing between packed and full frames */
#define PCS_FRAME_LOC(pcs, n) \
((pcs)->is_packed ? (pcs)->frames.packed[n].loc : (pcs)->frames.full[n].loc)
#define PCS_FRAMES(pcs) \
((pcs)->is_packed ? (void*)((pcs)->frames.packed) : (void*)((pcs)->frames.full))
#define PCS_FRAME_SZ(pcs) \
((pcs)->is_packed ? sizeof(*(pcs)->frames.packed) : sizeof(*(pcs)->frames.full))
/* Hashtable that stores name info. We never remove entries. */
#define MODNAME_TABLE_HASH_BITS 8
static hashtable_t modname_table;
static bool modname_table_initialized;
/* Array mapping index to name for use with packed_frame_t.
* Points at same modname_info_t as hashtable entry.
* Hashtable lock synchronizes writes; no synch on reads.
*/
#define MAX_MODNAMES_STORED UCHAR_MAX
static modname_info_t *modname_array[MAX_MODNAMES_STORED];
/* Index 0 is reserved to indicate a system call as the top frame of a callstack */
static uint modname_array_end = 1;
/* Unique id for looking up full module paths when postprocessing. Protected by
* modname_table lock. 0 means syscall or no module.
*/
static uint modname_unique_id = 1;
/* PR 473640: our own module region tree */
static rb_tree_t *module_tree;
static void *modtree_lock;
/* We maintain the modules w/ the lowest and highest addresses for quick
* queries of stack addrs, etc.
*/
static app_pc modtree_min_start;
static app_pc modtree_max_end;
/* cached values for module_lookup */
static app_pc modtree_last_start;
static size_t modtree_last_size;
static modname_info_t *modtree_last_name_info;
/* cached values for is_in_module() */
static app_pc modtree_last_hit;
static app_pc modtree_last_miss;
/* i#1217: exclude DR and DrMem retaddrs on app stack from -replace_malloc */
static app_pc libdr_base, libdr_end;
static app_pc libtoolbase, libtoolend;
/****************************************************************************
* Symbolized callstacks for comparing to suppressions.
* We do not store these long-term except those we read from suppression file.
* We need to print out to a max-size buffer anyway so we use fixed
* arrays for the strings.
*/
struct _symbolized_frame_t {
uint num;
app_loc_t loc;
/* For easier suppression comparison we store "<not in a module>" and
* "system call ..." in func. is_module distinguishes.
*/
bool is_module;
/* i#589: don't show module! for executable or other modules */
bool hide_modname;
/* i#635 i#603: Print offsets for frames without symbols. */
bool has_symbols;
/* i#446: Unique id of the module. */
uint modid;
/* We store the base for use in i#960 */
app_pc modbase;
char modname[MAX_MODULE_LEN+1]; /* always null-terminated */
/* This is a string instead of a number, again for comparison w/ wildcards
* in the modoffs in suppression frames
*/
char modoffs[MAX_PFX_LEN+1]; /* always null-terminated */
char func[MAX_FUNC_LEN+1]; /* always null-terminated */
size_t funcoffs;
char fname[MAX_FILENAME_LEN+1]; /* always null-terminated */
uint64 line;
size_t lineoffs;
/* i#1310: copy the user_data from the corresponding modname_info_t */
void *user_data;
};
/***************************************************************************/
/* i#1439: only allow retaddrs for calls we've seen */
#define RETADDR_TABLE_HASH_BITS 10
static hashtable_t retaddr_table;
static dr_emit_flags_t
event_basic_block_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
DR_PARAM_OUT void **user_data);
static bool
module_lookup(byte *pc, app_pc *start DR_PARAM_OUT, size_t *size DR_PARAM_OUT,
modname_info_t **name DR_PARAM_OUT);
static void
modname_info_free(void *p);
static void
warn_no_symbols(modname_info_t *name_info);
/***************************************************************************/
size_t
max_callstack_size(void)
{
static const char *max_line = "\tfp=0x12345678 parent=0x12345678 0x12345678 <>"NL;
size_t max_addr_sym_len = MAX_ADDR_LEN;
max_addr_sym_len += 1/*' '*/ + MAX_SYMBOL_LEN + 1/*\n*/ +
strlen(LINE_PREFIX) + MAX_FILE_LINE_LEN;
return ((ops.global_max_frames+1)/*for the ... line: over-estimate*/
*(strlen(max_line)+max_addr_sym_len)) + 1/*null*/;
}
void
callstack_init(callstack_options_t *options)
{
#ifdef HAVE_LIBUNWIND_H
drcallstack_options_t drcs_ops = {
sizeof(drcs_ops),
};
if (drcallstack_init(&drcs_ops) != DRCALLSTACK_SUCCESS)
ASSERT(false, "failed to initialize drcallstack");
#endif
tls_idx_callstack = drmgr_register_tls_field();
ASSERT(tls_idx_callstack > -1, "unable to reserve TLS slot");
ASSERT(options->struct_size <= sizeof(ops), "option struct too large");
memcpy(&ops, options, options->struct_size);
hashtable_init_ex(&modname_table, MODNAME_TABLE_HASH_BITS, HASH_STRING_NOCASE,
false/*!str_dup*/, false/*!synch*/, modname_info_free, NULL, NULL);
modname_table_initialized = true;
modtree_lock = dr_mutex_create();
module_tree = rb_tree_create(NULL);
if (!TEST(FP_SEARCH_ALLOW_UNSEEN_RETADDR, ops.fp_flags)) {
hashtable_config_t hashconfig = {sizeof(hashconfig),};
hashtable_init(&retaddr_table, RETADDR_TABLE_HASH_BITS,
HASH_INTPTR, false/*!str_dup*/);
hashconfig.resizable = true;
hashconfig.resize_threshold = 60; /* default is 75 */
hashtable_configure(&retaddr_table, &hashconfig);
drmgr_register_bb_instrumentation_event(event_basic_block_analysis, NULL, NULL);
}
IF_WINDOWS(ASSERT(using_private_peb(), "private peb not preserved"));
/* we rely on drsym_init() being called in utils_init() */
}
void
callstack_exit(void)
{
ASSERT(libdr_base != NULL, "never found DR lib");
ASSERT(!(ops.tool_lib_ignore != NULL && libtoolbase == NULL), "never found tool lib");
hashtable_delete(&modname_table);
if (!TEST(FP_SEARCH_ALLOW_UNSEEN_RETADDR, ops.fp_flags))
hashtable_delete_with_stats(&retaddr_table, "retaddr table");
dr_mutex_lock(modtree_lock);
rb_tree_destroy(module_tree);
dr_mutex_unlock(modtree_lock);
dr_mutex_destroy(modtree_lock);
IF_WINDOWS(ASSERT(using_private_peb(), "private peb not preserved"));
drmgr_unregister_tls_field(tls_idx_callstack);
#ifdef HAVE_LIBUNWIND_H
drcallstack_exit();
#endif
}
#ifdef STATISTICS
void
callstack_dump_statistics(file_t f)
{
dr_fprintf(f, "callstack walks: %9u, callstacks symbolized: %8u\n",
callstack_walks, callstacks_symbolized);
dr_fprintf(f, "callstack fp scans: %8u, cache hits: %8u\n",
find_next_fp_scans, find_next_fp_cache_hits);
dr_fprintf(f, "callstack strings: %6u, structs: %6u, target mismatch: %8u\n",
find_next_fp_strings, find_next_fp_string_structs,
cstack_is_retaddr_tgt_mismatch);
dr_fprintf(f, "callstack is_retaddr: %8u, backdecode: %8u, unreadable: %8u\n",
cstack_is_retaddr, cstack_is_retaddr_backdecode,
cstack_is_retaddr_unreadable);
dr_fprintf(f, "callstack is_retaddr cont'd: unseen %8u\n",
cstack_is_retaddr_unseen);
dr_fprintf(f, "symbol names truncated: %8u\n", symbol_names_truncated);
}
#endif
static void
callstack_set_lowest_frame(void *drcontext)
{
tls_callstack_t *pt = (tls_callstack_t *)
drmgr_get_tls_field(drcontext, tls_idx_callstack);
dr_mcontext_t mc; /* do not init whole thing: memset is expensive */
app_pc stack_base;
size_t stack_size;
mc.size = sizeof(mc);
mc.flags = DR_MC_CONTROL; /* only need xsp */
dr_get_mcontext(drcontext, &mc);
if (dr_query_memory((app_pc)mc.xsp, &stack_base, &stack_size, NULL)) {
LOG(2, "lowest frame for thread "TIDFMT" = top of stack "PFX"-"PFX
", sp="PFX"\n",
dr_get_thread_id(drcontext), stack_base, stack_base + stack_size, mc.xsp);
pt->stack_lowest_frame = stack_base + stack_size;
} else {
LOG(2, "unable to query stack: leaving lowest frame for thread "TIDFMT
" NULL\n", dr_get_thread_id(drcontext));
}
}
void
callstack_thread_init(void *drcontext)
{
#ifdef UNIX
static bool first = true;
#endif
tls_callstack_t *pt = (tls_callstack_t *)
thread_alloc(drcontext, sizeof(*pt), HEAPSTAT_MISC);
drmgr_set_tls_field(drcontext, tls_idx_callstack, pt);
memset(pt, 0, sizeof(*pt));
/* PR 456181: we need our error reports to use a single atomic write.
* We use a thread-private buffer to avoid using stack space or locks.
* We can have a second callstack for delayed frees (i#205).
*/
pt->errbufsz = MAX_ERROR_INITIAL_LINES + max_callstack_size() * 2;
pt->errbuf = (char *) thread_alloc(drcontext, pt->errbufsz, HEAPSTAT_CALLSTACK);
/* We take the space hit to avoid serializing all mallocs just for callstacks */
pt->page_buf = (byte *) thread_alloc(drcontext, PAGE_SIZE, HEAPSTAT_CALLSTACK);
#ifdef WINDOWS
if (get_TEB() != NULL) {
pt->stack_lowest_frame = get_TEB()->StackBase;
}
#else
if (first) {
/* We can't get mcontext for main thread (DR limitation), but
* it won't help us much anyway b/c of all the argv, env, and auxv
* stuff at the base of the stack.
* Instead we find the entry point which will be the lowest
* retaddr we should have.
*/
module_data_t *data = dr_get_main_module();
instr_t inst;
app_pc pc = data->entry_point;
app_pc stop = data->entry_point + PAGE_SIZE;
uint i;
/* Ensure we don't walk off the end of the segment (i#1846) */
for (i = 0; i < data->num_segments; i++) {
if (pc >= data->segments[i].start &&
pc < data->segments[i].end) {
if (data->segments[i].end < stop)
stop = data->segments[i].end;
break;
}
}
instr_init(drcontext, &inst);
do {
pc = decode(drcontext, pc, &inst);
/* We look for the first call. There might be a jmp instead,
* or the first call might just be a leaf helper function:
* we just won't have this optimization in those cases.
*/
if (instr_valid(&inst) && instr_is_call(&inst)) {
pt->stack_lowest_retaddr = pc;
break;
}
instr_reset(drcontext, &inst);
} while (pc != NULL && pc < stop);
instr_free(drcontext, &inst);
LOG(1, "stack_lowest_retaddr for main thread = 1st call "PFX" > entry "PFX"\n",
pt->stack_lowest_retaddr, data->entry_point);
dr_free_module_data(data);
first = false;
} else {
callstack_set_lowest_frame(drcontext);
}
#endif
}
void
callstack_thread_exit(void *drcontext)
{
tls_callstack_t *pt = (tls_callstack_t *)
drmgr_get_tls_field(drcontext, tls_idx_callstack);
thread_free(drcontext, (void *) pt->errbuf, pt->errbufsz, HEAPSTAT_CALLSTACK);
thread_free(drcontext, (void *) pt->page_buf, PAGE_SIZE, HEAPSTAT_CALLSTACK);
drmgr_set_tls_field(drcontext, tls_idx_callstack, NULL);
thread_free(drcontext, pt, sizeof(*pt), HEAPSTAT_MISC);
}
static dr_emit_flags_t
event_basic_block_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
DR_PARAM_OUT void **user_data)
{
instr_t *instr;
ASSERT(!TEST(FP_SEARCH_ALLOW_UNSEEN_RETADDR, ops.fp_flags), "hashtable not init!");
/* do nothing for translation */
if (translating)
return DR_EMIT_DEFAULT;
for (instr = instrlist_first(bb); instr != NULL; instr = instr_get_next(instr)) {
if (instr_is_app(instr) && instr_is_call(instr)) {
app_pc retaddr = instr_get_app_pc(instr) + instr_length(drcontext, instr);
/* we never remove from the table, and dups are fine */
hashtable_add(&retaddr_table, (void *)retaddr, (void *)tag);
}
}
return DR_EMIT_DEFAULT;
}
/***************************************************************************/
static void
init_symbolized_frame(symbolized_frame_t *frame DR_PARAM_OUT, uint frame_num)
{
memset(frame, 0, sizeof(*frame));
frame->num = frame_num;
frame->func[0] = '?';
frame->func[1] = '\0';
}
/* Symbol lookup: i#44/PR 243532 */
static void
lookup_func_and_line(symbolized_frame_t *frame DR_PARAM_OUT,
modname_info_t *name_info DR_PARAM_IN, size_t modoffs)
{
drsym_error_t symres;
drsym_info_t sym;
const char *modpath = name_info->path;
char name[MAX_FUNC_LEN];
char file[MAXIMUM_PATH];
sym.struct_size = sizeof(sym);
sym.name = name;
sym.name_size = BUFFER_SIZE_BYTES(name);
sym.file = file;
sym.file_size = BUFFER_SIZE_BYTES(file);
IF_WINDOWS(ASSERT(using_private_peb(), "private peb not preserved"));
STATS_INC(symbol_address_lookups);
symres = drsym_lookup_address(modpath, modoffs, &sym,
DRSYM_DEMANGLE |
(TEST(PRINT_EXPAND_TEMPLATES, ops.print_flags) ?
DRSYM_DEMANGLE_PDB_TEMPLATES : 0));
if (symres == DRSYM_SUCCESS || symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
LOG(4, "symbol %s+"PIFX" => %s+"PIFX" ("PIFX"-"PIFX") kind="PIFX"\n",
modpath, modoffs, sym.name, modoffs - sym.start_offs,
sym.start_offs, sym.end_offs, sym.debug_kind);
if (sym.name_available_size >= sym.name_size) {
DO_ONCE({
WARN("WARNING: at least one function name longer than max: %s\n",
sym.name);
});
STATS_INC(symbol_names_truncated);
}
frame->has_symbols = TEST(DRSYM_SYMBOLS, sym.debug_kind);
/* sym.name could be something like "BigInteger::operator%" */
dr_snprintf(frame->func, MAX_FUNC_LEN, "%s", sym.name);
NULL_TERMINATE_BUFFER(frame->func);
frame->funcoffs = (modoffs - sym.start_offs);
if (symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
frame->fname[0] = '\0';
frame->line = 0;
frame->lineoffs = 0;
} else {
char *fname = sym.file;
/* i#1634: if sym.file is longer than MAX_FILENAME_LEN,
* we skip some prefix.
*/
/* frame->fname has the size of MAX_FILENAME_LEN+1, so we do not need
* extra byte for NULL.
*/
if (strlen(fname) > MAX_FILENAME_LEN) {
fname += (strlen(fname) - MAX_FILENAME_LEN + 3 /* ... */);
if (strchr(fname, DIRSEP) != NULL)
fname = strchr(fname, DIRSEP);
}
dr_snprintf(frame->fname, MAX_FILENAME_LEN, "%s%s",
fname == sym.file ? "" : "...", fname);
NULL_TERMINATE_BUFFER(frame->fname);
frame->line = sym.line;
frame->lineoffs = sym.line_offs;
}
}
if (!frame->has_symbols) {
warn_no_symbols(name_info);
}
}
bool
print_symbol(byte *addr, char *buf, size_t bufsz, size_t *sofar,
bool use_custom_flags, uint custom_flags)
{
bool res;
ssize_t len = 0;
drsym_error_t symres;
drsym_info_t sym;
char name[MAX_FUNC_LEN];
module_data_t *data;
uint flags = use_custom_flags ? custom_flags : ops.print_flags;
const char *modname;
data = dr_lookup_module(addr);
if (data == NULL)
return false;
ASSERT(data->start <= addr && data->end > addr, "invalid module lookup");
modname = dr_module_preferred_name(data);
if (modname == NULL)
modname = "";
sym.struct_size = sizeof(sym);
sym.name = name;
sym.name_size = BUFFER_SIZE_BYTES(name);
sym.file = NULL;
IF_WINDOWS(ASSERT(using_private_peb(), "private peb not preserved"));
STATS_INC(symbol_address_lookups);
symres = drsym_lookup_address(data->full_path, addr - data->start, &sym,
DRSYM_DEMANGLE |
(TEST(PRINT_EXPAND_TEMPLATES, flags) ?
DRSYM_DEMANGLE_PDB_TEMPLATES : 0));
if (symres == DRSYM_SUCCESS || symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
if (sym.name_available_size >= sym.name_size) {
DO_ONCE({
LOG(1, "WARNING: at least one symbol name longer than max: %s\n",
sym.name);
});
STATS_INC(symbol_names_truncated);
}
/* I like having +0x%x to show offs within func but we'll match addr2line */
BUFPRINT_NO_ASSERT(buf, bufsz, *sofar, len, " %s!%s", modname, sym.name);
if (TEST(PRINT_SYMBOL_OFFSETS, flags)) {
/* no assert for any of these bufprints: for just printing we'll truncate */
BUFPRINT_NO_ASSERT(buf, bufsz, *sofar, len, "+"PIFX,
addr - data->start - sym.start_offs);
}
res = true;
} else {
BUFPRINT_NO_ASSERT(buf, bufsz, *sofar, len, " %s!?", modname);
res = false;
}
dr_free_module_data(data);
return res;
}
#ifdef DEBUG
static void
dump_app_stack(void *drcontext, tls_callstack_t *pt, dr_mcontext_t *mc, size_t amount,
app_pc pc)
{
byte *xsp = (byte *) MC_SP_REG(mc);
LOG(1, "callstack stack pc="PFX" xsp="PFX" xbp="PFX" low="PFX":\n", pc, MC_SP_REG(mc),
MC_FP_REG(mc), pt->stack_lowest_frame);
DR_TRY_EXCEPT(dr_get_current_drcontext(), {
while (xsp < (byte *)MC_SP_REG(mc) + amount && xsp < pt->stack_lowest_frame) {
void *val = *(void **)xsp;
char buf[128];
size_t sofar = 0;
ssize_t len;
BUFPRINT(buf, BUFFER_SIZE_ELEMENTS(buf), sofar, len,
"\t"PFX" "PFX, xsp, val);
print_symbol(val, buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, false, 0);
LOG(1, "%s\n", buf);
xsp += sizeof(void*);
}
}, { /* EXCEPT */
LOG(1, "<"PFX "unreadable => aborting>\n", xsp);
});
}
#endif
static bool
frame_include_srcfile(symbolized_frame_t *frame DR_PARAM_IN)
{
return (frame->fname[0] != '\0' &&
/* i#589: support hiding source files matching pattern */
(ops.srcfile_hide == NULL ||
!text_matches_any_pattern(frame->fname,
ops.srcfile_hide, FILESYS_CASELESS)));
}
/* We provide control over many aspects of callstack formatting (i#290)
* encoded in print_flags.
* We put file:line in [] and absaddr <mod!offs> in ()
*
* Example:
* 0 suppress.exe!do_uninit_read+0x27 [e:\derek\drmemory\git\src\tests\suppress.c @ 53] (0x004011d7 <suppress.exe+0x11d7>)
* 1 suppress.exe!uninit_test1+0xb [e:\derek\drmemory\git\src\tests\suppress.c @ 59] (0x0040119c <suppress.exe+0x119c>)
* 2 suppress.exe!test+0xf [e:\derek\drmemory\git\src\tests\suppress.c @ 213] (0x00401070 <suppress.exe+0x1070>)
* 3 suppress.exe!main+0x31 [e:\derek\drmemory\git\src\tests\suppress.c @ 247] (0x00401042 <suppress.exe+0x1042>)
* 4 suppress.exe!__tmainCRTStartup+0x15e [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] (0x00401d87 <suppress.exe+0x1d87>)
* 5 KERNEL32.dll!BaseProcessStart+0x27 (0x7d4e9982 <KERNEL32.dll+0x29982>)
*/
static void
print_file_and_line(symbolized_frame_t *frame DR_PARAM_IN,
char *buf, size_t bufsz, size_t *sofar,
uint print_flags, const char *prefix,
bool include_srcfile)
{
ssize_t len = 0;
/* XXX: add option for printing "[]" if field not present? */
if (include_srcfile) {
const char *fname = frame->fname;
if (TEST(PRINT_SRCFILE_NEWLINE, print_flags)) {
BUFPRINT(buf, bufsz, *sofar, len, NL"%s"LINE_PREFIX,
prefix == NULL ? "" : prefix);
} else
BUFPRINT(buf, bufsz, *sofar, len, " [");
if (ops.srcfile_prefix != NULL) {
/* i#575: support truncating source file prefix */
const char *matched;
const char *match =
text_contains_any_string(fname, ops.srcfile_prefix,
FILESYS_CASELESS, &matched);
if (match != NULL) {
fname = match + strlen(matched);
if (fname[0] == DIRSEP IF_WINDOWS(|| fname[0] == ALT_DIRSEP))
fname++;
}
}
BUFPRINT(buf, bufsz, *sofar, len, "%."STRINGIFY(MAX_FILENAME_LEN)"s", fname);
if (TEST(PRINT_VSTUDIO_FILE_LINE, print_flags))
BUFPRINT(buf, bufsz, *sofar, len, "(");
else if (!TEST(PRINT_SRCFILE_NO_COLON, print_flags))
BUFPRINT(buf, bufsz, *sofar, len, ":");
else /* windbg format */
BUFPRINT(buf, bufsz, *sofar, len, " @ ");
/* XXX: printf won't truncate ints. we could use dr_snprintf
* to limit line# to MAX_LINENO_DIGITS, but would be hacky w/
* BUFPRINT. for now we live w/ potentially truncating callstacks later
* if have giant line#s.
*/
BUFPRINT(buf, bufsz, *sofar, len, "%"UINT64_FORMAT_CODE, frame->line);
if (TEST(PRINT_LINE_OFFSETS, print_flags))
BUFPRINT(buf, bufsz, *sofar, len, "+"PIFX, frame->lineoffs);
if (TEST(PRINT_VSTUDIO_FILE_LINE, print_flags)) {
/* VS2005+ doesn't need the trailing colon, but VS6 does. */
BUFPRINT(buf, bufsz, *sofar, len, "):");
}
if (!TEST(PRINT_SRCFILE_NEWLINE, print_flags))
BUFPRINT(buf, bufsz, *sofar, len, "]");
} else {
if (TEST(PRINT_SRCFILE_NEWLINE, print_flags))
BUFPRINT(buf, bufsz, *sofar, len, NL""LINE_PREFIX"??:0");
}
}
#ifdef X64
# define PIFC INT64_FORMAT"x"
#else
# define PIFC "x"
#endif
static void
print_frame(symbolized_frame_t *frame DR_PARAM_IN,
char *buf, size_t bufsz, size_t *sofar,
bool use_custom_flags, uint custom_flags,
size_t max_func_len, const char *prefix)
{
ssize_t len = 0;
size_t align_sym = 0, align_mod = 0, align_moffs = 0;
uint flags = use_custom_flags ? custom_flags : ops.print_flags;
bool include_srcfile = frame_include_srcfile(frame);
bool print_addrs, later_info;
if (!frame->has_symbols && TEST(PRINT_NOSYMS_OFFSETS, flags)) {
/* i#603: Print absaddr and/or mod/offs if we don't have symbols. */
flags |= PRINT_ABS_ADDRESS | PRINT_MODULE_OFFSETS;
/* i#635: Print symoffs if we don't have symbols. */
flags |= PRINT_SYMBOL_OFFSETS;
}
/* To avoid trailing whitespace, determine now what will be printed at end
* of line.
*/
print_addrs =
((frame->loc.type == APP_LOC_PC && TEST(PRINT_ABS_ADDRESS, flags)) ||
(frame->is_module && TEST(PRINT_MODULE_OFFSETS | PRINT_MODULE_ID,
flags)));
later_info = print_addrs || TEST(PRINT_SYMBOL_OFFSETS, flags) ||
(include_srcfile && !TEST(PRINT_SRCFILE_NEWLINE, flags));
if (prefix != NULL)
BUFPRINT(buf, bufsz, *sofar, len, "%s", prefix);
if (TEST(PRINT_ALIGN_COLUMNS, flags)) {
/* XXX: could expose these as options. could also align "abs <mod+offs>". */
/* Avoid trailing whitespace by not aligning if line-final (i#584) */
if (TESTANY(PRINT_SYMBOL_FIRST, flags) || later_info) {
/* Shift alignment to match func name lengths, up to a limit */
align_sym = (max_func_len > 0 ? (max_func_len < 60 ? max_func_len : 60) : 35);
}
if ((TEST(PRINT_SYMBOL_OFFSETS, flags) && !TEST(PRINT_SYMBOL_FIRST, flags)) ||
later_info)
align_mod = 13; /* 8.3! */
if (TEST(PRINT_SYMBOL_FIRST, flags) || later_info)
align_moffs = 6;
}
if (TEST(PRINT_FRAME_NUMBERS, flags))
BUFPRINT(buf, bufsz, *sofar, len, "#%2d ", frame->num);
if (!frame->is_module) {
/* we already printed the syscall string or "<not in a module>" to func */
BUFPRINT(buf, bufsz, *sofar, len, "%-*s",
align_sym, frame->func);
if (frame->loc.type == APP_LOC_SYSCALL) {
if (TEST(PRINT_SRCFILE_NEWLINE, flags))
BUFPRINT(buf, bufsz, *sofar, len, NL""LINE_PREFIX"<system call>");
} else
ASSERT(frame->func[0] == '<' /* "<not in a module>" */, "inconsistency");
} else {
if (!TEST(PRINT_SYMBOL_FIRST, flags)) {
if (!frame->hide_modname || strcmp(frame->func, "?") == 0)
BUFPRINT(buf, bufsz, *sofar, len, "%s!", frame->modname);
else if (align_mod > 0)
align_mod += strlen(frame->modname) + 1 /*!*/;
BUFPRINT(buf, bufsz, *sofar, len, "%-*s",
align_mod + align_sym - strlen(frame->modname), frame->func);
} else
BUFPRINT(buf, bufsz, *sofar, len, "%-*s", align_sym, frame->func);
if (TEST(PRINT_SYMBOL_OFFSETS, flags))
BUFPRINT(buf, bufsz, *sofar, len, "+0x%-*"PIFC, align_moffs, frame->funcoffs);
if (TEST(PRINT_SYMBOL_FIRST, flags))
BUFPRINT(buf, bufsz, *sofar, len, " %-*s", align_mod, frame->modname);
/* if file+line are inlined, put before abs+mod!offs */
if (!TEST(PRINT_SRCFILE_NEWLINE, flags))
print_file_and_line(frame, buf, bufsz, sofar, flags, prefix, include_srcfile);
}
if (print_addrs) {
BUFPRINT(buf, bufsz, *sofar, len, " (");
if (frame->loc.type == APP_LOC_PC && TEST(PRINT_ABS_ADDRESS, flags)) {
BUFPRINT(buf, bufsz, *sofar, len, PFX, loc_to_pc(&frame->loc));
if (frame->is_module && TEST(PRINT_MODULE_OFFSETS, flags))
BUFPRINT(buf, bufsz, *sofar, len, " ");
}
if (frame->is_module && TEST(PRINT_MODULE_OFFSETS, flags)) {
BUFPRINT(buf, bufsz, *sofar, len,
"<%." STRINGIFY(MAX_MODULE_LEN) "s+%s>",
frame->modname, frame->modoffs);
}
BUFPRINT(buf, bufsz, *sofar, len, ")");
if (TEST(PRINT_MODULE_ID, flags)) {
/* i#446: We need unique module ids when postprocessing. */
BUFPRINT(buf, bufsz, *sofar, len, " modid:%d", frame->modid);
}
}
/* if file+line are on separate line, put after abs+mod!offs */
if (TEST(PRINT_SRCFILE_NEWLINE, flags)) {
if (frame->is_module) {
print_file_and_line(frame, buf, bufsz, sofar, flags, prefix, include_srcfile);
} else if (frame->loc.type == APP_LOC_PC) {
BUFPRINT(buf, bufsz, *sofar, len, NL""LINE_PREFIX"??:0");
}
}
BUFPRINT(buf, bufsz, *sofar, len, NL);
}
/* Fills in frame xor pcs.
* Returns whether a new frame was added (won't be if skip_non_module and pc
* is not in a module)
* sub1_sym is for PR 543863: subtract one from retaddrs in callstacks
*/
static bool
address_to_frame(symbolized_frame_t *frame DR_PARAM_OUT,
packed_callstack_t *pcs DR_PARAM_OUT,
app_pc pc, module_data_t *mod_in /*optional*/,
bool skip_non_module, bool sub1_sym, uint frame_num)
{
modname_info_t *name_info;
app_pc mod_start;
ASSERT((frame != NULL && pcs == NULL) || (frame == NULL && pcs != NULL),
"address_to_frame: can't pass frame and pcs");
if (frame != NULL) {
init_symbolized_frame(frame, frame_num);
pc_to_loc(&frame->loc, pc);
}
if (module_lookup(pc, &mod_start, NULL, &name_info)) {
ASSERT(pc >= mod_start, "internal pc-not-in-module error");
ASSERT(name_info != NULL, "module should have info");
ASSERT(mod_in == NULL || mod_in->start == mod_start, "module mismatch");
if (pcs != NULL) {
size_t sz = (pc - mod_start);
uint pcs_idx = pcs->num_frames;
if (pcs->is_packed) {
pcs->frames.packed[pcs_idx].loc.addr = pc;
if (name_info == NULL) { /* handling missing module in release build */
/* We already asserted above */
if (sz > MAX_MODOFFS_STORED) /* We lose data here */
pcs->frames.packed[pcs_idx].modoffs = MAX_MODOFFS_STORED;
else
pcs->frames.packed[pcs_idx].modoffs = sz;
pcs->frames.packed[pcs_idx].modname_idx = MAX_MODNAMES_STORED;
} else {
int idx = name_info->index;
while (sz > MAX_MODOFFS_STORED) {
sz -= MAX_MODOFFS_STORED;
if (idx + 1 == MAX_MODNAMES_STORED)
break;
idx++;
ASSERT(idx < modname_array_end, "large-modname entries truncated");
ASSERT(strcmp(modname_array[idx-1]->name,
modname_array[idx]->name) == 0,
"not enough large-modname entries");
}
pcs->frames.packed[pcs_idx].modoffs = sz;
pcs->frames.packed[pcs_idx].modname_idx = idx;
}
} else {
pcs->frames.full[pcs_idx].loc.addr = pc;
pcs->frames.full[pcs_idx].modoffs = sz;
pcs->frames.full[pcs_idx].modname = name_info;
}
pcs->num_frames++;
} else {
const char *modname = (name_info->name == NULL) ?
"<name unavailable>" : name_info->name;
frame->is_module = true;
frame->hide_modname = name_info->hide_modname;
frame->user_data = name_info->user_data;
frame->modbase = mod_start;
dr_snprintf(frame->modname, MAX_MODULE_LEN, "%s", modname);
NULL_TERMINATE_BUFFER(frame->modname);
dr_snprintf(frame->modoffs, MAX_PFX_LEN, PIFX, pc - mod_start);
NULL_TERMINATE_BUFFER(frame->modoffs);
if (name_info->path != NULL) {
lookup_func_and_line(frame, name_info,
pc - mod_start - (sub1_sym ? 1 : 0));
}
}
return true;
} else if (!skip_non_module) {
if (pcs != NULL) {
if (pcs->is_packed) {
pcs->frames.packed[pcs->num_frames].loc.addr = pc;
pcs->frames.packed[pcs->num_frames].modoffs = MAX_MODOFFS_STORED;
pcs->frames.packed[pcs->num_frames].modname_idx = MAX_MODNAMES_STORED;
} else {
pcs->frames.full[pcs->num_frames].loc.addr = pc;
pcs->frames.full[pcs->num_frames].modoffs = 0;
pcs->frames.full[pcs->num_frames].modname = NULL;
}
pcs->num_frames++;
} else {
ASSERT(!frame->is_module, "frame not initialized");
dr_snprintf(frame->func, MAX_FUNC_LEN, "<not in a module>");
NULL_TERMINATE_BUFFER(frame->func);
}
return true;
}
return false;
}
static bool
print_address_common(char *buf, size_t bufsz, size_t *sofar,
app_pc pc, module_data_t *mod_in /*optional*/,
bool skip_non_module, bool sub1_sym, bool for_log,
bool *last_frame DR_PARAM_OUT, uint frame_num)
{
symbolized_frame_t frame; /* 480 bytes but our stack can handle it */
if (address_to_frame(&frame, NULL, pc, mod_in, skip_non_module, sub1_sym, 0)) {
frame.num = frame_num;
print_frame(&frame, buf, bufsz, sofar, for_log, PRINT_FOR_LOG, 0, NULL);
if (last_frame != NULL && ops.truncate_below != NULL) {
*last_frame = text_matches_any_pattern((const char *)frame.func,
ops.truncate_below, false);
}
return true;
}
return false;
}
bool
print_address(char *buf, size_t bufsz, size_t *sofar,
app_pc pc, module_data_t *mod_in /*optional*/, bool for_log)
{
return print_address_common(buf, bufsz, sofar, pc, mod_in,
false/*include non-module*/, false/*don't sub1*/,
for_log, NULL, 0);
}
#ifndef X64
/* Walks a wide character string. Stops if it encounters any (widened) non-ascii
* component, or a null wchar.
* Reads from start, presumed to be in a safe buffer copy of orig, up to
* a max of safe_wchars, at which point it goes and reads from the original
* memory (orig + safe_wchars), up to a total max of max_wchars.
* Returns 0 if no proper wide string was found; else returns the length
* of the null-terminated wide string it found.
*/
static size_t
walk_wide_string(wchar_t *start, size_t safe_wchars,
wchar_t *orig, size_t max_wchars)
{
size_t len = 0;
wchar_t *s = start;
while (s - start < safe_wchars && IS_WCHAR_AT(s)) {
len++;
s++;
}
if (s - start < safe_wchars) {
if (*s == L'\0') /* terminating null */
return len;
else
return 0;
} else {
/* don't let the safe-read buffer limit prevent us identifying a wide string */
s = orig + (s - start);
DR_TRY_EXCEPT(dr_get_current_drcontext(), {
while (s - orig < max_wchars && IS_WCHAR_AT(s)) {
len++;
s++;
}
if (s - orig >= max_wchars || *s != L'\0')
len = 0;
}, { /* EXCEPT */
len = 0;
});
return len;
}
return 0;
}
#endif
#ifdef X86
# define OP_CALL_DIR 0xe8
# define OP_CALL_IND 0xff
# define OP_JMP_DIR_SHORT 0xeb
# define OP_JMP_DIR_LONG 0xe9
# define OP_JMP_IND 0xff
# define OP_SEG_FS 0x64
# define WOW64_SYSOFFS 0xc0
# define ENDBR32 0xfb1e0ff3
# define ENDBR64 0xfa1e0ff3
#endif
static bool
is_retaddr(app_pc pc, bool exclude_tool_lib)
{
/* XXX: for our purposes we really want is_in_code_section(). Since
* is_in_module() is used for is_image(), we would need a separate rbtree. We
* could do +rx segment via mem query and avoid walking sections. We'd have to
* store the range since might not be there at unmap time? So far the 3 backward
* derefs looking for calls haven't been slow enough or have enough false
* positives to make the +rx-only seem worth the effort: global var addresses on
* the stack don't seem any more common than things like SEH handlers that would
* match +rx anyway, and rare for global var to have what looks like a call prior
* to it.
*/
#ifdef ARM
bool is_thumb = TEST(1, (ptr_uint_t)pc);
pc = (app_pc) ALIGN_BACKWARD(pc, 2);
#endif
STATS_INC(cstack_is_retaddr);
if (!is_in_module(pc-1))
return false;
if (exclude_tool_lib &&
((pc >= libdr_base && pc < libdr_end) ||
(pc >= libtoolbase && pc < libtoolend)))
return false;
if (!TEST(FP_SEARCH_DO_NOT_DISASM, ops.fp_flags)) {
/* The is_in_module() check is more expensive than our 3 derefs here.
* We do not bother to cache frequent/recent values.
*/
/* more efficient to read 3 dwords than safe_read 6 into a buffer */
bool match;
STATS_INC(cstack_is_retaddr_backdecode);
DR_TRY_EXCEPT(dr_get_current_drcontext(), {
IF_X86_ELSE({
match = ((*(pc - 5) == OP_CALL_DIR
/* rule out call to next instr used for PIC */
IF_UNIX(&& *(int*)(pc - 4) != 0)) ||
(*(pc - 2) == OP_CALL_IND &&
/* indirect through mem: 0xff /2 (mod==0)
* => top 5 bits are 0x02, and rule out disp32 (rm==0x5)
*/
((((*(pc - 1) >> 3) == 0x02) && ((*(pc - 1) & 0x3) != 0x5)) ||
/* indirect through reg: 0xff /2 (mod==3)
* => top 5 bits are 0xd0 (0x3 << 3 | 0x2)
*/
((*(pc - 1) & 0xf8) == 0xd0))) ||
/* indirect through mem: 0xff /2 + disp8 (mod==1) */
(*(pc - 3) == OP_CALL_IND && ((*(pc - 2) >> 3) == 0x0a)) ||
/* indirect through mem: 0xff /2 + disp32 (mod==2) */
(*(pc - 6) == OP_CALL_IND &&
((*(pc - 5) >> 3) == 0x12 || *(pc - 5) == 0x15)
/* i#1217: rule out WOW64 syscall from DR code invoked on app
* stack by -replace_malloc. We always have a syscall
* in an app_loc_t so we should never need it in a frame.
*/
IF_NOT_X64(&& (*(uint*)(pc - 4) != WOW64_SYSOFFS ||
*(pc - 7) != OP_SEG_FS))
) ||
/* indirect through mem: 0xff /2 + sib (w/o sib reg=5) */
(*(pc - 3) == OP_CALL_IND &&
(*(pc - 2) == 0x14 && ((*(pc - 1) & 0x3) != 5))));
}, {
match =
(is_thumb &&
/* T32 bl <label> */
((((*(pc - 3) & 0xf0) == 0xf0) &&
((*(pc - 1) & 0xd0) == 0xd0)) ||
/* T32 blx <label> */
(((*(pc - 3) & 0xf0) == 0xf0) &&
((*(pc - 1) & 0xd0) == 0xc0)) ||
/* T32 blx <reg> */
(*(pc - 1) == 0x47 &&
((*(pc - 2) & 0x87) == 0x80)))) ||
(!is_thumb &&
/* A32 bl <label> */
(((*(pc - 1) & 0x0f) == 0x0b) ||
/* A32 blx <label> */
((*(pc - 1) & 0xfe) == 0xfa) ||
/* A32 blx <reg> */
(((*(pc - 1) & 0x0f) == 0x01) &&
*(pc - 2) == 0x2f &&
*(pc - 3) == 0xff &&
((*(pc - 4) & 0xf0) == 0x30))));
})
}, { /* EXCEPT */
match = false;
/* If we end up with a lot of these we could either cache
* frequent/recent or switch to +rx instead of whole module
*/
LOG(3, "is_retaddr: can't read "PFX"\n", pc);
STATS_INC(cstack_is_retaddr_unreadable);
});
DOLOG(5, {
char buf[128];
size_t sofar = 0;
ssize_t len;
BUFPRINT(buf, BUFFER_SIZE_ELEMENTS(buf), sofar, len,
"is_retaddr %d: "PFX" == ", match, pc);
print_symbol(pc, buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, false, 0);
LOG(1, "%s\n", buf);
});
if (!match)
return false;
}
if (!TEST(FP_SEARCH_ALLOW_UNSEEN_RETADDR, ops.fp_flags) &&
/* Do not check for retaddrs in tool libs which of course won't
* be in our table.
*/
!((pc >= libdr_base && pc < libdr_end) ||
(pc >= libtoolbase && pc < libtoolend))) {
/* i#1439: only allow retaddrs for calls we've seen */
if (hashtable_lookup(&retaddr_table, (void *)pc) == NULL) {
LOG(4, "is_retaddr: never-before-seen "PFX"\n", pc);
STATS_INC(cstack_is_retaddr_unseen);
return false;
}
}
return true;
}
#ifdef ARM
/* XXX: we should share this with DR's decode_raw_jmp_target().
* Should DR export that?
* It's ARM-only right now but we could make an x86 version and use it
* in several places where we directly de-reference the immed today.
*/
static byte *
get_call_target(byte *pc, dr_isa_mode_t mode)
{
if (mode == DR_ISA_ARM_A32) {
uint word = *(uint*)pc;
int disp = word & 0xffffff;
if (TEST(0x800000, disp))
disp |= 0xff000000; /* sign-extend */
return pc + 8 + (disp << 2);
} else {
/* A10,B13,B11,A9:0,B10:0 x2, but B13 and B11 are flipped if A10 is 0 */
/* XXX: share with decoder's TYPE_J_b26_b13_b11_b16_b0 */
ushort valA = *(ushort *)pc;
ushort valB = *(ushort *)(pc + 2);
uint bitA10 = (valA & 0x0400) >> 10;
uint bitB13 = (valB & 0x2000) >> 13;
uint bitB11 = (valB & 0x0800) >> 11;
int disp = valB & 0x7ff; /* B10:0 */
disp |= (valA & 0x3ff) << 11;
disp |= ((bitA10 == 0 ? (bitB11 == 0 ? 1 : 0) : bitB11) << 21);
disp |= ((bitA10 == 0 ? (bitB13 == 0 ? 1 : 0) : bitB13) << 22);
disp |= bitA10 << 23;
if (bitA10 == 1)
disp |= 0xff000000; /* sign-extend */
return pc + 4 + (disp << 1);
}
}
#endif
/* Checks that the call preceding next_retaddr targets the function containing
* frame_addr, or that a cross-module call is indirect, depending on ops.fp_flags.
* If it can't tell, it returns true.
*/
static bool
check_retaddr_targets_frame(app_pc frame_addr, app_pc next_retaddr, bool fp_walk)
{
app_pc frame_mod_start, ra_mod_start;
modname_info_t *frame_name, *ra_name;
app_pc pc = next_retaddr, call_target = NULL;
bool res = true;
symbolized_frame_t frame_sym;
#ifdef ARM
bool is_thumb = TEST(1, (ptr_uint_t)next_retaddr);
pc = (app_pc) ALIGN_BACKWARD(pc, 2);
#endif
LOG(4, "%s: checking does "PFX" => "PFX"\n", __FUNCTION__, next_retaddr, frame_addr);
if (TEST(FP_DO_NOT_VERIFY_CROSS_MOD_IND, ops.fp_flags) &&
!TESTANY(FP_VERIFY_CALL_TARGET | FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags))
return true; /* no checks were requested */
if (!module_lookup(frame_addr, &frame_mod_start, NULL, &frame_name) ||
/* do not check anything targeting a replaced routine */
(frame_addr >= libdr_base && frame_addr < libdr_end) ||
(frame_addr >= libtoolbase && frame_addr < libtoolend))
return true; /* no info */
if (TEST(FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags) ||
!TEST(FP_DO_NOT_VERIFY_CROSS_MOD_IND, ops.fp_flags)) {
/* check whether cross-module */
if (!module_lookup(next_retaddr, &ra_mod_start, NULL, &ra_name)) {
if (TEST(FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags))
return true; /* no module info, and no further checks */
} else if (frame_mod_start == ra_mod_start) {
if (TEST(FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags))
return true; /* only supposed to check cross-module */
} else if (fp_walk && frame_name->abort_fp_walk) {
/* i#703: break fp chain on exiting suspect libs */
LOG(3, "%s: breaking fp chain as module %s is suspect\n", __FUNCTION__,
frame_name->name);
return false;
} else if (!TEST(FP_DO_NOT_VERIFY_CROSS_MOD_IND, ops.fp_flags)) {
/* Only allow a cross-module transition that's an indirect call.
* When done only on scans (and not fp walks), this has minimal
* overhead and rules out bogus frames, in particular from Windows
* system calls (i#1436).
*/
DR_TRY_EXCEPT(dr_get_current_drcontext(), {
IF_X86_ELSE({
if (*(pc - 5) == OP_CALL_DIR) {
pc = *(int*)(pc - 4) + pc;
/* Follow "call; jmp*", where jmp* is 0xff /4.
* Allow endbr{32,64} before.
*/
if (*(int*)pc == IF_X64_ELSE(ENDBR64,ENDBR32))
pc += 4;
if (*pc != OP_JMP_IND ||
((*(pc + 1) >> 3) != 0x14 && *(pc + 1) != 0x25))
res = false;
}
}, {
/* We assume the PLT is always ARM and looks sthg like this:
* 0xe28fc600 add r12, pc, #0, 12
* 0xe28cca08 add r12, r12, #8, 20 ; 0x8000
* 0xe5bcfaf4 ldr pc, [r12, #2804]! ; 0xaf4
*/
if ((is_thumb &&
/* T32 bl <label> */
((*(pc - 3) & 0xf0) == 0xf0) &&
((*(pc - 1) & 0xd0) == 0xd0)) ||
(!is_thumb &&
/* A32 blx <reg> */
((*(pc - 1) & 0x0f) == 0x01) &&
*(pc - 2) == 0x2f &&
*(pc - 3) == 0xff &&
((*(pc - 4) & 0xf0) == 0x30)))
res = false;
else if ((is_thumb &&
/* T32 blx <label> */
((*(pc - 3) & 0xf0) == 0xf0) &&
((*(pc - 1) & 0xd0) == 0xc0)) ||
(!is_thumb &&
/* A32 bl <label> */
((*(pc - 1) & 0x0f) == 0x09))) {
pc = get_call_target(pc - 4, is_thumb);
LOG(4, "%s: call tgt is "PFX"\n", __FUNCTION__, pc);
/* Just look for an add -- rare in func prologue 1st instr */
if (((*(uint*)pc) & 0xe2800000) == 0xe2800000)
res = false;
}
})
}, { /* EXCEPT */
res = false;
LOG(3, "%s: can't read "PFX"\n", __FUNCTION__, pc);
STATS_INC(cstack_is_retaddr_unreadable);
});
LOG(4, "%s: candidate cross-module retaddr "PFX" has %s call\n", __FUNCTION__,
next_retaddr, res ? "indirect" : "direct");
DOSTATS({
if (!res)
STATS_INC(cstack_is_retaddr_tgt_mismatch);
});
if (!res || !TEST(FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags))
return res;
}
}
if (!TESTANY(FP_VERIFY_CALL_TARGET | FP_VERIFY_CROSS_MODULE_TARGET, ops.fp_flags))
return true; /* no further checks */
/* Here we check that the target of the retaddr matches the function
* containing frame_addr. This is risky b/c the retaddr could target
* some other routine that then tailcalls to frame_addr's function.
* At some point it's cheaper and more accurate to read the debug info.
*/
frame_sym.funcoffs = 0;
lookup_func_and_line(&frame_sym, frame_name, frame_addr - frame_mod_start);
DR_TRY_EXCEPT(dr_get_current_drcontext(), {
IF_X86_ELSE({
/* We only support a direct call or a 32-bit memory indirect: not
* feasible to figure out register values in prior frames.
*/
if (*(pc - 5) == OP_CALL_DIR) {
pc = *(int*)(pc - 4) + pc;
/* Follow "call; jmp*", where jmp* is 0xff /4 */
if (*pc == OP_JMP_IND && *(pc + 1) == 0x25) {
int disp32 = *(int*)(pc + 2);
app_pc indir = IF_X64_ELSE(pc + disp32, (app_pc) disp32);
call_target = *(app_pc*)indir;
} else
call_target = pc;
} else if (*(pc - 6) == OP_CALL_IND && *(pc - 5) == 0x15) {
int disp32 = *(int*)(pc - 4);
app_pc indir = IF_X64_ELSE(pc + disp32, (app_pc) disp32);
LOG(4, "%s: call* @ "PFX" targets poi("PFX")\n", __FUNCTION__,
pc - 6, indir);
call_target = *(app_pc*)indir;
/* Account for forwarding stubs like kernel32!HeapCreateStub */
if (*call_target == OP_JMP_DIR_SHORT ||
*call_target == OP_JMP_DIR_LONG) {
/* Bail -- too complex to find where it's going. Sometimes
* there's yet another jmp* intermediary.
*/
LOG(3, "%s: call* targets a stub: bailing\n", __FUNCTION__);
call_target = NULL;
}
}
}, {
/* FIXME i#1726: port to ARM */
})
}, { /* EXCEPT */
res = false;
LOG(3, "%s: can't read "PFX"\n", __FUNCTION__, pc);
STATS_INC(cstack_is_retaddr_unreadable);
});
if (res && call_target != NULL) {
LOG(4, "check: frame="PFX" (func "PFX"), ra="PFX", ra targets "PFX"\n",
frame_addr, frame_addr - frame_sym.funcoffs, next_retaddr, call_target);
res = (frame_sym.funcoffs != 0 && call_target == frame_addr - frame_sym.funcoffs);
}
DOSTATS({
if (!res)
STATS_INC(cstack_is_retaddr_tgt_mismatch);
});
LOG(4, "%s: returning %d\n", __FUNCTION__, res);
return res;
}
static void
fpcache_update(tls_callstack_t *pt, byte *fp_in, byte *fp_out, app_pc retaddr)
{
pt->fpcache[pt->fpcache_idx].input_fp = fp_in;
pt->fpcache[pt->fpcache_idx].output_fp = fp_out;
pt->fpcache[pt->fpcache_idx].retaddr = retaddr;
pt->fpcache_idx = (pt->fpcache_idx + 1) % FPSCAN_CACHE_ENTRIES;
}
static app_pc
find_next_fp(void *drcontext, tls_callstack_t *pt, app_pc fp, app_pc prior_ra,
bool top_frame, app_pc *retaddr/*OUT*/)
{
byte *page_buf = pt->page_buf;
app_pc orig_fp = fp;
ASSERT(page_buf != NULL, "thread's page_buf is not initialized");
/* Heuristic: scan stack for retaddr, or fp + retaddr pair */
ASSERT(fp != NULL, "internal callstack-finding error");
/* PR 416281: word-align fp so page assumptions hold */
fp = (app_pc) ALIGN_BACKWARD(fp, sizeof(app_pc));
/* Optimization: do not repeatedly walk the base of the stack beyond
* the lowest frame, querying for modules.
* FIXME: for now we use the lowest frame found in the first callstack
* for this thread: but that can lead to erroneously prematurely
* terminating callstacks so we should keep our eyes open.
* Perhaps we should replace this w/ the actual stack bounds?
*/
if (pt != NULL && pt->stack_lowest_frame != NULL &&
((fp >= pt->stack_lowest_frame &&
(fp - pt->stack_lowest_frame) < ops.stack_swap_threshold) ||
/* if hit a zero or bad fp near the lowest frame, don't scan.
* some apps like perlbmk have some weird loader callstacks
* and then a solid bottom frame so try not to scan every time.
* xref i#246.
*/
(!top_frame && (pt->stack_lowest_frame - fp) < FP_NO_SCAN_NEAR_LOW_THRESH))) {
LOG(4, "find_next_fp: aborting b/c "PFX" is beyond stack_lowest_frame "PFX"\n",
fp, pt->stack_lowest_frame);
return NULL;
}
/* Check the cache. We verify by reading the retaddr. With
* -zero_retaddr, we'll only be wrong if there's a non-retaddr
* slot holding this retaddr and the real next retaddr is in front
* of it. With -no_zero_retaddr, there are more chances of
* skipping frames, so we disable the cache in that scenario.
*
* XXX: we should also try a structured cache of the last callstack,
* which could result in greater speedup: but is also more complex
* to implement.
*/
if (ops.old_retaddrs_zeroed) {
uint i;
for (i = 0; i < FPSCAN_CACHE_ENTRIES; i++) {
if (orig_fp == pt->fpcache[i].input_fp) {
app_pc ra;
if (safe_read(pt->fpcache[i].output_fp + sizeof(app_pc),
sizeof(ra), &ra) &&
ra == pt->fpcache[i].retaddr &&
/* i#1231: we don't zero for full mode but we want the cache */
(ops.is_dword_defined == NULL ||
ops.is_dword_defined(drcontext,
pt->fpcache[i].output_fp + sizeof(app_pc)))) {
if (retaddr != NULL)
*retaddr = ra;
LOG(4, "find_next_fp: cache hit "PFX" => "PFX", ra="PFX"\n",
orig_fp, pt->fpcache[i].output_fp, ra);
/* Make sure we don't clobber this hit on our next miss */
pt->fpcache_idx = (i + 1) % FPSCAN_CACHE_ENTRIES;
STATS_INC(find_next_fp_cache_hits);
return pt->fpcache[i].output_fp;
} else {
pt->fpcache[i].input_fp = NULL; /* invalidate */
}
}
}
}
/* PR 454536: dr_memory_is_readable() is racy so we use a safe_read().
* On Windows safe_read() costs 1 system call: perhaps DR should
* use try/except there like on Linux?
* We use stack_lowest_frame, based on the stack bounds, to avoid
* incurring a fault (checked up above).
* XXX: should support partial safe read for invalid page next to stack
*/
if (safe_read((app_pc)ALIGN_BACKWARD(fp, PAGE_SIZE), PAGE_SIZE, page_buf)) {
app_pc buf_pg = (app_pc) ALIGN_BACKWARD(fp, PAGE_SIZE);
app_pc tos = fp;
app_pc sp;
app_pc slot0 = 0, slot1;
bool match, match_next_frame, fp_defined = false;
size_t ret_offs = TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags) ? sizeof(app_pc) : 0;
app_pc stop = tos + ops.fp_scan_sz;
IF_NOT_X64(uint conseq_wchar = 0;)
#ifdef WINDOWS
/* if on original thread stack, stop at limit (i#588) */
TEB *teb = get_TEB();
if (teb != NULL && fp >= (app_pc)teb->StackLimit && fp < (app_pc)teb->StackBase)
stop = (app_pc)teb->StackBase;
#endif
/* Scan one page worth and look for potential fp,retaddr pair */
STATS_INC(find_next_fp_scans);
/* We only look at fp if TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags) */
LOG(4, "find_next_fp: scanning %p-%p\n", tos, stop);
for (sp = tos; sp < stop; sp+=sizeof(app_pc)) {
match = false;
match_next_frame = false;
if (retaddr != NULL)
*retaddr = NULL;
if (TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags)) {
ASSERT((app_pc)ALIGN_BACKWARD(sp, PAGE_SIZE) == buf_pg, "buf error");
if (ops.is_dword_defined != NULL)
fp_defined = ops.is_dword_defined(drcontext, sp);
if (fp_defined)
slot0 = *((app_pc*)&page_buf[sp - buf_pg]);
}
/* Retrieve next page if slot1 will touch it */
if ((app_pc)ALIGN_BACKWARD(sp + ret_offs, PAGE_SIZE) != buf_pg) {
buf_pg = (app_pc) ALIGN_BACKWARD(sp + ret_offs, PAGE_SIZE);
if (!safe_read(buf_pg, PAGE_SIZE, page_buf)) {
LOG(4, "find_next_fp: returning NULL b/c couldn't read next page\n");
break;
}
}
LOG(5, "find_next_fp: considering sp="PFX"\n", sp);
if (TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags) && !fp_defined) {
IF_NOT_X64(conseq_wchar = 0;)
continue;
}
if (ops.is_dword_defined != NULL &&
!ops.is_dword_defined(drcontext, sp + ret_offs)) {
IF_NOT_X64(conseq_wchar = 0;)
continue; /* retaddr not defined */
}
if (!TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags) ||
(slot0 > tos && slot0 - tos < ops.stack_swap_threshold)) {
byte *buf_ptr = (byte *) &page_buf[(sp + ret_offs) - buf_pg];
slot1 = *((app_pc*)buf_ptr);
/* We should only consider retaddr in code section but
* let's keep it simple for now.
* We ignore DGC: perhaps a dr_is_executable_memory() could
* be used instead of checking modules.
* OPT: keep all modules in hashtable for quicker check
* that doesn't require alloc+free of heap */
#ifndef X64
if (IS_WCHARx2_AT(buf_ptr))
conseq_wchar += 2;
else
conseq_wchar = 0;
#endif
if (is_retaddr(slot1, true/*i#1217*/)) {
match = true;
#ifndef X64
/* Check for wide strings or *_STRING structures (i#1331, i#1271).
* XXX: these are quite difficult to construct authentic tests
* for so unfortunately we do not have automated tests and have
* tested only by running Chromium unit_tests.
*/
if (conseq_wchar > 0) {
/* i#1331: rule out wide strings that have
* address-look-alike sequences in the middle.
*/
# define STACK_WIDE_STRING_MIN_LEN 16
# define STACK_WIDE_STRING_MAX_READ 512
wchar_t *str = (wchar_t*) (buf_ptr + sizeof(app_pc));
size_t len =
walk_wide_string(str, (wchar_t *)(page_buf + PAGE_SIZE) - str,
(wchar_t*)(sp + ret_offs),
STACK_WIDE_STRING_MAX_READ);
if (len > 0 && len + conseq_wchar >= STACK_WIDE_STRING_MIN_LEN) {
LOG(2, "find_next_fp: ra "PFX"@"PFX" really wchar '%S'\n",
slot1, sp, str - conseq_wchar);
STATS_INC(find_next_fp_strings);
match = false;
} else {
/* i#1271: rule out *_STRING data struct with
* 2 short fields followed by a buffer pointer.
* We assume the 2 shorts will match IS_WCHARx2_AT.
* str points at the buffer field.
*/
wchar_t *strbuf;
if (safe_read(str, sizeof(strbuf), &strbuf) &&
walk_wide_string(strbuf, 0/*all unsafe*/, strbuf,
STACK_WIDE_STRING_MAX_READ) >=
STACK_WIDE_STRING_MIN_LEN) {
LOG(2, "find_next_fp: ra "PFX"@"PFX
" really *_STRING '%S'\n", slot1, sp, strbuf);
STATS_INC(find_next_fp_string_structs);
match = false;
}
}
}
#endif
#ifdef WINDOWS
} else if (top_frame && TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags)) {
/* PR 475715: msvcr80!malloc pushes ebx and then ebp! It then
* uses ebp as scratch, so we end up here for the top frame
* of a leak callstack.
*/
slot1 = *((app_pc*)&page_buf[(sp + 2*ret_offs) - buf_pg]);
if (is_retaddr(slot1, true/*i#1217*/)) {
match = true;
/* Do extra check for this case even if flags don't call for it */
match_next_frame = true;
/* Since there's a gap we return the retaddr */
ASSERT(retaddr != NULL, "invalid arg");
*retaddr = slot1;
}
#endif
}
}
if (match && prior_ra != NULL &&
!TEST(FP_DO_NOT_VERIFY_TARGET_IN_SCAN, ops.fp_flags) &&
!check_retaddr_targets_frame(prior_ra, slot1, false))
match = false;
if (match) {
app_pc parent_ret_ptr = slot0 + ret_offs;
app_pc parent_ret;
if (!TEST(FP_SEARCH_REQUIRE_FP, ops.fp_flags)) {
/* caller expects fp,ra pair */
LOG(4, "find_next_fp "PFX" => "PFX", ra="PFX"\n",
orig_fp, sp - sizeof(app_pc), slot1);
fpcache_update(pt, orig_fp, sp - sizeof(app_pc), slot1);
return sp - sizeof(app_pc);
}
if ((TEST(FP_SEARCH_MATCH_SINGLE_FRAME, ops.fp_flags) &&
!match_next_frame)) {
LOG(4, "find_next_fp "PFX" => "PFX", ra="PFX"\n",
orig_fp, sp, slot1);
fpcache_update(pt, orig_fp, sp, slot1);
return sp;
}
/* Require the next retaddr to be in a module as well, to avoid
* continuing past the bottom frame on ESXi (xref PR 469043)
*/
if (buf_pg == (app_pc)ALIGN_BACKWARD(parent_ret_ptr, PAGE_SIZE)) {
parent_ret = *((app_pc*)&page_buf[parent_ret_ptr - buf_pg]);
} else {
if (!safe_read(parent_ret_ptr, sizeof(parent_ret), &parent_ret))
parent_ret = NULL;
}
if (parent_ret != NULL && is_retaddr(parent_ret, true/*i#1217*/)) {
LOG(4, "find_next_fp "PFX" => "PFX", ra="PFX"\n",
orig_fp, sp, slot1);
fpcache_update(pt, orig_fp, sp, slot1);
return sp;
}
match = false;
}
}
LOG(4, "find_next_fp: returning NULL b/c didn't find fp,ra pair\n");
} else
LOG(4, "find_next_fp: returning NULL b/c couldn't read stack page\n");
return NULL;
}
#ifdef HAVE_LIBUNWIND_H
/* We have ELF unwind info support in drcallstack now.
* XXX i#1222: on win64, we should similarly use SEH unwind tables to walk the callstack.
*/
void
walk_unwind_info(char *buf, size_t bufsz, size_t *sofar, dr_mcontext_t *mc,
bool print_fps, packed_callstack_t *pcs, int num_frames_printed,
bool for_log, uint max_frames,
bool (*frame_cb)(app_pc pc, byte *fp, void *user_data), void *user_data)
{
int num = num_frames_printed;
ssize_t len = 0;
size_t prev_sofar = 0;
bool first_iter = true;
bool last_frame = false;
drcallstack_walk_t *walk;
/* XXX: Presumably we don't need the !FP_DO_NOT_SKIP_VSYSCALL_PUSH code? Or do we
* need to integrate this further into print_callstack()?
*/
LOG(4, "drcallstack init pc=%p sp=%p fp=%p\n", mc->pc, MC_SP_REG(mc), MC_FP_REG(mc));
drcallstack_status_t res = drcallstack_init_walk(mc, &walk);
ASSERT(res == DRCALLSTACK_SUCCESS, "failed to init drcallstack walk");
drcallstack_frame_t frame = {
sizeof(frame),
};
do {
res = drcallstack_next_frame(walk, &frame);
if (res != DRCALLSTACK_SUCCESS) {
LOG(3, "drcallstack error %d\n", res);
break;
}
/* XXX: Measure perf: do we want to use fpcache_update()? */
if (buf != NULL) {
prev_sofar = *sofar;
if (for_log)
BUFPRINT(buf, bufsz, *sofar, len, FP_PREFIX"#%2d ", num);
if (print_fps) {
/* We don't have the parent FP here. */
BUFPRINT(buf, bufsz, *sofar, len, "fp=" PFX, frame.sp);
}
}
if (pcs != NULL && first_iter && num == 1 &&
PCS_FRAME_LOC(pcs, 0).addr == frame.pc) {
/* caller already added this frame */
if (buf != NULL) /* undo the fp= print */
*sofar = prev_sofar;
} else if ((pcs == NULL &&
print_address_common(buf, bufsz, sofar, frame.pc, NULL,
!TEST(FP_SHOW_NON_MODULE_FRAMES, ops.fp_flags),
true, for_log, &last_frame, num)) ||
(pcs != NULL &&
address_to_frame(NULL, pcs, frame.pc, NULL,
!TEST(FP_SHOW_NON_MODULE_FRAMES, ops.fp_flags),
true, pcs->num_frames))) {
num++;
if (frame_cb != NULL) {
if (!(*frame_cb)(frame.pc, (app_pc)frame.sp, user_data))
break;
}
if (last_frame)
break;
} else {
/* Unlesss FP_SHOW_NON_MODULE_FRAMES, we do not include not-in-a-module
* addresses.
* XXX: Should we integrate fully with the raw scan/frame-walk code
* in print_callstack() so we can interleave frames from each method?
*/
LOG(4, "Skipping sp=%p pc=%p <unknown-module>\n", frame.sp, frame.pc);
}
if (num >= max_frames || (pcs != NULL && pcs->num_frames >= max_frames)) {
if (buf != NULL)
BUFPRINT(buf, bufsz, *sofar, len, FP_PREFIX"..."NL);
LOG(4, "truncating callstack: hit max frames %d %d\n",
num, pcs == NULL ? -1 : pcs->num_frames);
break;
}
} while (res == DRCALLSTACK_SUCCESS);
if (res != DRCALLSTACK_NO_MORE_FRAMES)
LOG(3, "final drcallstack error %d\n", res);
res = drcallstack_cleanup_walk(walk);
DR_ASSERT(res == DRCALLSTACK_SUCCESS);
if (num == 0 && buf != NULL && print_fps) {
BUFPRINT(buf, bufsz, *sofar, len, FP_PREFIX"<call stack walk failed>"NL);
}
if (buf != NULL) {
buf[bufsz-2] = '\n';
buf[bufsz-1] = '\0';
}
}
#endif
void
print_callstack(char *buf, size_t bufsz, size_t *sofar, dr_mcontext_t *mc,
bool print_fps, packed_callstack_t *pcs, int num_frames_printed,
bool for_log, uint max_frames,
bool (*frame_cb)(app_pc pc, byte *fp, void *user_data), void *user_data)
{
void *drcontext = dr_get_current_drcontext();
tls_callstack_t *pt = (tls_callstack_t *)
((drcontext == NULL) ? NULL : drmgr_get_tls_field(drcontext, tls_idx_callstack));
int num = num_frames_printed; /* PR 475453 - wrong call stack depths */
ssize_t len = 0;
ptr_uint_t *pc = (mc == NULL ? NULL : (ptr_uint_t *) MC_FP_REG(mc));
size_t prev_sofar = 0;
struct {
app_pc next_fp;
app_pc retaddr;
} appdata;
app_pc custom_retaddr = NULL;
app_pc prev_lowest_frame = NULL, lowest_frame = NULL;
bool first_iter = true;
bool have_appdata = false;
bool scanned = false;
bool last_frame = false;
byte *tos = (mc == NULL ? NULL : (byte *) MC_SP_REG(mc));
ASSERT(max_frames <= ops.global_max_frames, "max_frames > global_max_frames");
if (mc == NULL)
goto print_callstack_done;
ASSERT(num == 0 || num == 1, "only 1 frame can already be printed");
ASSERT((buf != NULL && sofar != NULL && pcs == NULL) ||
(buf == NULL && sofar == NULL && pcs != NULL),
"print_callstack: can't pass buf and pcs");
/* XXX: for ARM should we use %lr, which drwrap_replace_native stored?
* The problem is that the current %lr value might also be on the stack,
* and how would we know whether to skip it?
*/
#ifdef DEBUG
if (mc != NULL && ops.dump_app_stack > 0) {
dump_app_stack(drcontext, pt, mc, ops.dump_app_stack,
(pcs == NULL ? NULL : PCS_FRAME_LOC(pcs, 0).addr));
}
#endif
STATS_INC(callstack_walks);
LOG(4, "initial fp="PFX" vs sp="PFX" def=%d\n",
MC_FP_REG(mc), MC_SP_REG(mc),
(ops.is_dword_defined == NULL) ?
0 : ops.is_dword_defined(drcontext, (byte*)MC_FP_REG(mc)));
#ifdef HAVE_LIBUNWIND_H
/* If the start pc is not in a module we bail on using libunwind.
* XXX: Should we interleave the methods and try libunwind on the next frame
* if we find a valid first one by scanning?
* If we change this is_in_module() call we need to change the corresponding
* one in record_error() deciding whether to zero the fp.
*/
if (ops.use_unwind && is_in_module(mc->pc)) {
walk_unwind_info(buf, bufsz, sofar, mc, print_fps, pcs, num_frames_printed,
for_log, max_frames, frame_cb, user_data);
return;
}
#endif
if (MC_SP_REG(mc) != 0 &&
(!ALIGNED(MC_FP_REG(mc), sizeof(void*)) ||
MC_FP_REG(mc) < MC_SP_REG(mc) ||
MC_FP_REG(mc) - MC_SP_REG(mc) > ops.stack_swap_threshold ||
(ops.ignore_xbp != NULL &&
ops.ignore_xbp(drcontext, mc)) ||
#ifdef WINDOWS
/* don't trust ebp when in Windows syscall wrapper */
(pcs != NULL && pcs->first_is_syscall) ||
#endif
/* avoid stale fp,ra pair (i#640) */
(ops.is_dword_defined != NULL &&
(!ops.is_dword_defined(drcontext, (byte*)MC_FP_REG(mc)) ||
!ops.is_dword_defined(drcontext, (byte*)MC_FP_REG(mc) + sizeof(void*)))) ||
(MC_FP_REG(mc) != 0 &&
(!safe_read((byte *)MC_FP_REG(mc), sizeof(appdata), &appdata) ||
/* check the very first retaddr since ebp might point at
* a misleading stack slot
*/
(!TEST(FP_DO_NOT_CHECK_FIRST_RETADDR, ops.fp_flags) &&
!is_retaddr(appdata.retaddr, false/*include drmem*/)))))) {
/* We may start out in the middle of a frameless function that is
* using ebp for other purposes. Heuristic: scan stack for fp + retaddr.
*/
LOG(4, "find_next_fp b/c starting w/ non-fp ebp "PFX" (def=%d %d)\n",
MC_FP_REG(mc), ops.is_dword_defined == NULL ?
0 : ops.is_dword_defined(drcontext, (byte*)MC_FP_REG(mc)),
ops.is_dword_defined == NULL ?
0 : ops.is_dword_defined(drcontext, (byte*)MC_FP_REG(mc) + sizeof(void*)));
#if defined(LINUX) && !defined(X64)
if (pcs != NULL && pcs->first_is_syscall &&
!TEST(FP_DO_NOT_SKIP_VSYSCALL_PUSH, ops.fp_flags)) {
/* i#1265: skip the vsyscall sysenter "push ebp" to avoid skipping
* over a frame, as the libc routine that invoked the syscall often
* doesn't have a fp. We want to only apply this when in vsyscall,
* but even w/ a sysenter/syscall gateway there are still syscalls
* that use OP_int: thus we check for TOS holding a retaddr (should
* be relatively rare to get here so overhead not critical).
*/
drsys_gateway_t gateway;
if (drsys_syscall_gateway(&gateway) == DRMF_SUCCESS &&
(gateway == DRSYS_GATEWAY_SYSENTER || gateway == DRSYS_GATEWAY_SYSCALL) &&
safe_read(tos, sizeof(custom_retaddr), &custom_retaddr) &&
!is_retaddr(custom_retaddr, true/*exclude tool*/)) {
tos += sizeof(app_pc);
}
}
#endif
pc = (ptr_uint_t *) find_next_fp(drcontext, pt, tos,
/* Pass in the top frame for prior_ra */
(pcs != NULL && num_frames_printed == 1) ?
PCS_FRAME_LOC(pcs, 0).addr : NULL,
true/*top frame*/,
&custom_retaddr);
scanned = true;
}
while (pc != NULL) {
if (!have_appdata &&
!safe_read((byte *)pc, sizeof(appdata), &appdata)) {
LOG(4, "truncating callstack: can't read "PFX"\n", pc);
break;
}
LOG(4, "print_callstack: pc="PFX" => FP="PFX", RA="PFX"\n",
pc, appdata.next_fp, appdata.retaddr);
/* if we scanned and took the top dword as retaddr, don't use beyond-TOS as FP */
if ((byte *)pc < tos)
appdata.next_fp = NULL;
if (custom_retaddr != NULL) {
/* Support frames where there's a gap between ebp and retaddr (PR 475715) */
appdata.retaddr = custom_retaddr;
custom_retaddr = NULL;
}
if (buf != NULL) {
prev_sofar = *sofar;
if (for_log)
BUFPRINT(buf, bufsz, *sofar, len, FP_PREFIX"#%2d ", num);
if (print_fps) {
BUFPRINT(buf, bufsz, *sofar, len, "fp="PFX" parent="PFX" ",
pc, appdata.next_fp);
}
}
prev_lowest_frame = lowest_frame;
lowest_frame = (app_pc) pc;
/* Unlesss FP_SHOW_NON_MODULE_FRAMES, we do not include not-in-a-module
* addresses. Perhaps something like dr_is_executable_memory() could
* help us show non-module actual code: for now we skip it and just use
* it to find the next real frame w/ a module, and to skip crap at the
* base of callstacks.
*/
/* PR 543863: subtract one from retaddrs in callstacks so the line# is
* for the call and not for the next source code line, but only for
* symbol lookup so we still display a valid instr addr.
*/
if (pcs != NULL && first_iter && num == 1 &&
PCS_FRAME_LOC(pcs, 0).addr == appdata.retaddr) {
/* caller already added this frame */
if (buf != NULL) /* undo the fp= print */
*sofar = prev_sofar;
} else if ((pcs == NULL &&
print_address_common(buf, bufsz, sofar, appdata.retaddr, NULL,
!TEST(FP_SHOW_NON_MODULE_FRAMES, ops.fp_flags),
true, for_log, &last_frame, num)) ||
(pcs != NULL &&
address_to_frame(NULL, pcs, appdata.retaddr, NULL,
!TEST(FP_SHOW_NON_MODULE_FRAMES, ops.fp_flags),
true, pcs->num_frames))) {
num++;
if (frame_cb != NULL) {
if (!(*frame_cb)(appdata.retaddr, appdata.next_fp, user_data))
break;
}
if (last_frame)
break;
if (appdata.retaddr == pt->stack_lowest_retaddr &&
pt->stack_lowest_retaddr != NULL) {
LOG(4, "ending callstack: hit stack_lowest_retaddr "PFX"\n",
appdata.retaddr);
break;
}
} else {
lowest_frame = prev_lowest_frame; /* be sure to undo (i#1186) */
if (buf != NULL) /* undo the fp= print */
*sofar = prev_sofar;
if (first_iter) { /* don't trust "num==num_frames_printed" as test for 1st */
/* We may have started in a frameless function using ebp for
* other purposes but it happens to point to higher on the stack.
* Start over w/ top of stack to avoid skipping a frame (i#521).
*/
LOG(4, "find_next_fp "PFX" b/c starting w/ non-fp ebp "PFX"\n",
MC_SP_REG(mc), MC_FP_REG(mc));
pc = (ptr_uint_t *) find_next_fp(drcontext, pt,
(app_pc)MC_SP_REG(mc), NULL,
true/*top frame*/, &custom_retaddr);
scanned = true;
first_iter = false; /* don't loop */
continue;
}
}
first_iter = false;
/* pcs->num_frames could be larger if frames were printed before this routine */
if (num >= max_frames || (pcs != NULL && pcs->num_frames >= max_frames)) {
if (buf != NULL)
BUFPRINT(buf, bufsz, *sofar, len, FP_PREFIX"..."NL);
LOG(4, "truncating callstack: hit max frames %d %d\n",
num, pcs == NULL ? -1 : pcs->num_frames);
break;
}
/* yes I've seen weird recursive cases before */
if (pc == (ptr_uint_t *) appdata.next_fp) {
LOG(4, "truncating callstack: recursion\n");
break;
}
have_appdata = false;
if (appdata.next_fp == 0) {
/* We definitely need to search for the first frame, and also in the
* middle to cross loader/glue stubs/thunks or a signal/exception
* frames (though for sigaltstck we'll stop). However, on ESXi,
* searching past a 0 (the parent of the _start base frame) finds
* some data structures low on the stack (high addresses) that match
* its heuristics but are actually loader data structures; they make
* all callstacks erroneously long.
*/
if (!TEST(FP_STOP_AT_BAD_ZERO_FRAME, ops.fp_flags)) {
LOG(4, "find_next_fp b/c hit zero fp\n");
pc = (ptr_uint_t *) find_next_fp(drcontext, pt,
((app_pc)pc) + sizeof(appdata),
appdata.retaddr, false/*!top*/, NULL);
scanned = true;
} else {
LOG(4, "truncating callstack: zero frame ptr\n");
break;
}
} else {
/* appdata.next_fp is candidate */
bool out_of_range =
(appdata.next_fp < (app_pc)pc ||
/* i#1042: 0xffffffff`ffffffff - 0x00000000`00aaf1e0 >= 0x20000
* return false, so we cast it to ptr_uint_t.
*/
(ptr_uint_t)(appdata.next_fp - (app_pc)pc) >=
ops.stack_swap_threshold);
app_pc prior_ra = appdata.retaddr;
app_pc next_fp = appdata.next_fp;
if (!out_of_range &&
!safe_read((byte *)next_fp, sizeof(appdata), &appdata)) {
LOG(4, "truncating callstack: can't read "PFX"\n", pc);
break;
}
if (out_of_range ||
(!TEST(FP_DO_NOT_CHECK_RETADDR, ops.fp_flags) &&
/* checking retaddr on regular fp chain walk is a 40% perf hit
* on cfrac and roboop so we avoid it if we've never had to
* do a scan, trusting the fp's to be genuine (overridden by
* FP_CHECK_RETADDR_PRE_SCAN)
*/
(scanned || TEST(FP_CHECK_RETADDR_PRE_SCAN, ops.fp_flags)) &&
!is_retaddr(appdata.retaddr, false/*include drmem*/))) {
if (!TEST(FP_STOP_AT_BAD_NONZERO_FRAME, ops.fp_flags)) {
LOG(4, "find_next_fp "PFX" b/c hit bad non-zero fp "PFX"\n",
((app_pc)pc) + sizeof(appdata), appdata.next_fp);
pc = (ptr_uint_t *) find_next_fp(drcontext, pt,
((app_pc)pc) + sizeof(appdata),
prior_ra, false/*!top*/, NULL);
scanned = true;
} else {
LOG(4, "truncating callstack: bad frame ptr "PFX"\n", next_fp);
break;
}
} else if (TEST(FP_DO_NOT_WALK_FP, ops.fp_flags) ||
(!TEST(FP_DO_NOT_VERIFY_TARGET_IN_WALK, ops.fp_flags) &&
!check_retaddr_targets_frame(prior_ra, appdata.retaddr, true))) {
LOG(4, "find_next_fp "PFX" b/c not walking fp, or skips "PFX"\n",
((app_pc)pc) + sizeof(appdata), appdata.next_fp);
pc = (ptr_uint_t *) find_next_fp(drcontext, pt,
((app_pc)pc) + sizeof(appdata),
prior_ra, false/*!top*/, NULL);
scanned = true;
} else {
have_appdata = true;
pc = (ptr_uint_t *) next_fp;
}
}
if (pc == NULL)
LOG(4, "truncating callstack: can't find next fp\n");
}
print_callstack_done:
if (num == 0 && buf != NULL && print_fps) {
BUFPRINT(buf, bufsz, *sofar, len,
FP_PREFIX"<call stack frame ptr "PFX" unreadable>"NL, pc);
}
if (pt != NULL && lowest_frame > pt->stack_lowest_frame) {
if (pt->stack_lowest_frame == NULL) {
/* For main thread we couldn't query esp before, so do so now (i#1495) */
callstack_set_lowest_frame(drcontext);
}
if (lowest_frame > pt->stack_lowest_frame) {
pt->stack_lowest_frame = lowest_frame;
LOG(4, "set lowest frame to "PFX"\n", lowest_frame);
}
}
if (buf != NULL) {
buf[bufsz-2] = '\n';
buf[bufsz-1] = '\0';
}
}
void
print_buffer(file_t f, char *buf)
{
/* PR 427929: avoid truncation if over DR's internal buffer limit
* by doing direct write
*/
/* PR 458200: for PR 456181 we'd like this to be an atomic
* write. Since our writes are smaller than any disk buffer or kernel
* buffer (definitely smaller than 1 page), we should never get a partial
* write. All we need to do is check for EINTR and retry.
* Even if there is a chance of partial write it should be quite rare
* and the consequences are simply a messed-up callstack: long-term
* when we do symbols online then it will be visible to the user
* and should be re-constructible by the user.
*/
size_t sz = strlen(buf);
ssize_t res;
if (f == INVALID_FILE) {
ASSERT(IF_WINDOWS(f == STDERR ||) false, "print_buffer invalid file");
return;
}
while (true) {
res = dr_write_file(f, buf, sz);
if (res < 0) {
#ifdef UNIX
/* DR converts Mac's +errno,CF to -errno
* XXX: should we document that dr_write_file() returns -errno
* on failure on both Linux and Mac?
*/
/* FIXME: haven't tested this */
if (res == -EINTR)
continue;
#endif
REPORT_DISK_ERROR();
}
/* getting weird failures on stderr: aborting silently on those */
ASSERT(IF_WINDOWS(f == STDERR ||) res == sz, "dr_write_file partial write");
break;
}
}
#if DEBUG
/* Prints a callstack using pt->errbuf and prints to pt->f if f == INVALID_FILE,
* else prints to the f passed in.
*/
void
print_callstack_to_file(void *drcontext, dr_mcontext_t *mc, app_pc pc, file_t f,
uint max_frames)
{
size_t sofar = 0;
ssize_t len;
tls_callstack_t *pt = (tls_callstack_t *)
((drcontext == NULL) ? NULL : drmgr_get_tls_field(drcontext, tls_idx_callstack));
/* mc and pc will be NULL for startup heap iter */
if (pt == NULL) {
LOG(1, "Can't report callstack as pt is NULL\n");
return;
}
ASSERT(max_frames <= ops.global_max_frames, "max_frames > global_max_frames");
BUFPRINT(pt->errbuf, pt->errbufsz, sofar, len, "# 0 ");
print_address(pt->errbuf, pt->errbufsz, &sofar, pc, NULL, true/*for log*/);
print_callstack(pt->errbuf, pt->errbufsz, &sofar, mc,
true/*incl fp*/, NULL, 1, true, max_frames, NULL, NULL);
print_buffer(f == INVALID_FILE ? LOGFILE_GET(drcontext) : f, pt->errbuf);
}
#endif /* DEBUG */
app_pc
callstack_next_retaddr(dr_mcontext_t *mc)
{
app_pc res = NULL;
packed_callstack_t *pcs;
packed_callstack_record(&pcs, mc, NULL, 1);
if (pcs->num_frames > 0)
res = PCS_FRAME_LOC(pcs, 0).addr;
packed_callstack_destroy(pcs);
return res;
}
/****************************************************************************
* Binary callstacks for storing callstacks of allocation sites.
*/
/* Used for standalone allocation, rather than printing as part of an error report.
* Caller must call free_callstack() to free buf_out.
*/
void
packed_callstack_record(packed_callstack_t **pcs_out/*out*/, dr_mcontext_t *mc,
app_loc_t *loc, uint max_frames)
{
packed_callstack_t *pcs = (packed_callstack_t *)
global_alloc(sizeof(*pcs), HEAPSTAT_CALLSTACK);
size_t sz_out;
int num_frames_printed = 0;
ASSERT(max_frames <= ops.global_max_frames, "max_frames > global_max_frames");
ASSERT(pcs_out != NULL, "invalid args");
memset(pcs, 0, sizeof(*pcs));
pcs->refcount = 1;
if (modname_array_end < MAX_MODNAMES_STORED) {
pcs->is_packed = true;
pcs->frames.packed = (packed_frame_t *)
global_alloc(sizeof(*pcs->frames.packed) * max_frames,
HEAPSTAT_CALLSTACK);
} else {
pcs->is_packed = false;
pcs->frames.full = (full_frame_t *)
global_alloc(sizeof(*pcs->frames.full) * max_frames, HEAPSTAT_CALLSTACK);
}
if (loc != NULL) {
if (loc->type == APP_LOC_SYSCALL) {
/* For syscalls, we use index 0 and external storage.
* We copy from loc. The syscall aux identifier (PR 525269)
* is supposed to be a string literal and so we can clone it
* and compare it by just using its address.
*/
pcs->first_is_syscall = true;
if (pcs->is_packed) {
pcs->frames.packed[0].modname_idx = 0;
pcs->frames.packed[0].loc.sysloc = (syscall_loc_t *)
global_alloc(sizeof(syscall_loc_t), HEAPSTAT_CALLSTACK);
*pcs->frames.packed[0].loc.sysloc = loc->u.syscall;
} else {
pcs->frames.full[0].modname = (modname_info_t *) &MODNAME_INFO_SYSCALL;
pcs->frames.full[0].loc.sysloc = (syscall_loc_t *)
global_alloc(sizeof(syscall_loc_t), HEAPSTAT_CALLSTACK);
*pcs->frames.full[0].loc.sysloc = loc->u.syscall;
}
pcs->num_frames++;
} else {
app_pc pc = loc_to_pc(loc);
/* The caller should have already set mc->pc. */
ASSERT(loc->type == APP_LOC_PC, "unknown loc type");
address_to_frame(NULL, pcs, pc, NULL, false, false, 0);
}
num_frames_printed = 1;
}
print_callstack(NULL, 0, NULL, mc, false, pcs, num_frames_printed, false,
max_frames, NULL, NULL);
if (pcs->is_packed) {
packed_frame_t *frames_out;
sz_out = sizeof(*pcs->frames.packed) * pcs->num_frames;
if (sz_out == 0)
frames_out = NULL;
else {
frames_out = (packed_frame_t *) global_alloc(sz_out, HEAPSTAT_CALLSTACK);
memcpy(frames_out, pcs->frames.packed, sz_out);
}
global_free(pcs->frames.packed, sizeof(*pcs->frames.packed) * max_frames,
HEAPSTAT_CALLSTACK);
pcs->frames.packed = frames_out;
} else {
full_frame_t *frames_out;
sz_out = sizeof(*pcs->frames.full) * pcs->num_frames;
if (sz_out == 0)
frames_out = NULL;
else {
frames_out = (full_frame_t *) global_alloc(sz_out, HEAPSTAT_CALLSTACK);
memcpy(frames_out, pcs->frames.full, sz_out);
}
global_free(pcs->frames.full, sizeof(*pcs->frames.full) * max_frames,
HEAPSTAT_CALLSTACK);
pcs->frames.full = frames_out;
}
*pcs_out = pcs;
}
void
packed_callstack_first_frame_retaddr(packed_callstack_t *pcs)
{
pcs->first_is_retaddr = true;
}
/* Returns false if a syscall. If returns true, also fills in the OUT params. */
static bool
packed_callstack_frame_modinfo(packed_callstack_t *pcs, uint frame,
modname_info_t **name_info DR_PARAM_OUT,
size_t *modoffs DR_PARAM_OUT)
{
modname_info_t *info = NULL;
size_t offs = 0;
ASSERT(pcs != NULL, "invalid arg");
ASSERT(frame < pcs->num_frames, "invalid arg");
/* modname_idx==0 or modname==NULL is the code for a system call */
if (!pcs->is_packed) {
info = pcs->frames.full[frame].modname;
if (info == &MODNAME_INFO_SYSCALL) {
ASSERT(frame == 0, "syscall should only be top frame");
ASSERT(pcs->first_is_syscall, "flag not set");
return false;
}
offs = pcs->frames.full[frame].modoffs;
} else {
if (pcs->frames.packed[frame].modname_idx == 0) {
ASSERT(frame == 0, "syscall should only be top frame");
ASSERT(pcs->first_is_syscall, "flag not set");
return false;
}
if (pcs->frames.packed[frame].modoffs < MAX_MODOFFS_STORED) {
/* If module is larger than 16M, we need to adjust offset.
* The hashtable holds the first index.
*/
int start_idx;
int idx = pcs->frames.packed[frame].modname_idx;
ASSERT(idx < MAX_MODNAMES_STORED, "invalid modname idx");
offs = pcs->frames.packed[frame].modoffs;
info = modname_array[idx];
start_idx = info->index;
ASSERT(start_idx != 0, "module in array must be in table");
if (start_idx < idx)
offs += (idx - start_idx) * MAX_MODOFFS_STORED;
}
}
if (name_info != NULL)
*name_info = info;
if (modoffs != NULL)
*modoffs = offs;
return true;
}
static void
packed_frame_to_symbolized(packed_callstack_t *pcs DR_PARAM_IN,
symbolized_frame_t *frame DR_PARAM_OUT, uint idx)
{
modname_info_t *info = NULL;
size_t offs;
init_symbolized_frame(frame, idx);
if (!packed_callstack_frame_modinfo(pcs, idx, &info, &offs)) {
size_t sofar = 0;
ssize_t len;
const char *name = "<unknown>";
frame->loc.type = APP_LOC_SYSCALL;
frame->loc.u.syscall = *(PCS_FRAME_LOC(pcs, idx).sysloc);
/* we print the string now so we can compare to suppressions.
* we use func since modname is too short in windows.
*/
BUFPRINT(frame->func, MAX_FUNC_LEN, sofar, len, "system call ");
if (ops.get_syscall_name != NULL)
name = (*ops.get_syscall_name)(frame->loc.u.syscall.sysnum);
/* strip syscall # if have name, to be independent of windows ver */
ASSERT(name != NULL, "syscall name should not be NULL");
if (name[0] != '\0' && name[0] != '<' /* "<unknown>" */) {
BUFPRINT(frame->func, MAX_FUNC_LEN, sofar, len, "%s", name);
} else {
BUFPRINT(frame->func, MAX_FUNC_LEN, sofar, len, "%d.%d",
frame->loc.u.syscall.sysnum.number,
frame->loc.u.syscall.sysnum.secondary);
}
if (frame->loc.u.syscall.syscall_aux != NULL) {
/* syscall aux identifier (PR 525269) */
BUFPRINT(frame->func, MAX_FUNC_LEN, sofar, len, " %s",
frame->loc.u.syscall.syscall_aux);
}
NULL_TERMINATE_BUFFER(frame->func);
} else {
pc_to_loc(&frame->loc, PCS_FRAME_LOC(pcs, idx).addr);
if (info != NULL) {
const char *modname = (info->name == NULL) ?
"<name unavailable>" : info->name;
frame->is_module = true;
frame->hide_modname = info->hide_modname;
frame->user_data = info->user_data;
frame->modid = info->id;
/* Lazily compute frame->modbase so leave it NULL here */
dr_snprintf(frame->modname, MAX_MODULE_LEN, "%s", modname);
NULL_TERMINATE_BUFFER(frame->modname);
dr_snprintf(frame->modoffs, MAX_PFX_LEN, PIFX, offs);
NULL_TERMINATE_BUFFER(frame->modoffs);
/* PR 543863: subtract one from retaddrs in callstacks so the line#
* is for the call and not for the next source code line, but only
* for symbol lookup so we still display a valid instr addr.
* We assume first frame is not a retaddr.
*/
lookup_func_and_line(frame, info,
(idx == 0 && !pcs->first_is_retaddr) ? offs : offs-1);
} else {
ASSERT(!frame->is_module, "frame not initialized");
dr_snprintf(frame->func, MAX_FUNC_LEN, "<not in a module>");
NULL_TERMINATE_BUFFER(frame->func);
}
}
}
/* 0 for num_frames means to print them all prefixed with tabs and
* absolute addresses.
* otherwise num_frames indicates the number of frames to be printed.
*/
void
packed_callstack_print(packed_callstack_t *pcs, uint num_frames,
char *buf, size_t bufsz, size_t *sofar, const char *prefix)
{
uint i;
symbolized_frame_t frame; /* 480 bytes but our stack can handle it */
STATS_INC(callstacks_symbolized);
ASSERT(pcs != NULL, "invalid args");
for (i = 0; i < pcs->num_frames && (num_frames == 0 || i < num_frames); i++) {
packed_frame_to_symbolized(pcs, &frame, i);
print_frame(&frame, buf, bufsz, sofar, false, 0, 0, prefix);
if (ops.truncate_below != NULL &&
text_matches_any_pattern((const char *)frame.func, ops.truncate_below, false))
break;
}
}
void
packed_callstack_to_symbolized(packed_callstack_t *pcs DR_PARAM_IN,
symbolized_callstack_t *scs DR_PARAM_OUT)
{
uint i;
STATS_INC(callstacks_symbolized);
scs->num_frames = pcs->num_frames;
scs->num_frames_allocated = pcs->num_frames;
ASSERT(scs->num_frames > 0, "invalid empty callstack");
scs->frames = (symbolized_frame_t *)
global_alloc(sizeof(*scs->frames) * scs->num_frames, HEAPSTAT_CALLSTACK);
ASSERT(pcs != NULL, "invalid args");
for (i = 0; i < pcs->num_frames; i++) {
packed_frame_to_symbolized(pcs, &scs->frames[i], i);
/* we truncate for real and not just on printing (i#700) */
if (ops.truncate_below != NULL &&
text_matches_any_pattern((const char *)scs->frames[i].func,
ops.truncate_below, false)) {
/* not worth re-allocating */
scs->num_frames = i + 1;
break;
}
}
}
#ifdef DEBUG
void
packed_callstack_log(packed_callstack_t *pcs, file_t f)
{
void *drcontext = dr_get_current_drcontext();
tls_callstack_t *pt = (tls_callstack_t *)
((drcontext == NULL) ? NULL : drmgr_get_tls_field(drcontext, tls_idx_callstack));
char *buf;
size_t bufsz;
size_t sofar = 0;
ASSERT(pcs != NULL, "invalid args");
if (pt == NULL) {
/* at init time no pt yet */
bufsz = MAX_ERROR_INITIAL_LINES + max_callstack_size();
buf = (char *) global_alloc(bufsz, HEAPSTAT_CALLSTACK);
} else {
buf = pt->errbuf;
bufsz = pt->errbufsz;
}
packed_callstack_print(pcs, 0, buf, bufsz, &sofar, NULL);
if (f == INVALID_FILE)
LOG_LARGE(0, buf);
else
ELOG_LARGE_F(0, f, buf);
if (pt == NULL)
global_free(buf, bufsz, HEAPSTAT_CALLSTACK);
}
#endif
uint
packed_callstack_free(packed_callstack_t *pcs)
{
uint refcount;
ASSERT(pcs != NULL, "invalid args");
refcount = atomic_add32_return_sum((volatile int *)&pcs->refcount, - 1);
if (refcount == 0) {
if (pcs->first_is_syscall) {
global_free(PCS_FRAME_LOC(pcs, 0).sysloc, sizeof(syscall_loc_t),
HEAPSTAT_CALLSTACK);
}
if (pcs->is_packed) {
if (pcs->frames.packed != NULL) {
global_free(pcs->frames.packed,
sizeof(*pcs->frames.packed)*pcs->num_frames,
HEAPSTAT_CALLSTACK);
}
} else {
if (pcs->frames.full != NULL) {
global_free(pcs->frames.full,
sizeof(*pcs->frames.full)*pcs->num_frames,
HEAPSTAT_CALLSTACK);
}
}
global_free(pcs, sizeof(*pcs), HEAPSTAT_CALLSTACK);
}
return refcount;
}
uint
packed_callstack_refcount(packed_callstack_t *pcs)
{
return pcs->refcount;
}
void
packed_callstack_add_ref(packed_callstack_t *pcs)
{
ASSERT(pcs != NULL, "invalid args");
ATOMIC_INC32(pcs->refcount);
ASSERT(pcs->refcount > 0, "refcount overflowed");
}
packed_callstack_t *
packed_callstack_clone(packed_callstack_t *src)
{
packed_callstack_t *dst = (packed_callstack_t *)
global_alloc(sizeof(*dst), HEAPSTAT_CALLSTACK);
ASSERT(src != NULL, "invalid args");
memset(dst, 0, sizeof(*dst));
dst->refcount = 1;
dst->num_frames = src->num_frames;
dst->is_packed = src->is_packed;
dst->first_is_retaddr = src->first_is_retaddr;
dst->first_is_syscall = src->first_is_syscall;
if (dst->is_packed) {
dst->frames.packed = (packed_frame_t *)
global_alloc(sizeof(*dst->frames.packed) * src->num_frames,
HEAPSTAT_CALLSTACK);
memcpy(dst->frames.packed, src->frames.packed,
sizeof(*dst->frames.packed) * src->num_frames);
} else {
dst->frames.full = (full_frame_t *)
global_alloc(sizeof(*dst->frames.full) * src->num_frames,
HEAPSTAT_CALLSTACK);
memcpy(dst->frames.full, src->frames.full,
sizeof(*dst->frames.full) * src->num_frames);
}
if (dst->first_is_syscall) {
if (dst->is_packed) {
dst->frames.packed[0].loc.sysloc = (syscall_loc_t *)
global_alloc(sizeof(syscall_loc_t), HEAPSTAT_CALLSTACK);
} else {
dst->frames.full[0].loc.sysloc = (syscall_loc_t *)
global_alloc(sizeof(syscall_loc_t), HEAPSTAT_CALLSTACK);
}
memcpy(PCS_FRAME_LOC(dst, 0).sysloc, PCS_FRAME_LOC(src, 0).sysloc,
sizeof(syscall_loc_t));
}
return dst;
}
uint
packed_callstack_hash(packed_callstack_t *pcs)
{
uint hash = 0;
uint i;
for (i = 0; i < pcs->num_frames; i++) {
if (!pcs->first_is_syscall || i > 0)
hash ^= (ptr_uint_t) PCS_FRAME_LOC(pcs, i).addr;
}
return hash;
}
bool
packed_callstack_cmp(packed_callstack_t *pcs1, packed_callstack_t *pcs2)
{
uint i;
if (PCS_FRAMES(pcs1) == NULL) {
if (PCS_FRAMES(pcs2) != NULL)
return false;
return true;
}
if (PCS_FRAMES(pcs2) == NULL)
return false;
if (pcs1->num_frames != pcs2->num_frames)
return false;
if (!pcs1->first_is_syscall && !pcs2->first_is_syscall &&
((pcs1->is_packed && pcs2->is_packed) ||
(!pcs1->is_packed && !pcs2->is_packed))) {
return (memcmp(PCS_FRAMES(pcs1), PCS_FRAMES(pcs2),
PCS_FRAME_SZ(pcs1)*pcs1->num_frames) == 0);
}
/* One is packed, the other is not; or, one has a syscall.
* We have to walk the frames.
*/
for (i = 0; i < pcs1->num_frames; i++) {
modname_info_t *info1 = NULL, *info2 = NULL;
size_t offs1 = 0, offs2 = 0;
bool nonsys1, nonsys2;
nonsys1 = packed_callstack_frame_modinfo(pcs1, i, &info1, &offs1);
nonsys2 = packed_callstack_frame_modinfo(pcs2, i, &info2, &offs2);
if ((nonsys1 && !nonsys2) || (!nonsys1 && nonsys2))
return false;
if (!nonsys1) {
if (memcmp(PCS_FRAME_LOC(pcs1, i).sysloc, PCS_FRAME_LOC(pcs2, i).sysloc,
sizeof(syscall_loc_t)) != 0)
return false;
} else {
if (PCS_FRAME_LOC(pcs1, i).addr != PCS_FRAME_LOC(pcs2, i).addr)
return false;
if (info1 != info2)
return false;
if (offs1 != offs2)
return false;
}
}
return true;
}
void
packed_callstack_md5(packed_callstack_t *pcs, byte digest[MD5_RAW_BYTES])
{
if (pcs->num_frames == 0) {
memset(digest, 0, sizeof(digest[0])*MD5_RAW_BYTES);
} else {
get_md5_for_region((const byte *)PCS_FRAMES(pcs),
PCS_FRAME_SZ(pcs)*pcs->num_frames, digest);
}
}
void
packed_callstack_crc32(packed_callstack_t *pcs, uint crc[2])
{
crc32_whole_and_half((const char *)PCS_FRAMES(pcs),
PCS_FRAME_SZ(pcs)*pcs->num_frames, crc);
}
uint
packed_callstack_num_frames(packed_callstack_t *pcs)
{
return pcs->num_frames;
}
/* destroy the packted callstack */
void
packed_callstack_destroy(packed_callstack_t *pcs)
{
uint count;
LOG(4, "%s: force-free pcs "PFX"\n", __FUNCTION__, pcs);
/* There might be callstack left not deleted by the app (e.g., leaks),
* so we need to force-remove here.
*/
do {
count = packed_callstack_free(pcs);
/* XXX: do we need widen the refcount, it seems unlikely to do 4 billion
* handle creation system calls from one call site.
*/
ASSERT(count < UINT_MAX - 1, "underflow in count: likely double-free");
} while (count > 0);
}
/* add the packed callstack into the hashtable, assuming the caller is holding the lock */
packed_callstack_t *
packed_callstack_add_to_table(hashtable_t *table, packed_callstack_t *pcs
_IF_STATS(uint *callstack_count))
{
packed_callstack_t *existing;
existing = hashtable_lookup(table, (void *)pcs);
if (existing == NULL) {
/* avoid calling lookup twice by not calling hashtable_add() */
IF_DEBUG(void *prior =)
hashtable_add_replace(table, (void *)pcs, (void *)pcs);
ASSERT(prior == NULL, "just did lookup: cannot happen");
DOLOG(3, {
LOG(3, "@@@ unique callstack #%d\n", *callstack_count);
packed_callstack_log(pcs, INVALID_FILE);
});
STATS_INC(*callstack_count);
} else {
IF_DEBUG(uint count =) packed_callstack_free(pcs);
ASSERT(count == 0, "refcount should be 0");
pcs = existing;
}
/* The callstack in table is one reference, and the other references
* will add its reference count. Once all other references are gone
* and the refcount hits 1, we can remove it from the table.
*/
packed_callstack_add_ref(pcs);
return pcs;
}
/***************************************************************************
* SYMBOLIZED CALLSTACKS
*/
void
symbolized_callstack_print(const symbolized_callstack_t *scs DR_PARAM_IN,
char *buf, size_t bufsz, size_t *sofar,
const char *prefix, bool for_log)
{
uint i;
size_t max_flen = 0;
uint print_flags = for_log ? PRINT_FOR_POSTPROCESS : ops.print_flags;
ASSERT(scs != NULL, "invalid args");
if (TEST(PRINT_ALIGN_COLUMNS, print_flags)) {
for (i = 0; i < scs->num_frames; i++) {
size_t flen = strlen(scs->frames[i].func);
if (flen > max_flen)
max_flen = flen;
}
}
for (i = 0; i < scs->num_frames; i++) {
print_frame(&scs->frames[i], buf, bufsz, sofar, for_log, print_flags,
max_flen, prefix);
/* ops.truncate_below should have been done when symbolized cstack created.
* too much of a perf hit to assert on every single frame.
*/
}
}
void
symbolized_callstack_free(symbolized_callstack_t *scs)
{
ASSERT(scs != NULL, "invalid args");
if (scs->frames != NULL) {
global_free(scs->frames, sizeof(*scs->frames) * scs->num_frames_allocated,
HEAPSTAT_CALLSTACK);
}
}
bool
symbolized_callstack_frame_is_module(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return false;
return scs->frames[frame].is_module;
}
char *
symbolized_callstack_frame_modname(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return NULL;
ASSERT(scs->frames[frame].is_module ||
scs->frames[frame].modname[0] == '\0', "modname not initialized");
return scs->frames[frame].modname;
}
char *
symbolized_callstack_frame_modoffs(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return 0;
return scs->frames[frame].modoffs;
}
app_pc
symbolized_callstack_frame_modbase(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return NULL;
if (scs->frames[frame].is_module) {
if (scs->frames[frame].modbase == NULL) {
ASSERT(scs->frames[frame].loc.type == APP_LOC_PC &&
scs->frames[frame].loc.u.addr.valid, "invalid frame");
/* If this fails we'll just try again: should be rare, and caller's
* fault if asking about an unloaded module.
*/
module_lookup(scs->frames[frame].loc.u.addr.pc,
&scs->frames[frame].modbase, NULL, NULL);
}
}
return scs->frames[frame].modbase;
}
char *
symbolized_callstack_frame_func(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return NULL;
return scs->frames[frame].func;
}
char *
symbolized_callstack_frame_file(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return NULL;
return scs->frames[frame].fname;
}
void *
symbolized_callstack_frame_data(const symbolized_callstack_t *scs, uint frame)
{
ASSERT(scs != NULL, "invalid args");
if (scs->num_frames <= frame)
return false;
return scs->frames[frame].user_data;
}
/***************************************************************************
* MODULES
*/
/* For storing binary callstacks we need to store module names in a shared
* location to save space and handle unloaded and reloaded modules.
* Returns the index into modname_array, or -1 on error.
*/
static modname_info_t *
add_new_module(void *drcontext, const module_data_t *info)
{
modname_info_t *name_info;
const char *name;
IF_DEBUG(static bool has_noname = false;)
size_t sz;
name = dr_module_preferred_name(info);
if (name == NULL) {
name = "";
/* if multiple w/o names, we lose data */
ASSERT(!has_noname, "multiple modules w/o name: may lose data");
IF_DEBUG(has_noname = true;)
}
hashtable_lock(&modname_table);
/* key via path to reduce chance of duplicate name (i#729) */
name_info = (modname_info_t *) hashtable_lookup(&modname_table,
(void*)info->full_path);
if (name_info == NULL) {
name_info = (modname_info_t *)
global_alloc(sizeof(*name_info), HEAPSTAT_HASHTABLE);
name_info->name = drmem_strdup(name, HEAPSTAT_HASHTABLE);
name_info->path = drmem_strdup(info->full_path, HEAPSTAT_HASHTABLE);
name_info->index = modname_array_end; /* store first index if multi-entry */
name_info->id = modname_unique_id++;
/* we cache this value to avoid re-matching on every frame */
name_info->hide_modname =
(ops.modname_hide != NULL &&
text_matches_any_pattern(name_info->name, ops.modname_hide,
FILESYS_CASELESS));
name_info->abort_fp_walk =
(ops.bad_fp_list != NULL &&
text_matches_any_pattern(name_info->name, ops.bad_fp_list,
FILESYS_CASELESS));
if (ops.module_load != NULL)
name_info->user_data = ops.module_load(name_info->path, name, info->start);
name_info->warned_no_syms = false;
hashtable_add(&modname_table, (void*)name_info->path, (void*)name_info);
/* We need an entry for every 16M of module size */
sz = info->end - info->start;
while (true) {
if (modname_array_end >= MAX_MODNAMES_STORED) {
DO_ONCE({
LOG(1, "hit max # packed modules: switching to unpacked frames\n");
});
/* Alternative is to have missing names for error reports: for
* dup entries we'd just get offset wrong; for first entry we'd
* miss in table and print out <unknown module>: not acceptable.
*/
name_info->index = -1;
break;
}
LOG(2, "modname_array %d = %s\n", modname_array_end, name);
modname_array[modname_array_end] = name_info;
modname_array_end++;
if (sz <= MAX_MODOFFS_STORED)
break;
sz -= MAX_MODOFFS_STORED;
}
}
/* i#446: Log module load events with a full path and unique id for
* postprocessing.
*/
dr_fprintf(f_global, NL"module load event: \"%s\" "PFX"-"PFX" modid: %d %s"NL,
name, info->start, info->end, name_info->id, info->full_path);
hashtable_unlock(&modname_table);
return name_info;
}
static void
modname_info_free(void *p)
{
modname_info_t *info = (modname_info_t *) p;
if (ops.module_load != NULL)
ops.module_unload(info->path, info->user_data);
if (info->name != NULL)
global_free((void *)info->name, strlen(info->name) + 1, HEAPSTAT_HASHTABLE);
if (info->path != NULL)
global_free((void *)info->path, strlen(info->path) + 1, HEAPSTAT_HASHTABLE);
global_free((void *)info, sizeof(*info), HEAPSTAT_HASHTABLE);
}
/* Caller must hold modtree_lock */
static void
callstack_module_add_region(app_pc start, app_pc end, modname_info_t *info)
{
IF_DEBUG(rb_node_t *node = )
rb_insert(module_tree, start, (end - start), (void *)info);
#ifdef DEBUG
if (node != NULL) {
# ifdef MACOS
/* dyld shared cache shares __LINKEDIT segments */
LOG(2, "new module segment overlaps w/ existing\n");
# else
ASSERT(false, "new module overlaps w/ existing");
# endif
}
#endif
if (start < modtree_min_start || modtree_min_start == NULL)
modtree_min_start = start;
if (end > modtree_max_end)
modtree_max_end = end;
}
/* Caller must hold modtree_lock */
static void
callstack_module_remove_region(app_pc start, app_pc end)
{
rb_node_t *node = rb_find(module_tree, start);
ASSERT(node != NULL, "module mismatch");
if (node != NULL) {
app_pc node_start;
size_t node_size;
rb_node_fields(node, &node_start, &node_size, NULL);
ASSERT(start == node_start &&
end == node_start + node_size, "module mismatch");
rb_delete(module_tree, node);
}
}
static void
callstack_module_get_text_bounds(const module_data_t *info, bool loaded,
app_pc *start DR_PARAM_OUT, app_pc *end DR_PARAM_OUT)
{
ASSERT(loaded, "only supports fully loaded modules");
#ifdef UNIX
/* Yes, our own x64 libs are not contiguous */
if (!info->contiguous) {
/* We assume the 1st segment has .text */
*start = info->segments[0].start;
*end = info->segments[0].end;
} else
#endif
{
*start = info->start;
*end = info->end;
}
}
/* For storing binary callstacks we need to store module names in a shared
* location to save space and handle unloaded and reloaded modules.
*/
void
callstack_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
modname_info_t *name_info = add_new_module(drcontext, info);
/* Record DR and DrMem lib bounds. We assume they are contiguous. */
if (text_matches_pattern(name_info->name, DYNAMORIO_LIBNAME, FILESYS_CASELESS)) {
ASSERT(libdr_base == NULL, "duplicate DR lib");
callstack_module_get_text_bounds(info, loaded, &libdr_base, &libdr_end);
} else if (ops.tool_lib_ignore != NULL &&
text_matches_pattern(name_info->name, ops.tool_lib_ignore,
FILESYS_CASELESS)) {
ASSERT(libtoolbase == NULL, "duplicate tool lib");
callstack_module_get_text_bounds(info, loaded, &libtoolbase, &libtoolend);
}
/* PR 473640: maintain our own module tree */
dr_mutex_lock(modtree_lock);
ASSERT(info->end > info->start, "invalid mod bounds");
#ifdef WINDOWS
callstack_module_add_region(info->start, info->end, name_info);
#else
if (info->contiguous)
callstack_module_add_region(info->start, info->end, name_info);
else {
/* Add the non-contiguous segments (i#160/PR 562667) */
app_pc seg_base;
uint i;
ASSERT(info->num_segments > 1 && info->segments != NULL, "invalid seg data");
seg_base = info->segments[0].start;
for (i = 1; i < info->num_segments; i++) {
if (info->segments[i].start > info->segments[i - 1].end) {
callstack_module_add_region(seg_base, info->segments[i - 1].end,
name_info);
seg_base = info->segments[i].start;
} else {
ASSERT(info->segments[i].start == info->segments[i - 1].end,
"module list should be sorted");
}
}
callstack_module_add_region(seg_base, info->segments[i - 1].end, name_info);
}
#endif
/* update cached values */
modtree_last_hit = NULL;
modtree_last_miss = NULL;
dr_mutex_unlock(modtree_lock);
}
void
callstack_module_unload(void *drcontext, const module_data_t *info)
{
/* PR 473640: maintain our own module tree */
rb_node_t *node;
app_pc node_start;
size_t node_size;
ASSERT(info->end > info->start, "invalid mod bounds");
LOG(1, "module unload event: \"%s\" "PFX"-"PFX"\n",
(dr_module_preferred_name(info) == NULL) ? "" :
dr_module_preferred_name(info), info->start, info->end);
dr_mutex_lock(modtree_lock);
#ifdef WINDOWS
callstack_module_remove_region(info->start, info->end);
#else
if (info->contiguous)
callstack_module_remove_region(info->start, info->end);
else {
/* Remove all non-contiguous segments (i#160/PR 562667) */
app_pc seg_base;
uint i;
ASSERT(info->num_segments > 1 && info->segments != NULL, "invalid seg data");
seg_base = info->segments[0].start;
for (i = 1; i < info->num_segments; i++) {
if (info->segments[i].start > info->segments[i - 1].end) {
callstack_module_remove_region(seg_base, info->segments[i - 1].end);
seg_base = info->segments[i].start;
} else {
ASSERT(info->segments[i].start == info->segments[i - 1].end,
"module list should be sorted");
}
}
callstack_module_remove_region(seg_base, info->segments[i - 1].end);
}
#endif
/* update cached bounds */
node = rb_max_node(module_tree);
if (node != NULL) {
rb_node_fields(node, &node_start, &node_size, NULL);
modtree_max_end = node_start + node_size;
} else
modtree_max_end = NULL;
node = rb_min_node(module_tree);
if (node != NULL) {
rb_node_fields(node, &node_start, NULL, NULL);
modtree_min_start = node_start;
} else
modtree_min_start = NULL;
modtree_last_start = NULL;
modtree_last_hit = NULL;
modtree_last_miss = NULL;
dr_mutex_unlock(modtree_lock);
}
static bool
module_lookup(byte *pc, app_pc *start DR_PARAM_OUT, size_t *size DR_PARAM_OUT,
modname_info_t **name)
{
rb_node_t *node;
bool res = false;
dr_mutex_lock(modtree_lock);
/* We cache to avoid the rb_in_node cost */
if (modtree_last_start != NULL &&
pc >= modtree_last_start && pc < modtree_last_start + modtree_last_size) {
/* use cached values */
res = true;
LOG(5, "module_lookup: using cached "PFX"\n", modtree_last_start);
} else {
LOG(5, "module_lookup: "PFX" doesn't match cached "PFX"\n",
pc, modtree_last_start);
node = rb_in_node(module_tree, pc);
if (node != NULL) {
res = true;
rb_node_fields(node, &modtree_last_start, &modtree_last_size,
(void **) &modtree_last_name_info);
}
}
if (res) {
if (start != NULL)
*start = modtree_last_start;
if (size != NULL)
*size = modtree_last_size;
if (name != NULL)
*name = modtree_last_name_info;
}
dr_mutex_unlock(modtree_lock);
return res;
}
/* this is exported for PR 570839 for is_image() */
bool
is_in_module(byte *pc)
{
/* We cache the last page queried for performance */
bool res = false;
/* This is a perf bottleneck so we use caching.
* We read these values w/o a lock, assuming they are written
* atomically (since aligned they won't cross cache lines).
*/
if (pc < modtree_min_start || pc >= modtree_max_end)
res = false;
else if ((app_pc) ALIGN_BACKWARD(pc, PAGE_SIZE) == modtree_last_miss)
res = false;
else if ((app_pc) ALIGN_BACKWARD(pc, PAGE_SIZE) == modtree_last_hit)
res = true;
else {
dr_mutex_lock(modtree_lock);
LOG(5, "is_in_module: "PFX" missed cached "PFX"-"PFX", miss="PFX", hit="PFX"\n",
pc, modtree_min_start, modtree_max_end, modtree_last_miss, modtree_last_hit);
res = (rb_in_node(module_tree, pc) != NULL);
/* XXX: we could cache the range on a hit, and the range from prev lower
* to next higher on a miss: but going to wait for this to show up
* in pclookup.
*/
if (res)
modtree_last_hit = (app_pc) ALIGN_BACKWARD(pc, PAGE_SIZE);
else
modtree_last_miss = (app_pc) ALIGN_BACKWARD(pc, PAGE_SIZE);
dr_mutex_unlock(modtree_lock);
}
return res;
}
const char *
module_lookup_path(byte *pc)
{
modname_info_t *name_info;
bool found = module_lookup(pc, NULL, NULL, &name_info);
return found ? name_info->path : NULL;
}
/* Exported for i#838, module wildcard suppression. */
const char *
module_lookup_preferred_name(byte *pc)
{
modname_info_t *name_info;
bool found = module_lookup(pc, NULL, NULL, &name_info);
return found ? name_info->name : NULL;
}
void *
module_lookup_user_data(byte *pc, app_pc *start DR_PARAM_OUT, size_t *size DR_PARAM_OUT)
{
modname_info_t *name_info;
bool found = module_lookup(pc, NULL, NULL, &name_info);
return found ? name_info->user_data : NULL;
}
/* Warn once (or twice with races) about modules that don't have symbols, and
* log them so we can fetch symbols at the end of the run.
*/
static void
warn_no_symbols(modname_info_t *name_info)
{
if (!name_info->warned_no_syms) {
name_info->warned_no_syms = true;
WARN("WARNING: unable to load symbols for %s\n", name_info->path);
if (ops.missing_syms_cb != NULL) {
ops.missing_syms_cb(name_info->path);
}
}
}
void
module_check_for_symbols(const char *modpath)
{
drsym_debug_kind_t kind;
modname_info_t *name_info;
if (!modname_table_initialized) {
return; /* Happens for perturb_only. */
}
hashtable_lock(&modname_table);
name_info = (modname_info_t *) hashtable_lookup(&modname_table,
(void *)modpath);
/* The lookup can fail on ntdll lookups during dr_init, because we haven't
* hit the initial module load events yet. That's OK, we'll probably catch
* those modules later.
*/
if (name_info != NULL) {
drsym_error_t res = drsym_get_module_debug_kind(modpath, &kind);
if (res != DRSYM_SUCCESS || !TEST(DRSYM_SYMBOLS, kind)) {
warn_no_symbols(name_info);
}
}
hashtable_unlock(&modname_table);
}
/****************************************************************************
* Application locations
*/
void
pc_to_loc(app_loc_t *loc, app_pc pc)
{
ASSERT(loc != NULL, "invalid param");
loc->type = APP_LOC_PC;
loc->u.addr.valid = true;
loc->u.addr.pc = pc;
}
void
syscall_to_loc(app_loc_t *loc, drsys_sysnum_t sysnum, const char *aux)
{
ASSERT(loc != NULL, "invalid param");
loc->type = APP_LOC_SYSCALL;
loc->u.syscall.sysnum = sysnum;
loc->u.syscall.syscall_aux = aux;
}
/* loc_to_pc() and loc_to_print() must be defined by the tool-specific code */