mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-03 18:13:01 +00:00
Bug 79362 - Debug info is lost for .so files when they are dlclose'd. Majorly reworked by Philippe Waroquiers.
This commit is contained in:
parent
f1a49eeb42
commit
cceed053ce
2
.gitignore
vendored
2
.gitignore
vendored
@ -1075,6 +1075,8 @@
|
||||
/memcheck/tests/linux/.deps
|
||||
/memcheck/tests/linux/brk
|
||||
/memcheck/tests/linux/capget
|
||||
/memcheck/tests/linux/dlclose_leak
|
||||
/memcheck/tests/linux/dlclose_leak_so.so
|
||||
/memcheck/tests/linux/getregset
|
||||
/memcheck/tests/linux/ioctl-tiocsig
|
||||
/memcheck/tests/linux/lsframe1
|
||||
|
||||
6
NEWS
6
NEWS
@ -13,6 +13,11 @@ support for X86/macOS 10.13, AMD64/macOS 10.13.
|
||||
|
||||
* ==================== CORE CHANGES ===================
|
||||
|
||||
* The new option --keep-debuginfo=no|yes (default no) can be used to
|
||||
keep symbols etc for unloaded code. This allows saved stack traces
|
||||
(e.g. memory leaks) to include file/line info for code that has been
|
||||
dlclose'd (or similar). See user manual for more information and
|
||||
known limitations.
|
||||
|
||||
* ================== PLATFORM CHANGES =================
|
||||
|
||||
@ -43,6 +48,7 @@ To see details of a given bug, visit
|
||||
https://bugs.kde.org/show_bug.cgi?id=XXXXXX
|
||||
where XXXXXX is the bug number as listed below.
|
||||
|
||||
79362 Debug info is lost for .so files when they are dlclose'd
|
||||
208052 strlcpy error when n = 0
|
||||
255603 exp-sgcheck Assertion '!already_present' failed
|
||||
376257 helgrind history full speed up using a cached stack
|
||||
|
||||
@ -210,12 +210,14 @@ static HChar* get_perm_string(const HChar* s)
|
||||
static void get_debug_info(Addr instr_addr, const HChar **dir,
|
||||
const HChar **file, const HChar **fn, UInt* line)
|
||||
{
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
Bool found_file_line = VG_(get_filename_linenum)(
|
||||
ep,
|
||||
instr_addr,
|
||||
file, dir,
|
||||
line
|
||||
);
|
||||
Bool found_fn = VG_(get_fnname)(instr_addr, fn);
|
||||
Bool found_fn = VG_(get_fnname)(ep, instr_addr, fn);
|
||||
|
||||
if (!found_file_line) {
|
||||
*file = "???";
|
||||
|
||||
@ -199,7 +199,8 @@ obj_node* obj_of_address(Addr addr)
|
||||
DebugInfo* di;
|
||||
PtrdiffT offset;
|
||||
|
||||
di = VG_(find_DebugInfo)(addr);
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
di = VG_(find_DebugInfo)(ep, addr);
|
||||
obj = CLG_(get_obj_node)( di );
|
||||
|
||||
/* Update symbol offset in object if remapped */
|
||||
|
||||
@ -373,7 +373,8 @@ Bool get_debug_pos(BBCC* bbcc, Addr addr, AddrPos* p)
|
||||
found_file_line = debug_cache_info[cachepos];
|
||||
}
|
||||
else {
|
||||
found_file_line = VG_(get_filename_linenum)(addr,
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
found_file_line = VG_(get_filename_linenum)(ep, addr,
|
||||
&file,
|
||||
&dir,
|
||||
&(p->line));
|
||||
|
||||
@ -434,17 +434,18 @@ Bool CLG_(get_debug_info)(Addr instr_addr,
|
||||
|
||||
CLG_DEBUG(6, " + get_debug_info(%#lx)\n", instr_addr);
|
||||
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
if (pDebugInfo) {
|
||||
*pDebugInfo = VG_(find_DebugInfo)(instr_addr);
|
||||
*pDebugInfo = VG_(find_DebugInfo)(ep, instr_addr);
|
||||
|
||||
// for generated code in anonymous space, pSegInfo is 0
|
||||
}
|
||||
|
||||
found_file_line = VG_(get_filename_linenum)(instr_addr,
|
||||
found_file_line = VG_(get_filename_linenum)(ep, instr_addr,
|
||||
file,
|
||||
dir,
|
||||
&line);
|
||||
found_fn = VG_(get_fnname)(instr_addr, fn_name);
|
||||
found_fn = VG_(get_fnname)(ep, instr_addr, fn_name);
|
||||
|
||||
if (!found_file_line && !found_fn) {
|
||||
CLG_(stat).no_debug_BBs++;
|
||||
@ -503,6 +504,7 @@ fn_node* CLG_(get_fn_node)(BB* bb)
|
||||
CLG_(get_debug_info)(bb_addr(bb),
|
||||
&dirname, &filename, &fnname, &line_num, &di);
|
||||
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
if (0 == VG_(strcmp)(fnname, "???")) {
|
||||
int p;
|
||||
static HChar buf[32]; // for sure large enough
|
||||
@ -521,7 +523,7 @@ fn_node* CLG_(get_fn_node)(BB* bb)
|
||||
fnname = buf;
|
||||
}
|
||||
else {
|
||||
if (VG_(get_fnname_if_entry)(bb_addr(bb), &fnname))
|
||||
if (VG_(get_fnname_if_entry)(ep, bb_addr(bb), &fnname))
|
||||
bb->is_entry = 1;
|
||||
}
|
||||
|
||||
|
||||
@ -86,7 +86,7 @@ static ThreadId find_tid_with_stack_containing (Addr a)
|
||||
return VG_INVALID_THREADID;
|
||||
}
|
||||
|
||||
void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
void VG_(describe_addr) ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai )
|
||||
{
|
||||
VgSectKind sect;
|
||||
|
||||
@ -99,7 +99,7 @@ void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
VG_(free), sizeof(HChar) );
|
||||
|
||||
(void) VG_(get_data_description)( ai->Addr.Variable.descr1,
|
||||
ai->Addr.Variable.descr2, a );
|
||||
ai->Addr.Variable.descr2, ep, a );
|
||||
/* If there's nothing in descr1/2, free them. Why is it safe to
|
||||
VG_(indexXA) at zero here? Because VG_(get_data_description)
|
||||
guarantees to zero terminate descr1/2 regardless of the outcome
|
||||
@ -127,7 +127,7 @@ void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
there. -- */
|
||||
const HChar *name;
|
||||
if (VG_(get_datasym_and_offset)(
|
||||
a, &name,
|
||||
ep, a, &name,
|
||||
&ai->Addr.DataSym.offset )) {
|
||||
ai->Addr.DataSym.name = VG_(strdup)("mc.da.dsname", name);
|
||||
ai->tag = Addr_DataSym;
|
||||
@ -148,6 +148,7 @@ void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
ai->tag = Addr_Stack;
|
||||
VG_(initThreadInfo)(&ai->Addr.Stack.tinfo);
|
||||
ai->Addr.Stack.tinfo.tid = tid;
|
||||
ai->Addr.Stack.epoch = ep;
|
||||
ai->Addr.Stack.IP = 0;
|
||||
ai->Addr.Stack.frameNo = -1;
|
||||
ai->Addr.Stack.stackPos = StackPos_stacked;
|
||||
@ -248,6 +249,7 @@ void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
ai->tag = Addr_Stack;
|
||||
VG_(initThreadInfo)(&ai->Addr.Stack.tinfo);
|
||||
ai->Addr.Stack.tinfo.tid = tid;
|
||||
ai->Addr.Stack.epoch = ep;
|
||||
ai->Addr.Stack.IP = 0;
|
||||
ai->Addr.Stack.frameNo = -1;
|
||||
vg_assert (stackPos != StackPos_stacked);
|
||||
@ -447,20 +449,24 @@ static void pp_addrinfo_WRK ( Addr a, const AddrInfo* ai, Bool mc,
|
||||
Bool haslinenum;
|
||||
PtrdiffT offset;
|
||||
|
||||
if (VG_(get_inst_offset_in_function)( ai->Addr.Stack.IP,
|
||||
if (VG_(get_inst_offset_in_function)( ai->Addr.Stack.epoch,
|
||||
ai->Addr.Stack.IP,
|
||||
&offset))
|
||||
haslinenum = VG_(get_linenum) (ai->Addr.Stack.IP - offset,
|
||||
haslinenum = VG_(get_linenum) (ai->Addr.Stack.epoch,
|
||||
ai->Addr.Stack.IP - offset,
|
||||
&linenum);
|
||||
else
|
||||
haslinenum = False;
|
||||
|
||||
hasfile = VG_(get_filename)(ai->Addr.Stack.IP, &file);
|
||||
hasfile = VG_(get_filename)(ai->Addr.Stack.epoch,
|
||||
ai->Addr.Stack.IP, &file);
|
||||
|
||||
HChar strlinenum[16] = ""; // large enough
|
||||
if (hasfile && haslinenum)
|
||||
VG_(sprintf)(strlinenum, "%u", linenum);
|
||||
|
||||
hasfn = VG_(get_fnname)(ai->Addr.Stack.IP, &fn);
|
||||
hasfn = VG_(get_fnname)(ai->Addr.Stack.epoch,
|
||||
ai->Addr.Stack.IP, &fn);
|
||||
|
||||
if (hasfn || hasfile)
|
||||
VG_(emit)( "%sin frame #%d, created by %ps (%ps:%s)%s\n",
|
||||
@ -603,7 +609,7 @@ static void pp_addrinfo_WRK ( Addr a, const AddrInfo* ai, Bool mc,
|
||||
if (ai->Addr.SectKind.kind == Vg_SectText) {
|
||||
/* To better describe the address in a text segment,
|
||||
pp a dummy stacktrace made of this single address. */
|
||||
VG_(pp_StackTrace)( &a, 1 );
|
||||
VG_(pp_StackTrace)( VG_(current_DiEpoch)(), &a, 1 );
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
#include "pub_core_machine.h" // VG_PLAT_USES_PPCTOC
|
||||
#include "pub_core_xarray.h"
|
||||
#include "pub_core_oset.h"
|
||||
#include "pub_core_execontext.h"
|
||||
#include "pub_core_stacktrace.h" // VG_(get_StackTrace) XXX: circular dependency
|
||||
#include "pub_core_ume.h"
|
||||
|
||||
@ -70,6 +71,10 @@
|
||||
should-we-load-debuginfo-now? finite state machine. */
|
||||
#define DEBUG_FSM 0
|
||||
|
||||
/* Set this to 1 to enable somewhat minimal debug printing for the
|
||||
debuginfo-epoch machinery. */
|
||||
#define DEBUG_EPOCHS 0
|
||||
|
||||
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- The _svma / _avma / _image / _bias naming scheme ---*/
|
||||
@ -108,6 +113,110 @@
|
||||
static void caches__invalidate (void);
|
||||
|
||||
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- Epochs ---*/
|
||||
/*------------------------------------------------------------*/
|
||||
|
||||
/* The DebugInfo epoch is incremented every time we either load debuginfo in
|
||||
response to an object mapping, or an existing DebugInfo becomes
|
||||
non-current (or will be discarded) due to an object unmap. By storing,
|
||||
in each DebugInfo, the first and last epoch for which it is valid, we can
|
||||
unambiguously identify the set of DebugInfos which should be used to
|
||||
provide metadata for a code or data address, provided we know the epoch
|
||||
to which that address pertains.
|
||||
|
||||
Note, this isn't the same as the "handle_counter" below. That only
|
||||
advances when new DebugInfos are created. "current_epoch" advances both
|
||||
at DebugInfo created and destruction-or-making-non-current.
|
||||
*/
|
||||
|
||||
// The value zero is reserved for indicating an invalid epoch number.
|
||||
static UInt current_epoch = 1;
|
||||
|
||||
inline DiEpoch VG_(current_DiEpoch) ( void ) {
|
||||
DiEpoch dep; dep.n = current_epoch; return dep;
|
||||
}
|
||||
|
||||
static void advance_current_DiEpoch ( const HChar* msg ) {
|
||||
current_epoch++;
|
||||
if (DEBUG_EPOCHS)
|
||||
VG_(printf)("Advancing current epoch to %u due to %s\n",
|
||||
current_epoch, msg);
|
||||
}
|
||||
|
||||
static inline Bool eq_DiEpoch ( DiEpoch dep1, DiEpoch dep2 ) {
|
||||
return dep1.n == dep2.n && /*neither is invalid*/dep1.n != 0;
|
||||
}
|
||||
|
||||
// Is this DebugInfo currently "allocated" (pre-use state, only FSM active) ?
|
||||
static inline Bool is_DebugInfo_allocated ( const DebugInfo* di )
|
||||
{
|
||||
if (is_DiEpoch_INVALID(di->first_epoch)
|
||||
&& is_DiEpoch_INVALID(di->last_epoch)) {
|
||||
return True;
|
||||
} else {
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this DebugInfo currently "active" (valid for the current epoch) ?
|
||||
static inline Bool is_DebugInfo_active ( const DebugInfo* di )
|
||||
{
|
||||
if (!is_DiEpoch_INVALID(di->first_epoch)
|
||||
&& is_DiEpoch_INVALID(di->last_epoch)) {
|
||||
// Yes it is active. Sanity check ..
|
||||
vg_assert(di->first_epoch.n <= current_epoch);
|
||||
return True;
|
||||
} else {
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this DebugInfo currently "archived" ?
|
||||
static inline Bool is_DebugInfo_archived ( const DebugInfo* di )
|
||||
{
|
||||
if (!is_DiEpoch_INVALID(di->first_epoch)
|
||||
&& !is_DiEpoch_INVALID(di->last_epoch)) {
|
||||
// Yes it is archived. Sanity checks ..
|
||||
vg_assert(di->first_epoch.n <= di->last_epoch.n);
|
||||
vg_assert(di->last_epoch.n <= current_epoch);
|
||||
return True;
|
||||
} else {
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this DebugInfo valid for the specified epoch?
|
||||
static inline Bool is_DI_valid_for_epoch ( const DebugInfo* di, DiEpoch ep )
|
||||
{
|
||||
// Stay sane
|
||||
vg_assert(ep.n > 0 && ep.n <= current_epoch);
|
||||
|
||||
Bool first_valid = !is_DiEpoch_INVALID(di->first_epoch);
|
||||
Bool last_valid = !is_DiEpoch_INVALID(di->last_epoch);
|
||||
|
||||
if (first_valid) {
|
||||
if (last_valid) {
|
||||
// Both valid. di is in Archived state.
|
||||
return di->first_epoch.n <= ep.n && ep.n <= di->last_epoch.n;
|
||||
} else {
|
||||
// First is valid, last is invalid. di is in Active state.
|
||||
return di->first_epoch.n <= ep.n;
|
||||
}
|
||||
} else {
|
||||
vg_assert (!last_valid); // First invalid, last valid is a bad state.
|
||||
// Neither is valid. di is in Allocated state.
|
||||
return False;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline UInt ROL32 ( UInt x, UInt n )
|
||||
{
|
||||
return (x << n) | (x >> (32-n));
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- Root structure ---*/
|
||||
/*------------------------------------------------------------*/
|
||||
@ -162,6 +271,23 @@ static void move_DebugInfo_one_step_forward ( DebugInfo* di )
|
||||
}
|
||||
|
||||
|
||||
// Debugging helper for epochs
|
||||
static void show_epochs ( const HChar* msg )
|
||||
{
|
||||
if (DEBUG_EPOCHS) {
|
||||
DebugInfo* di;
|
||||
VG_(printf)("\nDebugInfo epoch display, requested by \"%s\"\n", msg);
|
||||
VG_(printf)(" Current epoch (note: 0 means \"invalid epoch\") = %u\n",
|
||||
current_epoch);
|
||||
for (di = debugInfo_list; di; di = di->next) {
|
||||
VG_(printf)(" [di=%p] first %u last %u %s\n",
|
||||
di, di->first_epoch.n, di->last_epoch.n, di->fsm.filename);
|
||||
}
|
||||
VG_(printf)("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- Notification (acquire/discard) helpers ---*/
|
||||
/*------------------------------------------------------------*/
|
||||
@ -182,6 +308,8 @@ DebugInfo* alloc_DebugInfo( const HChar* filename )
|
||||
|
||||
di = ML_(dinfo_zalloc)("di.debuginfo.aDI.1", sizeof(DebugInfo));
|
||||
di->handle = handle_counter++;
|
||||
di->first_epoch = DiEpoch_INVALID();
|
||||
di->last_epoch = DiEpoch_INVALID();
|
||||
di->fsm.filename = ML_(dinfo_strdup)("di.debuginfo.aDI.2", filename);
|
||||
di->fsm.maps = VG_(newXA)(
|
||||
ML_(dinfo_zalloc), "di.debuginfo.aDI.3",
|
||||
@ -302,34 +430,54 @@ static void free_DebugInfo ( DebugInfo* di )
|
||||
}
|
||||
|
||||
|
||||
/* 'si' is a member of debugInfo_list. Find it, remove it from the
|
||||
list, notify m_redir that this has happened, and free all storage
|
||||
reachable from it.
|
||||
/* 'di' is a member of debugInfo_list. Find it, and either (remove it from
|
||||
the list and free all storage reachable from it) or archive it.
|
||||
Notify m_redir that this removal/archiving has happened.
|
||||
|
||||
Note that 'di' can't be archived. Is a DebugInfo is archived then we
|
||||
want to hold on to it forever. This is asserted for.
|
||||
|
||||
Note also, we don't advance the current epoch here. That's the
|
||||
responsibility of some (non-immediate) caller.
|
||||
*/
|
||||
static void discard_DebugInfo ( DebugInfo* di )
|
||||
static void discard_or_archive_DebugInfo ( DebugInfo* di )
|
||||
{
|
||||
const HChar* reason = "munmap";
|
||||
const HChar* reason = "munmap";
|
||||
const Bool archive = VG_(clo_keep_debuginfo);
|
||||
|
||||
DebugInfo** prev_next_ptr = &debugInfo_list;
|
||||
DebugInfo* curr = debugInfo_list;
|
||||
|
||||
/* It must be active! */
|
||||
vg_assert( is_DebugInfo_active(di));
|
||||
while (curr) {
|
||||
if (curr == di) {
|
||||
/* Found it; remove from list and free it. */
|
||||
/* Found it; (remove from list and free it), or archive it. */
|
||||
if (curr->have_dinfo
|
||||
&& (VG_(clo_verbosity) > 1 || VG_(clo_trace_redir)))
|
||||
VG_(message)(Vg_DebugMsg,
|
||||
"Discarding syms at %#lx-%#lx in %s due to %s()\n",
|
||||
"%s syms at %#lx-%#lx in %s due to %s()\n",
|
||||
archive ? "Archiving" : "Discarding",
|
||||
di->text_avma,
|
||||
di->text_avma + di->text_size,
|
||||
curr->fsm.filename ? curr->fsm.filename
|
||||
: "???",
|
||||
reason);
|
||||
vg_assert(*prev_next_ptr == curr);
|
||||
*prev_next_ptr = curr->next;
|
||||
if (curr->have_dinfo)
|
||||
if (!archive) {
|
||||
*prev_next_ptr = curr->next;
|
||||
}
|
||||
if (curr->have_dinfo) {
|
||||
VG_(redir_notify_delete_DebugInfo)( curr );
|
||||
free_DebugInfo(curr);
|
||||
}
|
||||
if (archive) {
|
||||
/* Adjust the epoch markers appropriately. */
|
||||
di->last_epoch = VG_(current_DiEpoch)();
|
||||
VG_(archive_ExeContext_in_range) (di->last_epoch,
|
||||
di->text_avma, di->text_size);
|
||||
} else {
|
||||
free_DebugInfo(curr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
prev_next_ptr = &curr->next;
|
||||
@ -358,10 +506,12 @@ static Bool discard_syms_in_range ( Addr start, SizeT length )
|
||||
while (True) {
|
||||
if (curr == NULL)
|
||||
break;
|
||||
if (curr->text_present
|
||||
&& curr->text_size > 0
|
||||
&& (start+length - 1 < curr->text_avma
|
||||
|| curr->text_avma + curr->text_size - 1 < start)) {
|
||||
if (is_DebugInfo_archived(curr)
|
||||
|| !curr->text_present
|
||||
|| (curr->text_present
|
||||
&& curr->text_size > 0
|
||||
&& (start+length - 1 < curr->text_avma
|
||||
|| curr->text_avma + curr->text_size - 1 < start))) {
|
||||
/* no overlap */
|
||||
} else {
|
||||
found = True;
|
||||
@ -372,7 +522,7 @@ static Bool discard_syms_in_range ( Addr start, SizeT length )
|
||||
|
||||
if (!found) break;
|
||||
anyFound = True;
|
||||
discard_DebugInfo( curr );
|
||||
discard_or_archive_DebugInfo( curr );
|
||||
}
|
||||
|
||||
return anyFound;
|
||||
@ -418,9 +568,9 @@ static Bool do_DebugInfos_overlap ( const DebugInfo* di1, const DebugInfo* di2 )
|
||||
}
|
||||
|
||||
|
||||
/* Discard all elements of debugInfo_list whose .mark bit is set.
|
||||
/* Discard or archive all elements of debugInfo_list whose .mark bit is set.
|
||||
*/
|
||||
static void discard_marked_DebugInfos ( void )
|
||||
static void discard_or_archive_marked_DebugInfos ( void )
|
||||
{
|
||||
DebugInfo* curr;
|
||||
|
||||
@ -436,7 +586,7 @@ static void discard_marked_DebugInfos ( void )
|
||||
}
|
||||
|
||||
if (!curr) break;
|
||||
discard_DebugInfo( curr );
|
||||
discard_or_archive_DebugInfo( curr );
|
||||
|
||||
}
|
||||
}
|
||||
@ -446,19 +596,22 @@ static void discard_marked_DebugInfos ( void )
|
||||
Clearly diRef must have its mapping information set to something sane. */
|
||||
static void discard_DebugInfos_which_overlap_with ( DebugInfo* diRef )
|
||||
{
|
||||
vg_assert(is_DebugInfo_allocated(diRef));
|
||||
DebugInfo* di;
|
||||
/* Mark all the DebugInfos in debugInfo_list that need to be
|
||||
deleted. First, clear all the mark bits; then set them if they
|
||||
overlap with siRef. Since siRef itself is in this list we at
|
||||
least expect its own mark bit to be set. */
|
||||
for (di = debugInfo_list; di; di = di->next) {
|
||||
if (is_DebugInfo_archived(di))
|
||||
continue;
|
||||
di->mark = do_DebugInfos_overlap( di, diRef );
|
||||
if (di == diRef) {
|
||||
vg_assert(di->mark);
|
||||
di->mark = False;
|
||||
}
|
||||
}
|
||||
discard_marked_DebugInfos();
|
||||
discard_or_archive_marked_DebugInfos();
|
||||
}
|
||||
|
||||
|
||||
@ -470,6 +623,8 @@ static DebugInfo* find_or_create_DebugInfo_for ( const HChar* filename )
|
||||
DebugInfo* di;
|
||||
vg_assert(filename);
|
||||
for (di = debugInfo_list; di; di = di->next) {
|
||||
if (is_DebugInfo_archived(di))
|
||||
continue;
|
||||
vg_assert(di->fsm.filename);
|
||||
if (0==VG_(strcmp)(di->fsm.filename, filename))
|
||||
break;
|
||||
@ -480,6 +635,7 @@ static DebugInfo* find_or_create_DebugInfo_for ( const HChar* filename )
|
||||
di->next = debugInfo_list;
|
||||
debugInfo_list = di;
|
||||
}
|
||||
vg_assert(!is_DebugInfo_archived(di));
|
||||
return di;
|
||||
}
|
||||
|
||||
@ -723,6 +879,8 @@ static ULong di_notify_ACHIEVE_ACCEPT_STATE ( struct _DebugInfo* di )
|
||||
ULong di_handle;
|
||||
Bool ok;
|
||||
|
||||
advance_current_DiEpoch("di_notify_ACHIEVE_ACCEPT_STATE");
|
||||
|
||||
vg_assert(di->fsm.filename);
|
||||
TRACE_SYMTAB("\n");
|
||||
TRACE_SYMTAB("------ start ELF OBJECT "
|
||||
@ -734,7 +892,8 @@ static ULong di_notify_ACHIEVE_ACCEPT_STATE ( struct _DebugInfo* di )
|
||||
/* We're going to read symbols and debug info for the avma
|
||||
ranges specified in the _DebugInfoFsm mapping array. First
|
||||
get rid of any other DebugInfos which overlap any of those
|
||||
ranges (to avoid total confusion). */
|
||||
ranges (to avoid total confusion). But only those valid in
|
||||
the current epoch. We don't want to discard archived DebugInfos. */
|
||||
discard_DebugInfos_which_overlap_with( di );
|
||||
|
||||
/* The DebugInfoMappings that now exist in the FSM may involve
|
||||
@ -765,6 +924,15 @@ static ULong di_notify_ACHIEVE_ACCEPT_STATE ( struct _DebugInfo* di )
|
||||
priv_storage.h. */
|
||||
check_CFSI_related_invariants(di);
|
||||
ML_(finish_CFSI_arrays)(di);
|
||||
|
||||
// Mark di's first epoch point as a valid epoch. Because its
|
||||
// last_epoch value is still invalid, this changes di's state from
|
||||
// "allocated" to "active".
|
||||
vg_assert(is_DebugInfo_allocated(di));
|
||||
di->first_epoch = VG_(current_DiEpoch)();
|
||||
vg_assert(is_DebugInfo_active(di));
|
||||
show_epochs("di_notify_ACHIEVE_ACCEPT_STATE success");
|
||||
|
||||
/* notify m_redir about it */
|
||||
TRACE_SYMTAB("\n------ Notifying m_redir ------\n");
|
||||
VG_(redir_notify_new_DebugInfo)( di );
|
||||
@ -1077,8 +1245,11 @@ void VG_(di_notify_munmap)( Addr a, SizeT len )
|
||||
Bool anyFound;
|
||||
if (0) VG_(printf)("DISCARD %#lx %#lx\n", a, a+len);
|
||||
anyFound = discard_syms_in_range(a, len);
|
||||
if (anyFound)
|
||||
if (anyFound) {
|
||||
caches__invalidate();
|
||||
advance_current_DiEpoch("VG_(di_notify_munmap)");
|
||||
show_epochs("VG_(di_notify_munmap)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1094,8 +1265,10 @@ void VG_(di_notify_mprotect)( Addr a, SizeT len, UInt prot )
|
||||
# endif
|
||||
if (0 && !exe_ok) {
|
||||
Bool anyFound = discard_syms_in_range(a, len);
|
||||
if (anyFound)
|
||||
if (anyFound) {
|
||||
caches__invalidate();
|
||||
advance_current_DiEpoch("VG_(di_notify_mprotect)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1395,6 +1568,7 @@ void VG_(di_notify_pdb_debuginfo)( Int fd_obj, Addr avma_obj,
|
||||
caches__invalidate();
|
||||
/* dump old info for this range, if any */
|
||||
discard_syms_in_range( avma_obj, total_size );
|
||||
advance_current_DiEpoch("VG_(di_notify_pdb_debuginfo)");
|
||||
|
||||
{ DebugInfo* di = find_or_create_DebugInfo_for(exename);
|
||||
|
||||
@ -1415,7 +1589,14 @@ void VG_(di_notify_pdb_debuginfo)( Int fd_obj, Addr avma_obj,
|
||||
} else {
|
||||
VG_(message)(Vg_UserMsg, "LOAD_PDB_DEBUGINFO: failed loading info "
|
||||
"from %s\n", pdbname);
|
||||
discard_DebugInfo (di);
|
||||
/* We cannot make any sense of this pdb, so (force) discard it,
|
||||
even if VG_(clo_keep_debuginfo) is True. */
|
||||
const Bool save_clo_keep_debuginfo = VG_(clo_keep_debuginfo);
|
||||
VG_(clo_keep_debuginfo) = False;
|
||||
// The below will assert if di is not active. Not too sure what
|
||||
// the state of di in this failed loading state.
|
||||
discard_or_archive_DebugInfo (di);
|
||||
VG_(clo_keep_debuginfo) = save_clo_keep_debuginfo;
|
||||
}
|
||||
VG_(am_munmap_valgrind)( (Addr)pdbimage, n_pdbimage );
|
||||
VG_(close)(fd_pdbimage);
|
||||
@ -1474,6 +1655,7 @@ DebugInfoMapping* ML_(find_rx_mapping) ( DebugInfo* di, Addr lo, Addr hi )
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- Types and functions for inlined IP cursor ---*/
|
||||
/*------------------------------------------------------------*/
|
||||
|
||||
struct _InlIPCursor {
|
||||
Addr eip; // Cursor used to describe calls at eip.
|
||||
DebugInfo* di; // DebugInfo describing inlined calls at eip
|
||||
@ -1537,8 +1719,8 @@ Bool VG_(next_IIPC)(InlIPCursor *iipc)
|
||||
}
|
||||
|
||||
/* Forward */
|
||||
static void search_all_loctabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
/*OUT*/Word* locno );
|
||||
static void search_all_loctabs ( DiEpoch ep, Addr ptr,
|
||||
/*OUT*/DebugInfo** pdi, /*OUT*/Word* locno );
|
||||
|
||||
/* Returns the position after which eip would be inserted in inltab.
|
||||
(-1 if eip should be inserted before position 0).
|
||||
@ -1568,7 +1750,7 @@ static Word inltab_insert_pos (DebugInfo *di, Addr eip)
|
||||
return lo - 1;
|
||||
}
|
||||
|
||||
InlIPCursor* VG_(new_IIPC)(Addr eip)
|
||||
InlIPCursor* VG_(new_IIPC)(DiEpoch ep, Addr eip)
|
||||
{
|
||||
DebugInfo* di;
|
||||
Word locno;
|
||||
@ -1579,8 +1761,8 @@ InlIPCursor* VG_(new_IIPC)(Addr eip)
|
||||
if (!VG_(clo_read_inline_info))
|
||||
return NULL; // No way we can find inlined calls.
|
||||
|
||||
/* Search the DebugInfo for eip */
|
||||
search_all_loctabs ( eip, &di, &locno );
|
||||
/* Search the DebugInfo for (ep, eip) */
|
||||
search_all_loctabs ( ep, eip, &di, &locno );
|
||||
if (di == NULL || di->inltab_used == 0)
|
||||
return NULL; // No di (with inltab) containing eip.
|
||||
|
||||
@ -1644,8 +1826,8 @@ void VG_(delete_IIPC)(InlIPCursor *iipc)
|
||||
If findText==True, only text symbols are searched for.
|
||||
If findText==False, only data symbols are searched for.
|
||||
*/
|
||||
static void search_all_symtabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
/*OUT*/Word* symno,
|
||||
static void search_all_symtabs ( DiEpoch ep, Addr ptr,
|
||||
/*OUT*/DebugInfo** pdi, /*OUT*/Word* symno,
|
||||
Bool findText )
|
||||
{
|
||||
Word sno;
|
||||
@ -1654,6 +1836,9 @@ static void search_all_symtabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
|
||||
if (findText) {
|
||||
/* Consider any symbol in the r-x mapped area to be text.
|
||||
See Comment_Regarding_Text_Range_Checks in storage.c for
|
||||
@ -1701,15 +1886,17 @@ static void search_all_symtabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
}
|
||||
|
||||
|
||||
/* Search all loctabs that we know about to locate ptr. If found, set
|
||||
*pdi to the relevant DebugInfo, and *locno to the loctab entry
|
||||
/* Search all loctabs that we know about to locate ptr at epoch ep. If
|
||||
*found, set pdi to the relevant DebugInfo, and *locno to the loctab entry
|
||||
*number within that. If not found, *pdi is set to NULL. */
|
||||
static void search_all_loctabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
/*OUT*/Word* locno )
|
||||
static void search_all_loctabs ( DiEpoch ep, Addr ptr,
|
||||
/*OUT*/DebugInfo** pdi, /*OUT*/Word* locno )
|
||||
{
|
||||
Word lno;
|
||||
DebugInfo* di;
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
if (di->text_present
|
||||
&& di->text_size > 0
|
||||
&& di->text_avma <= ptr
|
||||
@ -1732,19 +1919,22 @@ static void search_all_loctabs ( Addr ptr, /*OUT*/DebugInfo** pdi,
|
||||
|
||||
typedef
|
||||
struct {
|
||||
Addr sym_avma;
|
||||
// (sym_epoch, sym_avma) are the hash table key.
|
||||
DiEpoch sym_epoch;
|
||||
Addr sym_avma;
|
||||
// Fields below here are not part of the key.
|
||||
const HChar* sym_name;
|
||||
PtrdiffT offset : (sizeof(PtrdiffT)*8)-1;
|
||||
Bool isText : 1;
|
||||
}
|
||||
Sym_Name_CacheEnt;
|
||||
/* Sym_Name_CacheEnt associates a queried address to the sym name found.
|
||||
By nature, if a sym name was found, it means the searched address
|
||||
stored in the cache is an avma (see e.g. search_all_symtabs).
|
||||
Note however that the caller is responsibe to work with 'avma'
|
||||
addresses e.g. when calling VG_(get_fnname) : m_debuginfo.c has
|
||||
no way to differentiate an 'svma a' from an 'avma a'. It is however
|
||||
unlikely that svma would percolate outside of this module. */
|
||||
/* Sym_Name_CacheEnt associates a queried (epoch, address) pair to the sym
|
||||
name found. By nature, if a sym name was found, it means the searched
|
||||
address stored in the cache is an avma (see e.g. search_all_symtabs).
|
||||
Note however that the caller is responsible to work with 'avma' addresses
|
||||
e.g. when calling VG_(get_fnname) : m_debuginfo.c has no way to
|
||||
differentiate an 'svma a' from an 'avma a'. It is however unlikely that
|
||||
svma would percolate outside of this module. */
|
||||
|
||||
static Sym_Name_CacheEnt sym_name_cache[N_SYM_NAME_CACHE];
|
||||
|
||||
@ -1760,13 +1950,15 @@ static void sym_name_cache__invalidate ( void ) {
|
||||
sym_name_cache[0].sym_name = no_sym_name;
|
||||
}
|
||||
|
||||
/* The whole point of this whole big deal: map a code address to a
|
||||
plausible symbol name. Returns False if no idea; otherwise True.
|
||||
/* The whole point of this whole big deal: map an (epoch, code address) pair
|
||||
to a plausible symbol name. Returns False if no idea; otherwise True.
|
||||
|
||||
Caller supplies buf. If do_cxx_demangling is False, don't do
|
||||
C++ demangling, regardless of VG_(clo_demangle) -- probably because the
|
||||
call has come from VG_(get_fnname_raw)(). findText
|
||||
indicates whether we're looking for a text symbol or a data symbol
|
||||
-- caller must choose one kind or the other.
|
||||
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h
|
||||
get_sym_name and the fact it calls the demangler is the main reason
|
||||
@ -1775,22 +1967,32 @@ static void sym_name_cache__invalidate ( void ) {
|
||||
(1) the DebugInfo it belongs to is not discarded
|
||||
(2) the demangler is not invoked again
|
||||
Also, the returned string is owned by "somebody else". Callers must
|
||||
not free it or modify it.*/
|
||||
not free it or modify it. */
|
||||
static
|
||||
Bool get_sym_name ( Bool do_cxx_demangling, Bool do_z_demangling,
|
||||
Bool do_below_main_renaming,
|
||||
Addr a, const HChar** buf,
|
||||
DiEpoch ep, Addr a, const HChar** buf,
|
||||
Bool match_anywhere_in_sym, Bool show_offset,
|
||||
Bool findText, /*OUT*/PtrdiffT* offsetP )
|
||||
{
|
||||
UWord hash = a % N_SYM_NAME_CACHE;
|
||||
Sym_Name_CacheEnt* se = &sym_name_cache[hash];
|
||||
// Compute the hash from 'ep' and 'a'. The latter contains lots of
|
||||
// significant bits, but 'ep' is expected to be a small number, typically
|
||||
// less than 500. So rotate it around a bit in the hope of spreading the
|
||||
// bits out somewhat.
|
||||
vg_assert(!is_DiEpoch_INVALID(ep));
|
||||
UWord hash = a ^ (UWord)(ep.n ^ ROL32(ep.n, 5)
|
||||
^ ROL32(ep.n, 13) ^ ROL32(ep.n, 19));
|
||||
hash %= N_SYM_NAME_CACHE;
|
||||
|
||||
if (UNLIKELY(se->sym_avma != a || se->isText != findText)) {
|
||||
Sym_Name_CacheEnt* se = &sym_name_cache[hash];
|
||||
|
||||
if (UNLIKELY(se->sym_epoch.n != ep.n || se->sym_avma != a
|
||||
|| se->isText != findText)) {
|
||||
DebugInfo* di;
|
||||
Word sno;
|
||||
|
||||
search_all_symtabs ( a, &di, &sno, findText );
|
||||
search_all_symtabs ( ep, a, &di, &sno, findText );
|
||||
se->sym_epoch = ep;
|
||||
se->sym_avma = a;
|
||||
se->isText = findText;
|
||||
if (di == NULL || a == 0)
|
||||
@ -1849,12 +2051,12 @@ Bool get_sym_name ( Bool do_cxx_demangling, Bool do_z_demangling,
|
||||
/* ppc64be-linux only: find the TOC pointer (R2 value) that should be in
|
||||
force at the entry point address of the function containing
|
||||
guest_code_addr. Returns 0 if not known. */
|
||||
Addr VG_(get_tocptr) ( Addr guest_code_addr )
|
||||
Addr VG_(get_tocptr) ( DiEpoch ep, Addr guest_code_addr )
|
||||
{
|
||||
#if defined(VGA_ppc64be) || defined(VGA_ppc64le)
|
||||
DebugInfo* si;
|
||||
Word sno;
|
||||
search_all_symtabs ( guest_code_addr,
|
||||
search_all_symtabs ( ep, guest_code_addr,
|
||||
&si, &sno,
|
||||
True/*consider text symbols only*/ );
|
||||
if (si == NULL)
|
||||
@ -1870,11 +2072,11 @@ Addr VG_(get_tocptr) ( Addr guest_code_addr )
|
||||
match anywhere in function, but don't show offsets.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_fnname) ( Addr a, const HChar** buf )
|
||||
Bool VG_(get_fnname) ( DiEpoch ep, Addr a, const HChar** buf )
|
||||
{
|
||||
return get_sym_name ( /*C++-demangle*/True, /*Z-demangle*/True,
|
||||
/*below-main-renaming*/True,
|
||||
a, buf,
|
||||
ep, a, buf,
|
||||
/*match_anywhere_in_fun*/True,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/True,
|
||||
@ -1885,11 +2087,11 @@ Bool VG_(get_fnname) ( Addr a, const HChar** buf )
|
||||
match anywhere in function, and show offset if nonzero.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_fnname_w_offset) ( Addr a, const HChar** buf )
|
||||
Bool VG_(get_fnname_w_offset) ( DiEpoch ep, Addr a, const HChar** buf )
|
||||
{
|
||||
return get_sym_name ( /*C++-demangle*/True, /*Z-demangle*/True,
|
||||
/*below-main-renaming*/True,
|
||||
a, buf,
|
||||
ep, a, buf,
|
||||
/*match_anywhere_in_fun*/True,
|
||||
/*show offset?*/True,
|
||||
/*text sym*/True,
|
||||
@ -1901,14 +2103,14 @@ Bool VG_(get_fnname_w_offset) ( Addr a, const HChar** buf )
|
||||
and don't show offsets.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_fnname_if_entry) ( Addr a, const HChar** buf )
|
||||
Bool VG_(get_fnname_if_entry) ( DiEpoch ep, Addr a, const HChar** buf )
|
||||
{
|
||||
const HChar *tmp;
|
||||
Bool res;
|
||||
|
||||
res = get_sym_name ( /*C++-demangle*/True, /*Z-demangle*/True,
|
||||
/*below-main-renaming*/True,
|
||||
a, &tmp,
|
||||
ep, a, &tmp,
|
||||
/*match_anywhere_in_fun*/False,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/True,
|
||||
@ -1923,11 +2125,11 @@ Bool VG_(get_fnname_if_entry) ( Addr a, const HChar** buf )
|
||||
offsets.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_fnname_raw) ( Addr a, const HChar** buf )
|
||||
Bool VG_(get_fnname_raw) ( DiEpoch ep, Addr a, const HChar** buf )
|
||||
{
|
||||
return get_sym_name ( /*C++-demangle*/False, /*Z-demangle*/False,
|
||||
/*below-main-renaming*/False,
|
||||
a, buf,
|
||||
ep, a, buf,
|
||||
/*match_anywhere_in_fun*/True,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/True,
|
||||
@ -1939,14 +2141,23 @@ Bool VG_(get_fnname_raw) ( Addr a, const HChar** buf )
|
||||
don't show offsets.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_fnname_no_cxx_demangle) ( Addr a, const HChar** buf,
|
||||
Bool VG_(get_fnname_no_cxx_demangle) ( DiEpoch ep, Addr a, const HChar** buf,
|
||||
const InlIPCursor* iipc )
|
||||
{
|
||||
// All the callers of VG_(get_fnname_no_cxx_demangle) must build
|
||||
// the iipc with the same ep as provided to VG_(get_fnname_no_cxx_demangle).
|
||||
// So, if we have an iipc, iipc->di must be valid in the provided ep.
|
||||
// Functionally, we could equally use iipc->di->first_epoch or ep, as
|
||||
// all the inlined fn calls will be described by the same di.
|
||||
if (iipc) {
|
||||
vg_assert(is_DI_valid_for_epoch(iipc->di, ep));
|
||||
}
|
||||
|
||||
if (is_bottom(iipc)) {
|
||||
// At the bottom (towards main), we describe the fn at eip.
|
||||
return get_sym_name ( /*C++-demangle*/False, /*Z-demangle*/True,
|
||||
/*below-main-renaming*/True,
|
||||
a, buf,
|
||||
ep, a, buf,
|
||||
/*match_anywhere_in_fun*/True,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/True,
|
||||
@ -1965,13 +2176,13 @@ Bool VG_(get_fnname_no_cxx_demangle) ( Addr a, const HChar** buf,
|
||||
/* mips-linux only: find the offset of current address. This is needed for
|
||||
stack unwinding for MIPS.
|
||||
*/
|
||||
Bool VG_(get_inst_offset_in_function)( Addr a,
|
||||
Bool VG_(get_inst_offset_in_function)( DiEpoch ep, Addr a,
|
||||
/*OUT*/PtrdiffT* offset )
|
||||
{
|
||||
const HChar *fnname;
|
||||
return get_sym_name ( /*C++-demangle*/False, /*Z-demangle*/False,
|
||||
/*below-main-renaming*/False,
|
||||
a, &fnname,
|
||||
ep, a, &fnname,
|
||||
/*match_anywhere_in_sym*/True,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/True,
|
||||
@ -2006,13 +2217,13 @@ Vg_FnNameKind VG_(get_fnname_kind) ( const HChar* name )
|
||||
}
|
||||
}
|
||||
|
||||
Vg_FnNameKind VG_(get_fnname_kind_from_IP) ( Addr ip )
|
||||
Vg_FnNameKind VG_(get_fnname_kind_from_IP) ( DiEpoch ep, Addr ip )
|
||||
{
|
||||
const HChar *buf;
|
||||
|
||||
// We don't demangle, because it's faster not to, and the special names
|
||||
// we're looking for won't be mangled.
|
||||
if (VG_(get_fnname_raw) ( ip, &buf )) {
|
||||
if (VG_(get_fnname_raw) ( ep, ip, &buf )) {
|
||||
|
||||
return VG_(get_fnname_kind)(buf);
|
||||
} else {
|
||||
@ -2025,13 +2236,13 @@ Vg_FnNameKind VG_(get_fnname_kind_from_IP) ( Addr ip )
|
||||
Also data_addr's offset from the symbol start is put into *offset.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership
|
||||
in pub_tool_debuginfo.h */
|
||||
Bool VG_(get_datasym_and_offset)( Addr data_addr,
|
||||
Bool VG_(get_datasym_and_offset)( DiEpoch ep, Addr data_addr,
|
||||
/*OUT*/const HChar** dname,
|
||||
/*OUT*/PtrdiffT* offset )
|
||||
{
|
||||
return get_sym_name ( /*C++-demangle*/False, /*Z-demangle*/False,
|
||||
/*below-main-renaming*/False,
|
||||
data_addr, dname,
|
||||
ep, data_addr, dname,
|
||||
/*match_anywhere_in_sym*/True,
|
||||
/*show offset?*/False,
|
||||
/*text sym*/False,
|
||||
@ -2044,7 +2255,7 @@ Bool VG_(get_datasym_and_offset)( Addr data_addr,
|
||||
(1) the DebugInfo it belongs to is not discarded
|
||||
(2) the segment containing the address is not merged with another segment
|
||||
*/
|
||||
Bool VG_(get_objname) ( Addr a, const HChar** objname )
|
||||
Bool VG_(get_objname) ( DiEpoch ep, Addr a, const HChar** objname )
|
||||
{
|
||||
DebugInfo* di;
|
||||
const NSegment *seg;
|
||||
@ -2053,6 +2264,8 @@ Bool VG_(get_objname) ( Addr a, const HChar** objname )
|
||||
/* Look in the debugInfo_list to find the name. In most cases we
|
||||
expect this to produce a result. */
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
if (di->text_present
|
||||
&& di->text_size > 0
|
||||
&& di->text_avma <= a
|
||||
@ -2065,8 +2278,13 @@ Bool VG_(get_objname) ( Addr a, const HChar** objname )
|
||||
the debugInfo_list, ask the address space manager whether it
|
||||
knows the name of the file associated with this mapping. This
|
||||
allows us to print the names of exe/dll files in the stack trace
|
||||
when running programs under wine. */
|
||||
if ( (seg = VG_(am_find_nsegment)(a)) != NULL
|
||||
when running programs under wine.
|
||||
|
||||
Restrict this to the case where 'ep' is the current epoch, though, so
|
||||
that we don't return information about this epoch when the caller was
|
||||
enquiring about a different one. */
|
||||
if ( eq_DiEpoch(ep, VG_(current_DiEpoch)())
|
||||
&& (seg = VG_(am_find_nsegment)(a)) != NULL
|
||||
&& (filename = VG_(am_get_filename)(seg)) != NULL ) {
|
||||
*objname = filename;
|
||||
return True;
|
||||
@ -2076,12 +2294,14 @@ Bool VG_(get_objname) ( Addr a, const HChar** objname )
|
||||
|
||||
/* Map a code address to its DebugInfo. Returns NULL if not found. Doesn't
|
||||
require debug info. */
|
||||
DebugInfo* VG_(find_DebugInfo) ( Addr a )
|
||||
DebugInfo* VG_(find_DebugInfo) ( DiEpoch ep, Addr a )
|
||||
{
|
||||
static UWord n_search = 0;
|
||||
DebugInfo* di;
|
||||
n_search++;
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
if (di->text_present
|
||||
&& di->text_size > 0
|
||||
&& di->text_avma <= a
|
||||
@ -2097,13 +2317,13 @@ DebugInfo* VG_(find_DebugInfo) ( Addr a )
|
||||
/* Map a code address to a filename. Returns True if successful. The
|
||||
returned string is persistent as long as the DebugInfo to which it
|
||||
belongs is not discarded. */
|
||||
Bool VG_(get_filename)( Addr a, const HChar** filename )
|
||||
Bool VG_(get_filename)( DiEpoch ep, Addr a, const HChar** filename )
|
||||
{
|
||||
DebugInfo* si;
|
||||
Word locno;
|
||||
UInt fndn_ix;
|
||||
|
||||
search_all_loctabs ( a, &si, &locno );
|
||||
search_all_loctabs ( ep, a, &si, &locno );
|
||||
if (si == NULL)
|
||||
return False;
|
||||
fndn_ix = ML_(fndn_ix) (si, locno);
|
||||
@ -2112,11 +2332,11 @@ Bool VG_(get_filename)( Addr a, const HChar** filename )
|
||||
}
|
||||
|
||||
/* Map a code address to a line number. Returns True if successful. */
|
||||
Bool VG_(get_linenum)( Addr a, UInt* lineno )
|
||||
Bool VG_(get_linenum)( DiEpoch ep, Addr a, UInt* lineno )
|
||||
{
|
||||
DebugInfo* si;
|
||||
Word locno;
|
||||
search_all_loctabs ( a, &si, &locno );
|
||||
search_all_loctabs ( ep, a, &si, &locno );
|
||||
if (si == NULL)
|
||||
return False;
|
||||
*lineno = si->loctab[locno].lineno;
|
||||
@ -2127,7 +2347,7 @@ Bool VG_(get_linenum)( Addr a, UInt* lineno )
|
||||
/* Map a code address to a filename/line number/dir name info.
|
||||
See prototype for detailed description of behaviour.
|
||||
*/
|
||||
Bool VG_(get_filename_linenum) ( Addr a,
|
||||
Bool VG_(get_filename_linenum) ( DiEpoch ep, Addr a,
|
||||
/*OUT*/const HChar** filename,
|
||||
/*OUT*/const HChar** dirname,
|
||||
/*OUT*/UInt* lineno )
|
||||
@ -2136,7 +2356,7 @@ Bool VG_(get_filename_linenum) ( Addr a,
|
||||
Word locno;
|
||||
UInt fndn_ix;
|
||||
|
||||
search_all_loctabs ( a, &si, &locno );
|
||||
search_all_loctabs ( ep, a, &si, &locno );
|
||||
if (si == NULL) {
|
||||
if (dirname) {
|
||||
*dirname = "";
|
||||
@ -2165,7 +2385,8 @@ Bool VG_(get_filename_linenum) ( Addr a,
|
||||
Therefore specify "*" to search all the objects. On TOC-afflicted
|
||||
platforms, a symbol is deemed to be found only if it has a nonzero
|
||||
TOC pointer. */
|
||||
Bool VG_(lookup_symbol_SLOW)(const HChar* sopatt, const HChar* name,
|
||||
Bool VG_(lookup_symbol_SLOW)(DiEpoch ep,
|
||||
const HChar* sopatt, const HChar* name,
|
||||
SymAVMAs* avmas)
|
||||
{
|
||||
Bool require_pToc = False;
|
||||
@ -2178,6 +2399,8 @@ Bool VG_(lookup_symbol_SLOW)(const HChar* sopatt, const HChar* name,
|
||||
for (si = debugInfo_list; si; si = si->next) {
|
||||
if (debug)
|
||||
VG_(printf)("lookup_symbol_SLOW: considering %s\n", si->soname);
|
||||
if (!is_DI_valid_for_epoch(si, ep))
|
||||
continue;
|
||||
if (!VG_(string_match)(sopatt, si->soname)) {
|
||||
if (debug)
|
||||
VG_(printf)(" ... skip\n");
|
||||
@ -2260,7 +2483,7 @@ putStrEsc( SizeT n, HChar** buf, SizeT *bufsiz, const HChar* str )
|
||||
return n;
|
||||
}
|
||||
|
||||
const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor *iipc)
|
||||
const HChar* VG_(describe_IP)(DiEpoch ep, Addr eip, const InlIPCursor *iipc)
|
||||
{
|
||||
static HChar *buf = NULL;
|
||||
static SizeT bufsiz = 0;
|
||||
@ -2273,7 +2496,10 @@ const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor *iipc)
|
||||
HChar ibuf[50]; // large enough
|
||||
SizeT n = 0;
|
||||
|
||||
vg_assert (!iipc || iipc->eip == eip);
|
||||
// An InlIPCursor is associated with one specific DebugInfo. So if
|
||||
// it exists, make sure that it is valid for the specified DiEpoch.
|
||||
vg_assert (!iipc
|
||||
|| (is_DI_valid_for_epoch(iipc->di, ep) && iipc->eip == eip));
|
||||
|
||||
const HChar *buf_fn;
|
||||
const HChar *buf_obj;
|
||||
@ -2288,8 +2514,8 @@ const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor *iipc)
|
||||
if (is_bottom(iipc)) {
|
||||
// At the bottom (towards main), we describe the fn at eip.
|
||||
know_fnname = VG_(clo_sym_offsets)
|
||||
? VG_(get_fnname_w_offset) (eip, &buf_fn)
|
||||
: VG_(get_fnname) (eip, &buf_fn);
|
||||
? VG_(get_fnname_w_offset) (ep, eip, &buf_fn)
|
||||
: VG_(get_fnname) (ep, eip, &buf_fn);
|
||||
} else {
|
||||
const DiInlLoc *next_inl = iipc && iipc->next_inltab >= 0
|
||||
? & iipc->di->inltab[iipc->next_inltab]
|
||||
@ -2307,15 +2533,15 @@ const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor *iipc)
|
||||
// ??? Currently never showing an offset.
|
||||
}
|
||||
|
||||
know_objname = VG_(get_objname)(eip, &buf_obj);
|
||||
know_objname = VG_(get_objname)(ep, eip, &buf_obj);
|
||||
|
||||
if (is_top(iipc)) {
|
||||
// The source for the highest level is in the loctab entry.
|
||||
know_srcloc = VG_(get_filename_linenum)(
|
||||
eip,
|
||||
&buf_srcloc,
|
||||
ep, eip,
|
||||
&buf_srcloc,
|
||||
&buf_dirname,
|
||||
&lineno
|
||||
&lineno
|
||||
);
|
||||
know_dirinfo = buf_dirname[0] != '\0';
|
||||
} else {
|
||||
@ -2465,6 +2691,20 @@ const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor *iipc)
|
||||
/*--- ---*/
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
/* Note that the CFI machinery pertains to unwinding the stack "right now".
|
||||
There is no support for unwinding stack images obtained from some time in
|
||||
the past. That means that:
|
||||
|
||||
(1) We only deal with CFI from DebugInfos that are valid for the current
|
||||
debuginfo epoch. Unlike in the rest of the file, there is no
|
||||
epoch-awareness.
|
||||
|
||||
(2) We assume that the CFI cache will be invalidated every time the the
|
||||
epoch changes. This is done by ensuring (in the file above) that
|
||||
every call to advance_current_DiEpoch has a call to
|
||||
caches__invalidate alongside it.
|
||||
*/
|
||||
|
||||
/* Gather up all the constant pieces of info needed to evaluate
|
||||
a CfiExpr into one convenient struct. */
|
||||
typedef
|
||||
@ -2585,6 +2825,9 @@ UWord evalCfiExpr ( const XArray* exprs, Int ix,
|
||||
*cfsi_mP to the cfsi_m pointer in that DebugInfo's cfsi_m_pool.
|
||||
|
||||
If not found, set *diP to (DebugInfo*)1 and *cfsi_mP to zero.
|
||||
|
||||
Per comments at the top of this section, we only look for CFI in
|
||||
DebugInfos that are valid for the current epoch.
|
||||
*/
|
||||
__attribute__((noinline))
|
||||
static void find_DiCfSI ( /*OUT*/DebugInfo** diP,
|
||||
@ -2600,10 +2843,15 @@ static void find_DiCfSI ( /*OUT*/DebugInfo** diP,
|
||||
|
||||
if (0) VG_(printf)("search for %#lx\n", ip);
|
||||
|
||||
DiEpoch curr_epoch = VG_(current_DiEpoch)();
|
||||
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
Word j;
|
||||
n_steps++;
|
||||
|
||||
if (!is_DI_valid_for_epoch(di, curr_epoch))
|
||||
continue;
|
||||
|
||||
/* Use the per-DebugInfo summary address ranges to skip
|
||||
inapplicable DebugInfos quickly. */
|
||||
if (di->cfsi_used == 0)
|
||||
@ -2611,6 +2859,11 @@ static void find_DiCfSI ( /*OUT*/DebugInfo** diP,
|
||||
if (ip < di->cfsi_minavma || ip > di->cfsi_maxavma)
|
||||
continue;
|
||||
|
||||
// This di must be active (because we have explicitly chosen not to
|
||||
// allow unwinding stacks that pertain to some past epoch). It can't
|
||||
// be archived or not-yet-active.
|
||||
vg_assert(is_DebugInfo_active(di));
|
||||
|
||||
/* It might be in this DebugInfo. Search it. */
|
||||
j = ML_(search_one_cfitab)( di, ip );
|
||||
vg_assert(j >= -1 && j < (Word)di->cfsi_used);
|
||||
@ -3044,6 +3297,7 @@ Bool VG_(use_CF_info) ( /*MOD*/D3UnwindRegs* uregsHere,
|
||||
Bool VG_(use_FPO_info) ( /*MOD*/Addr* ipP,
|
||||
/*MOD*/Addr* spP,
|
||||
/*MOD*/Addr* fpP,
|
||||
DiEpoch ep,
|
||||
Addr min_accessible,
|
||||
Addr max_accessible )
|
||||
{
|
||||
@ -3061,6 +3315,9 @@ Bool VG_(use_FPO_info) ( /*MOD*/Addr* ipP,
|
||||
for (di = debugInfo_list; di != NULL; di = di->next) {
|
||||
n_steps++;
|
||||
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
|
||||
/* Use the per-DebugInfo summary address ranges to skip
|
||||
inapplicable DebugInfos quickly. */
|
||||
if (di->fpo == NULL)
|
||||
@ -3564,6 +3821,7 @@ static void format_message ( /*MOD*/XArray* /* of HChar */ dn1,
|
||||
static
|
||||
Bool consider_vars_in_frame ( /*MOD*/XArray* /* of HChar */ dname1,
|
||||
/*MOD*/XArray* /* of HChar */ dname2,
|
||||
DiEpoch ep,
|
||||
Addr data_addr,
|
||||
Addr ip, Addr sp, Addr fp,
|
||||
/* shown to user: */
|
||||
@ -3582,6 +3840,8 @@ Bool consider_vars_in_frame ( /*MOD*/XArray* /* of HChar */ dname1,
|
||||
/* first, find the DebugInfo that pertains to 'ip'. */
|
||||
for (di = debugInfo_list; di; di = di->next) {
|
||||
n_steps++;
|
||||
if (!is_DI_valid_for_epoch(di, ep))
|
||||
continue;
|
||||
/* text segment missing? unlikely, but handle it .. */
|
||||
if (!di->text_present || di->text_size == 0)
|
||||
continue;
|
||||
@ -3699,7 +3959,7 @@ Bool consider_vars_in_frame ( /*MOD*/XArray* /* of HChar */ dname1,
|
||||
Bool VG_(get_data_description)(
|
||||
/*MOD*/ XArray* /* of HChar */ dname1,
|
||||
/*MOD*/ XArray* /* of HChar */ dname2,
|
||||
Addr data_addr
|
||||
DiEpoch ep, Addr data_addr
|
||||
)
|
||||
{
|
||||
# define N_FRAMES 8
|
||||
@ -3819,7 +4079,7 @@ Bool VG_(get_data_description)(
|
||||
vg_assert(n_frames >= 0 && n_frames <= N_FRAMES);
|
||||
for (j = 0; j < n_frames; j++) {
|
||||
if (consider_vars_in_frame( dname1, dname2,
|
||||
data_addr,
|
||||
ep, data_addr,
|
||||
ips[j],
|
||||
sps[j], fps[j], tid, j )) {
|
||||
zterm_XA( dname1 );
|
||||
@ -3846,7 +4106,7 @@ Bool VG_(get_data_description)(
|
||||
equivalent kludge. */
|
||||
if (j > 0 /* this is a non-innermost frame */
|
||||
&& consider_vars_in_frame( dname1, dname2,
|
||||
data_addr,
|
||||
ep, data_addr,
|
||||
ips[j] + 1,
|
||||
sps[j], fps[j], tid, j )) {
|
||||
zterm_XA( dname1 );
|
||||
|
||||
@ -588,6 +588,36 @@ struct _DebugInfo {
|
||||
structure is allocated. */
|
||||
ULong handle;
|
||||
|
||||
/* The range of epochs for which this DebugInfo is valid. These also
|
||||
divide the DebugInfo's lifetime into three parts:
|
||||
|
||||
(1) Allocated: but with only .fsm holding useful info -- in
|
||||
particular, not yet holding any debug info.
|
||||
.first_epoch == DebugInfoEpoch_INVALID
|
||||
.last_epoch == DebugInfoEpoch_INVALID
|
||||
|
||||
(2) Active: containing debug info, and current.
|
||||
.first_epoch != DebugInfoEpoch_INVALID
|
||||
.last_epoch == DebugInfoEpoch_INVALID
|
||||
|
||||
(3) Archived: containing debug info, but no longer current.
|
||||
.first_epoch != DebugInfoEpoch_INVALID
|
||||
.last_epoch != DebugInfoEpoch_INVALID
|
||||
|
||||
State (2) corresponds to an object which is currently mapped. When
|
||||
the object is unmapped, what happens depends on the setting of
|
||||
--keep-debuginfo:
|
||||
|
||||
* when =no, the DebugInfo is removed from debugInfo_list and
|
||||
deleted.
|
||||
|
||||
* when =yes, the DebugInfo is retained in debugInfo_list, but its
|
||||
.last_epoch field is filled in, and current_epoch is advanced. This
|
||||
effectively moves the DebugInfo into state (3).
|
||||
*/
|
||||
DiEpoch first_epoch;
|
||||
DiEpoch last_epoch;
|
||||
|
||||
/* Used for debugging only - indicate what stuff to dump whilst
|
||||
reading stuff into the seginfo. Are computed as early in the
|
||||
lifetime of the DebugInfo as possible -- at the point when it is
|
||||
|
||||
@ -321,15 +321,15 @@ static Bool eq_Error ( VgRes res, const Error* e1, const Error* e2 )
|
||||
*/
|
||||
#define ERRTXT_LEN 4096
|
||||
|
||||
static void printSuppForIp_XML(UInt n, Addr ip, void* uu_opaque)
|
||||
static void printSuppForIp_XML(UInt n, DiEpoch ep, Addr ip, void* uu_opaque)
|
||||
{
|
||||
const HChar *buf;
|
||||
InlIPCursor* iipc = VG_(new_IIPC)(ip);
|
||||
InlIPCursor* iipc = VG_(new_IIPC)(ep, ip);
|
||||
do {
|
||||
if ( VG_(get_fnname_no_cxx_demangle) (ip, &buf, iipc) ) {
|
||||
if ( VG_(get_fnname_no_cxx_demangle) (ep, ip, &buf, iipc) ) {
|
||||
VG_(printf_xml)(" <sframe> <fun>%pS</fun> </sframe>\n", buf);
|
||||
} else
|
||||
if ( VG_(get_objname)(ip, &buf) ) {
|
||||
if ( VG_(get_objname)(ep, ip, &buf) ) {
|
||||
VG_(printf_xml)(" <sframe> <obj>%pS</obj> </sframe>\n", buf);
|
||||
} else {
|
||||
VG_(printf_xml)(" <sframe> <obj>*</obj> </sframe>\n");
|
||||
@ -338,16 +338,16 @@ static void printSuppForIp_XML(UInt n, Addr ip, void* uu_opaque)
|
||||
VG_(delete_IIPC)(iipc);
|
||||
}
|
||||
|
||||
static void printSuppForIp_nonXML(UInt n, Addr ip, void* textV)
|
||||
static void printSuppForIp_nonXML(UInt n, DiEpoch ep, Addr ip, void* textV)
|
||||
{
|
||||
const HChar *buf;
|
||||
XArray* /* of HChar */ text = (XArray*)textV;
|
||||
InlIPCursor* iipc = VG_(new_IIPC)(ip);
|
||||
InlIPCursor* iipc = VG_(new_IIPC)(ep, ip);
|
||||
do {
|
||||
if ( VG_(get_fnname_no_cxx_demangle) (ip, &buf, iipc) ) {
|
||||
if ( VG_(get_fnname_no_cxx_demangle) (ep, ip, &buf, iipc) ) {
|
||||
VG_(xaprintf)(text, " fun:%s\n", buf);
|
||||
} else
|
||||
if ( VG_(get_objname)(ip, &buf) ) {
|
||||
if ( VG_(get_objname)(ep, ip, &buf) ) {
|
||||
VG_(xaprintf)(text, " obj:%s\n", buf);
|
||||
} else {
|
||||
VG_(xaprintf)(text, " obj:*\n");
|
||||
@ -412,7 +412,7 @@ static void gen_suppression(const Error* err)
|
||||
vg_assert(n_ips > 0);
|
||||
vg_assert(n_ips <= VG_DEEPEST_BACKTRACE);
|
||||
VG_(apply_StackTrace)(printSuppForIp_nonXML,
|
||||
text,
|
||||
text, VG_(get_ExeContext_epoch)(ec),
|
||||
VG_(get_ExeContext_StackTrace)(ec),
|
||||
n_ips);
|
||||
|
||||
@ -441,7 +441,7 @@ static void gen_suppression(const Error* err)
|
||||
|
||||
// Print stack trace elements
|
||||
VG_(apply_StackTrace)(printSuppForIp_XML,
|
||||
NULL,
|
||||
NULL, VG_(get_ExeContext_epoch)(ec),
|
||||
VG_(get_ExeContext_StackTrace)(ec),
|
||||
VG_(get_ExeContext_n_ips)(ec));
|
||||
|
||||
@ -660,7 +660,7 @@ void construct_error ( Error* err, ThreadId tid, ErrorKind ekind, Addr a,
|
||||
err->count = 1;
|
||||
err->tid = tid;
|
||||
if (NULL == where)
|
||||
err->where = VG_(record_ExeContext)( tid, 0 );
|
||||
err->where = VG_(record_ExeContext)( tid, 0 );
|
||||
else
|
||||
err->where = where;
|
||||
|
||||
@ -1509,6 +1509,7 @@ static Bool supploc_IsQuery ( const void* supplocV )
|
||||
allocations and the nr of debuginfo search. */
|
||||
typedef
|
||||
struct {
|
||||
DiEpoch epoch; // used to interpret .ips
|
||||
StackTrace ips; // stack trace we are lazily completing.
|
||||
UWord n_ips; // nr of elements in ips.
|
||||
|
||||
@ -1604,9 +1605,10 @@ static void clearIPtoFunOrObjCompleter ( const Supp *su,
|
||||
su->sname,
|
||||
filename,
|
||||
su->sname_lineno);
|
||||
} else
|
||||
} else {
|
||||
VG_(dmsg)("errormgr matching end no suppression matched:\n");
|
||||
VG_(pp_StackTrace) (ip2fo->ips, ip2fo->n_ips);
|
||||
}
|
||||
VG_(pp_StackTrace) (ip2fo->epoch, ip2fo->ips, ip2fo->n_ips);
|
||||
pp_ip2fo(ip2fo);
|
||||
}
|
||||
if (ip2fo->n_offsets_per_ip) VG_(free)(ip2fo->n_offsets_per_ip);
|
||||
@ -1669,7 +1671,8 @@ static HChar* foComplete(IPtoFunOrObjCompleter* ip2fo,
|
||||
// up comparing "malloc" in the suppression against
|
||||
// "_vgrZU_libcZdsoZa_malloc" in the backtrace, and the
|
||||
// two of them need to be made to match.
|
||||
if (!VG_(get_fnname_no_cxx_demangle)(ip2fo->ips[ixInput],
|
||||
if (!VG_(get_fnname_no_cxx_demangle)(ip2fo->epoch,
|
||||
ip2fo->ips[ixInput],
|
||||
&caller,
|
||||
NULL))
|
||||
caller = "???";
|
||||
@ -1690,7 +1693,7 @@ static HChar* foComplete(IPtoFunOrObjCompleter* ip2fo,
|
||||
last_expand_pos_ips is the last offset in fun/obj where
|
||||
ips[pos_ips] has been expanded. */
|
||||
|
||||
if (!VG_(get_objname)(ip2fo->ips[pos_ips], &caller))
|
||||
if (!VG_(get_objname)(ip2fo->epoch, ip2fo->ips[pos_ips], &caller))
|
||||
caller = "???";
|
||||
|
||||
// Have all inlined calls pointing at this object name
|
||||
@ -1760,7 +1763,7 @@ static void expandInput (IPtoFunOrObjCompleter* ip2fo, UWord ixInput )
|
||||
const Addr IP = ip2fo->ips[ip2fo->n_ips_expanded];
|
||||
InlIPCursor *iipc;
|
||||
|
||||
iipc = VG_(new_IIPC)(IP);
|
||||
iipc = VG_(new_IIPC)(ip2fo->epoch, IP);
|
||||
// The only thing we really need is the nr of inlined fn calls
|
||||
// corresponding to the IP we will expand.
|
||||
// However, computing this is mostly the same as finding
|
||||
@ -1769,7 +1772,7 @@ static void expandInput (IPtoFunOrObjCompleter* ip2fo, UWord ixInput )
|
||||
const HChar *caller;
|
||||
grow_offsets(ip2fo, ip2fo->n_expanded+1);
|
||||
ip2fo->fun_offsets[ip2fo->n_expanded] = ip2fo->names_free;
|
||||
if (!VG_(get_fnname_no_cxx_demangle)(IP,
|
||||
if (!VG_(get_fnname_no_cxx_demangle)(ip2fo->epoch, IP,
|
||||
&caller,
|
||||
iipc))
|
||||
caller = "???";
|
||||
@ -1797,18 +1800,18 @@ static void expandInput (IPtoFunOrObjCompleter* ip2fo, UWord ixInput )
|
||||
}
|
||||
}
|
||||
|
||||
static Bool haveInputInpC (void* inputCompleter, UWord ixInput )
|
||||
static Bool haveInputInpC (void* inputCompleterV, UWord ixInput )
|
||||
{
|
||||
IPtoFunOrObjCompleter* ip2fo = inputCompleter;
|
||||
IPtoFunOrObjCompleter* ip2fo = (IPtoFunOrObjCompleter*)inputCompleterV;
|
||||
expandInput(ip2fo, ixInput);
|
||||
return ixInput < ip2fo->n_expanded;
|
||||
}
|
||||
|
||||
static Bool supp_pattEQinp ( const void* supplocV, const void* addrV,
|
||||
void* inputCompleter, UWord ixInput )
|
||||
void* inputCompleterV, UWord ixInput )
|
||||
{
|
||||
const SuppLoc* supploc = supplocV; /* PATTERN */
|
||||
IPtoFunOrObjCompleter* ip2fo = inputCompleter;
|
||||
const SuppLoc* supploc = (const SuppLoc*)supplocV; /* PATTERN */
|
||||
IPtoFunOrObjCompleter* ip2fo = (IPtoFunOrObjCompleter*)inputCompleterV;
|
||||
HChar* funobj_name; // Fun or Obj name.
|
||||
Bool ret;
|
||||
|
||||
@ -1935,6 +1938,7 @@ static Supp* is_suppressible_error ( const Error* err )
|
||||
em_supplist_searches++;
|
||||
|
||||
/* Prepare the lazy input completer. */
|
||||
ip2fo.epoch = VG_(get_ExeContext_epoch)(err->where);
|
||||
ip2fo.ips = VG_(get_ExeContext_StackTrace)(err->where);
|
||||
ip2fo.n_ips = VG_(get_ExeContext_n_ips)(err->where);
|
||||
ip2fo.n_ips_expanded = 0;
|
||||
|
||||
@ -81,6 +81,21 @@ struct _ExeContext {
|
||||
be a multiple of four, and must be unique. Hence they start at
|
||||
4. */
|
||||
UInt ecu;
|
||||
/* epoch in which the ExeContext can be symbolised. In other words, epoch
|
||||
identifies the set of debug info to use to symbolise the Addr in ips
|
||||
using e.g. VG_(get_filename), VG_(get_fnname), ...
|
||||
Note 1: 2 ExeContexts are equal when their ips array are equal and
|
||||
their epoch are equal.
|
||||
Note 2: a freshly created ExeContext has a DiEpoch_INVALID epoch.
|
||||
DiEpoch_INVALID is used as a special value to indicate that ExeContext
|
||||
is valid in the current epoch. VG_(get_ExeContext_epoch) translates
|
||||
this invalid value in the real current epoch.
|
||||
When a debug info is archived, the set of ExeContext is scanned :
|
||||
If an ExeContext with epoch == DiEpoch_INVALID has one or more
|
||||
ips Addr corresponding to the just archived debug info, the ExeContext
|
||||
epoch is changed to the last epoch identifying the set containing the
|
||||
archived debug info. */
|
||||
DiEpoch epoch;
|
||||
/* Variable-length array. The size is 'n_ips'; at
|
||||
least 1, at most VG_DEEPEST_BACKTRACE. [0] is the current IP,
|
||||
[1] is its caller, [2] is the caller of [1], etc. */
|
||||
@ -116,7 +131,7 @@ static ULong ec_cmpAlls;
|
||||
|
||||
|
||||
/*------------------------------------------------------------*/
|
||||
/*--- Exported functions. ---*/
|
||||
/*--- ExeContext functions. ---*/
|
||||
/*------------------------------------------------------------*/
|
||||
|
||||
static ExeContext* record_ExeContext_wrk2 ( const Addr* ips, UInt n_ips );
|
||||
@ -153,6 +168,13 @@ static void init_ExeContext_storage ( void )
|
||||
init_done = True;
|
||||
}
|
||||
|
||||
DiEpoch VG_(get_ExeContext_epoch)( const ExeContext* e )
|
||||
{
|
||||
if (is_DiEpoch_INVALID (e->epoch))
|
||||
return VG_(current_DiEpoch)();
|
||||
else
|
||||
return e->epoch;
|
||||
}
|
||||
|
||||
/* Print stats. */
|
||||
void VG_(print_ExeContext_stats) ( Bool with_stacktraces )
|
||||
@ -167,9 +189,11 @@ void VG_(print_ExeContext_stats) ( Bool with_stacktraces )
|
||||
VG_(message)(Vg_DebugMsg, " exectx: Printing contexts stacktraces\n");
|
||||
for (i = 0; i < ec_htab_size; i++) {
|
||||
for (ec = ec_htab[i]; ec; ec = ec->chain) {
|
||||
VG_(message)(Vg_DebugMsg, " exectx: stacktrace ecu %u n_ips %u\n",
|
||||
ec->ecu, ec->n_ips);
|
||||
VG_(pp_StackTrace)( ec->ips, ec->n_ips );
|
||||
VG_(message)(Vg_DebugMsg,
|
||||
" exectx: stacktrace ecu %u epoch %u n_ips %u\n",
|
||||
ec->ecu, ec->epoch.n, ec->n_ips);
|
||||
VG_(pp_StackTrace)( VG_(get_ExeContext_epoch)(ec),
|
||||
ec->ips, ec->n_ips );
|
||||
}
|
||||
}
|
||||
VG_(message)(Vg_DebugMsg,
|
||||
@ -205,10 +229,39 @@ void VG_(print_ExeContext_stats) ( Bool with_stacktraces )
|
||||
/* Print an ExeContext. */
|
||||
void VG_(pp_ExeContext) ( ExeContext* ec )
|
||||
{
|
||||
VG_(pp_StackTrace)( ec->ips, ec->n_ips );
|
||||
VG_(pp_StackTrace)( VG_(get_ExeContext_epoch)(ec), ec->ips, ec->n_ips );
|
||||
}
|
||||
|
||||
|
||||
void VG_(archive_ExeContext_in_range) (DiEpoch last_epoch,
|
||||
Addr text_avma, SizeT length )
|
||||
{
|
||||
Int i;
|
||||
ExeContext* ec;
|
||||
ULong n_archived = 0;
|
||||
const Addr text_avma_end = text_avma + length - 1;
|
||||
|
||||
if (VG_(clo_verbosity) > 1)
|
||||
VG_(message)(Vg_DebugMsg, "Scanning and archiving ExeContexts ...\n");
|
||||
for (i = 0; i < ec_htab_size; i++) {
|
||||
for (ec = ec_htab[i]; ec; ec = ec->chain) {
|
||||
if (is_DiEpoch_INVALID (ec->epoch))
|
||||
for (UInt j = 0; j < ec->n_ips; j++) {
|
||||
if (UNLIKELY(ec->ips[j] >= text_avma
|
||||
&& ec->ips[j] <= text_avma_end)) {
|
||||
ec->epoch = last_epoch;
|
||||
n_archived++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (VG_(clo_verbosity) > 1)
|
||||
VG_(message)(Vg_DebugMsg,
|
||||
"Scanned %'llu ExeContexts archived %'llu ExeContexts\n",
|
||||
ec_totstored, n_archived);
|
||||
}
|
||||
|
||||
/* Compare two ExeContexts. Number of callers considered depends on res. */
|
||||
Bool VG_(eq_ExeContext) ( VgRes res, const ExeContext* e1,
|
||||
const ExeContext* e2 )
|
||||
@ -221,6 +274,9 @@ Bool VG_(eq_ExeContext) ( VgRes res, const ExeContext* e1,
|
||||
// Must be at least one address in each trace.
|
||||
vg_assert(e1->n_ips >= 1 && e2->n_ips >= 1);
|
||||
|
||||
// Note: we compare the epoch in the case below, and not here
|
||||
// to have the ec_cmp* stats correct.
|
||||
|
||||
switch (res) {
|
||||
case Vg_LowRes:
|
||||
/* Just compare the top two callers. */
|
||||
@ -231,7 +287,7 @@ Bool VG_(eq_ExeContext) ( VgRes res, const ExeContext* e1,
|
||||
if (!(e1->n_ips <= i) && (e2->n_ips <= i)) return False;
|
||||
if (e1->ips[i] != e2->ips[i]) return False;
|
||||
}
|
||||
return True;
|
||||
return e1->epoch.n == e2->epoch.n;
|
||||
|
||||
case Vg_MedRes:
|
||||
/* Just compare the top four callers. */
|
||||
@ -242,7 +298,7 @@ Bool VG_(eq_ExeContext) ( VgRes res, const ExeContext* e1,
|
||||
if (!(e1->n_ips <= i) && (e2->n_ips <= i)) return False;
|
||||
if (e1->ips[i] != e2->ips[i]) return False;
|
||||
}
|
||||
return True;
|
||||
return e1->epoch.n == e2->epoch.n;
|
||||
|
||||
case Vg_HighRes:
|
||||
ec_cmpAlls++;
|
||||
@ -425,7 +481,7 @@ static ExeContext* record_ExeContext_wrk2 ( const Addr* ips, UInt n_ips )
|
||||
while (True) {
|
||||
if (list == NULL) break;
|
||||
ec_searchcmps++;
|
||||
same = list->n_ips == n_ips;
|
||||
same = list->n_ips == n_ips && is_DiEpoch_INVALID (list->epoch);
|
||||
for (i = 0; i < n_ips && same ; i++) {
|
||||
same = list->ips[i] == ips[i];
|
||||
}
|
||||
|
||||
@ -142,14 +142,17 @@ static HChar* sym (Addr addr, Bool is_code)
|
||||
PtrdiffT offset;
|
||||
if (w == 2) w = 0;
|
||||
|
||||
// sym is used for debugging/tracing, so cur_ep is a reasonable choice.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
if (is_code) {
|
||||
const HChar *name;
|
||||
name = VG_(describe_IP) (addr, NULL);
|
||||
name = VG_(describe_IP) (cur_ep, addr, NULL);
|
||||
if (buf[w]) VG_(free)(buf[w]);
|
||||
buf[w] = VG_(strdup)("gdbserver sym", name);
|
||||
} else {
|
||||
const HChar *name;
|
||||
VG_(get_datasym_and_offset) (addr, &name, &offset);
|
||||
VG_(get_datasym_and_offset) (cur_ep, addr, &name, &offset);
|
||||
if (buf[w]) VG_(free)(buf[w]);
|
||||
buf[w] = VG_(strdup)("gdbserver sym", name);
|
||||
}
|
||||
|
||||
@ -195,6 +195,8 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
|
||||
int kwdid;
|
||||
int int_value;
|
||||
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
vg_assert (initial_valgrind_sink_saved);
|
||||
|
||||
strcpy (s, mon);
|
||||
@ -334,7 +336,7 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
|
||||
}
|
||||
if (hostvisibility) {
|
||||
const DebugInfo *tooldi
|
||||
= VG_(find_DebugInfo) ((Addr)handle_gdb_valgrind_command);
|
||||
= VG_(find_DebugInfo) (cur_ep, (Addr)handle_gdb_valgrind_command);
|
||||
/* Normally, we should always find the tooldi. In case we
|
||||
do not, suggest a 'likely somewhat working' address: */
|
||||
const Addr tool_text_start
|
||||
@ -442,14 +444,14 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
|
||||
&dummy_sz, &ssaveptr)) {
|
||||
// If tool provides location information, use that.
|
||||
if (VG_(needs).info_location) {
|
||||
VG_TDICT_CALL(tool_info_location, address);
|
||||
VG_TDICT_CALL(tool_info_location, cur_ep, address);
|
||||
}
|
||||
// If tool does not provide location info, use the common one.
|
||||
// Also use the common to compare with tool when debug log is set.
|
||||
if (!VG_(needs).info_location || VG_(debugLog_getLevel)() > 0 ) {
|
||||
AddrInfo ai;
|
||||
ai.tag = Addr_Undescribed;
|
||||
VG_(describe_addr) (address, &ai);
|
||||
VG_(describe_addr) (cur_ep, address, &ai);
|
||||
VG_(pp_addrinfo) (address, &ai);
|
||||
VG_(clear_addrinfo) (&ai);
|
||||
}
|
||||
|
||||
@ -209,7 +209,10 @@ void gdbserver_process_exit_encountered (unsigned char status, Int code)
|
||||
static
|
||||
const HChar* sym (Addr addr)
|
||||
{
|
||||
return VG_(describe_IP) (addr, NULL);
|
||||
// Tracing/debugging so cur_ep is reasonable.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
return VG_(describe_IP) (cur_ep, addr, NULL);
|
||||
}
|
||||
|
||||
ThreadId vgdb_interrupted_tid = 0;
|
||||
|
||||
@ -149,8 +149,13 @@ Addr thumb_pc (Addr pc)
|
||||
// the debug info with the bit0 set
|
||||
// (why can't debug info do that for us ???)
|
||||
// (why if this is a 4 bytes thumb instruction ???)
|
||||
if (VG_(get_fnname_raw) (pc | 1, &fnname)) {
|
||||
if (VG_(lookup_symbol_SLOW)( "*", fnname, &avmas )) {
|
||||
|
||||
// Used to check if the instruction is a thumb instruction,
|
||||
// typically for a live address, so cur_ep is a reasonable choice.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
if (VG_(get_fnname_raw) (cur_ep, pc | 1, &fnname)) {
|
||||
if (VG_(lookup_symbol_SLOW)( cur_ep, "*", fnname, &avmas )) {
|
||||
dlog (1, "fnname %s lookupsym %p => %p %s.\n",
|
||||
fnname, C2v(avmas.main), C2v(pc),
|
||||
(avmas.main & 1 ? "thumb" : "arm"));
|
||||
|
||||
@ -228,7 +228,11 @@ static Addr mips_adjust_breakpoint_address (Addr pc)
|
||||
/* Make sure we don't scan back before the beginning of the current
|
||||
function, since we may fetch constant data or insns that look like
|
||||
a jump. */
|
||||
if (VG_(get_inst_offset_in_function) (bpaddr, &offset)) {
|
||||
|
||||
// Placing a breakpoint, so pc should be in di of current epoch.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
if (VG_(get_inst_offset_in_function) (cur_ep, bpaddr, &offset)) {
|
||||
func_addr = bpaddr - offset;
|
||||
if (func_addr > boundary && func_addr <= bpaddr)
|
||||
boundary = func_addr;
|
||||
|
||||
@ -229,7 +229,11 @@ static Addr mips_adjust_breakpoint_address (Addr pc)
|
||||
/* Make sure we don't scan back before the beginning of the current
|
||||
function, since we may fetch constant data or insns that look like
|
||||
a jump. */
|
||||
if (VG_(get_inst_offset_in_function) (bpaddr, &offset)) {
|
||||
|
||||
// Placing a breakpoint, so pc should be in di of current epoch.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
if (VG_(get_inst_offset_in_function) (cur_ep, bpaddr, &offset)) {
|
||||
func_addr = bpaddr - offset;
|
||||
if (func_addr > boundary && func_addr <= bpaddr)
|
||||
boundary = func_addr;
|
||||
|
||||
@ -369,7 +369,7 @@ static void show_sched_status_wrk ( Bool host_stacktrace,
|
||||
);
|
||||
VG_(printf)("\nhost stacktrace:\n");
|
||||
VG_(clo_xml) = False;
|
||||
VG_(pp_StackTrace) (ips, n_ips);
|
||||
VG_(pp_StackTrace) (VG_(current_DiEpoch)(), ips, n_ips);
|
||||
VG_(clo_xml) = save_clo_xml;
|
||||
}
|
||||
|
||||
|
||||
@ -129,6 +129,10 @@ static void usage_NORETURN ( Bool debug_help )
|
||||
" --error-exitcode=<number> exit code to return if errors found [0=disable]\n"
|
||||
" --error-markers=<begin>,<end> add lines with begin/end markers before/after\n"
|
||||
" each error output in plain text mode [none]\n"
|
||||
" --keep-debuginfo=no|yes Keep symbols etc for unloaded code [no]\n"
|
||||
" This allows saved stack traces (e.g. memory leaks)\n"
|
||||
" to include file/line info for code that has been\n"
|
||||
" dlclose'd (or similar)\n"
|
||||
" --show-below-main=no|yes continue stack traces below main() [no]\n"
|
||||
" --default-suppressions=yes|no\n"
|
||||
" load default suppressions [yes]\n"
|
||||
@ -634,6 +638,7 @@ void main_process_cmd_line_options( void )
|
||||
else if VG_BOOL_CLO(arg, "--run-libc-freeres", VG_(clo_run_libc_freeres)) {}
|
||||
else if VG_BOOL_CLO(arg, "--run-cxx-freeres", VG_(clo_run_cxx_freeres)) {}
|
||||
else if VG_BOOL_CLO(arg, "--show-below-main", VG_(clo_show_below_main)) {}
|
||||
else if VG_BOOL_CLO(arg, "--keep-debuginfo", VG_(clo_keep_debuginfo)) {}
|
||||
else if VG_BOOL_CLO(arg, "--time-stamp", VG_(clo_time_stamp)) {}
|
||||
else if VG_BOOL_CLO(arg, "--track-fds", VG_(clo_track_fds)) {}
|
||||
else if VG_BOOL_CLO(arg, "--trace-children", VG_(clo_trace_children)) {}
|
||||
@ -2266,7 +2271,8 @@ static void final_tidyup(ThreadId tid)
|
||||
}
|
||||
|
||||
# if defined(VGP_ppc64be_linux)
|
||||
Addr r2 = VG_(get_tocptr)(freeres_wrapper);
|
||||
Addr r2 = VG_(get_tocptr)(VG_(current_DiEpoch)(),
|
||||
freeres_wrapper);
|
||||
if (r2 == 0) {
|
||||
VG_(message)(Vg_UserMsg,
|
||||
"Caught __NR_exit, but can't run __gnu_cxx::__freeres()\n");
|
||||
|
||||
@ -130,6 +130,7 @@ Bool VG_(clo_run_libc_freeres) = True;
|
||||
Bool VG_(clo_run_cxx_freeres) = True;
|
||||
Bool VG_(clo_track_fds) = False;
|
||||
Bool VG_(clo_show_below_main)= False;
|
||||
Bool VG_(clo_keep_debuginfo) = False;
|
||||
Bool VG_(clo_show_emwarns) = False;
|
||||
Word VG_(clo_max_stackframe) = 2000000;
|
||||
UInt VG_(clo_max_threads) = MAX_THREADS_DEFAULT;
|
||||
|
||||
@ -1870,15 +1870,16 @@ static void show_active ( const HChar* left, const Active* act )
|
||||
{
|
||||
Bool ok;
|
||||
const HChar *buf;
|
||||
|
||||
ok = VG_(get_fnname_w_offset)(act->from_addr, &buf);
|
||||
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
ok = VG_(get_fnname_w_offset)(ep, act->from_addr, &buf);
|
||||
if (!ok) buf = "???";
|
||||
// Stash away name1
|
||||
HChar name1[VG_(strlen)(buf) + 1];
|
||||
VG_(strcpy)(name1, buf);
|
||||
|
||||
const HChar *name2;
|
||||
ok = VG_(get_fnname_w_offset)(act->to_addr, &name2);
|
||||
ok = VG_(get_fnname_w_offset)(ep, act->to_addr, &name2);
|
||||
if (!ok) name2 = "???";
|
||||
|
||||
VG_(message)(Vg_DebugMsg, "%s0x%08lx (%-20s) %s-> (%04d.%d) 0x%08lx %s\n",
|
||||
|
||||
@ -74,6 +74,9 @@ void show_SB_profile ( const SBProfEntry tops[], UInt n_tops,
|
||||
|
||||
VG_(printf)("Total score = %'llu\n\n", score_total);
|
||||
|
||||
// FIXME JRS EPOCH 28 July 2017: this is probably not right in general
|
||||
DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
/* Print an initial per-block summary. */
|
||||
VG_(printf)("rank ---cumulative--- -----self-----\n");
|
||||
score_cumul = 0;
|
||||
@ -84,7 +87,7 @@ void show_SB_profile ( const SBProfEntry tops[], UInt n_tops,
|
||||
continue;
|
||||
|
||||
const HChar *name;
|
||||
VG_(get_fnname_w_offset)(tops[r].addr, &name);
|
||||
VG_(get_fnname_w_offset)(cur_ep, tops[r].addr, &name);
|
||||
|
||||
score_here = tops[r].score;
|
||||
score_cumul += score_here;
|
||||
@ -123,7 +126,7 @@ void show_SB_profile ( const SBProfEntry tops[], UInt n_tops,
|
||||
continue;
|
||||
|
||||
const HChar *name;
|
||||
VG_(get_fnname_w_offset)(tops[r].addr, &name);
|
||||
VG_(get_fnname_w_offset)(cur_ep, tops[r].addr, &name);
|
||||
|
||||
score_here = tops[r].score;
|
||||
score_cumul += score_here;
|
||||
@ -159,7 +162,7 @@ void show_SB_profile ( const SBProfEntry tops[], UInt n_tops,
|
||||
continue;
|
||||
|
||||
const HChar *name;
|
||||
VG_(get_fnname_w_offset)(tops[r].addr, &name);
|
||||
VG_(get_fnname_w_offset)(cur_ep, tops[r].addr, &name);
|
||||
|
||||
score_here = tops[r].score;
|
||||
|
||||
|
||||
@ -2037,8 +2037,14 @@ void do_client_request ( ThreadId tid )
|
||||
|
||||
VG_(memset)(buf64, 0, 64);
|
||||
UInt linenum = 0;
|
||||
|
||||
// Unless the guest would become epoch aware (and would need to
|
||||
// describe IP addresses of dlclosed libs), using cur_ep is a
|
||||
// reasonable choice.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
Bool ok = VG_(get_filename_linenum)(
|
||||
ip, &buf, NULL, &linenum
|
||||
cur_ep, ip, &buf, NULL, &linenum
|
||||
);
|
||||
if (ok) {
|
||||
/* For backward compatibility truncate the filename to
|
||||
|
||||
@ -446,6 +446,7 @@ UInt VG_(get_StackTrace_wrk) ( ThreadId tid_if_known,
|
||||
/* And, similarly, try for MSVC FPO unwind info. */
|
||||
if (FPO_info_present
|
||||
&& VG_(use_FPO_info)( &uregs.xip, &uregs.xsp, &uregs.xbp,
|
||||
VG_(current_DiEpoch)(),
|
||||
fp_min, fp_max ) ) {
|
||||
if (debug) unwind_case = "MS";
|
||||
if (do_stats) stats.MS++;
|
||||
@ -730,6 +731,7 @@ UInt VG_(get_StackTrace_wrk) ( ThreadId tid_if_known,
|
||||
Word redirs_used = 0;
|
||||
# endif
|
||||
const Int cmrf = VG_(clo_merge_recursive_frames);
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
Bool debug = False;
|
||||
Int i;
|
||||
@ -813,10 +815,10 @@ UInt VG_(get_StackTrace_wrk) ( ThreadId tid_if_known,
|
||||
const HChar *buf_lr, *buf_ip;
|
||||
/* The following conditional looks grossly inefficient and
|
||||
surely could be majorly improved, with not much effort. */
|
||||
if (VG_(get_fnname_raw) (lr, &buf_lr)) {
|
||||
if (VG_(get_fnname_raw) (cur_ep, lr, &buf_lr)) {
|
||||
HChar buf_lr_copy[VG_(strlen)(buf_lr) + 1];
|
||||
VG_(strcpy)(buf_lr_copy, buf_lr);
|
||||
if (VG_(get_fnname_raw) (ip, &buf_ip))
|
||||
if (VG_(get_fnname_raw) (cur_ep, ip, &buf_ip))
|
||||
if (VG_(strcmp)(buf_lr_copy, buf_ip))
|
||||
lr_is_first_RA = True;
|
||||
}
|
||||
@ -1558,12 +1560,12 @@ UInt VG_(get_StackTrace) ( ThreadId tid,
|
||||
);
|
||||
}
|
||||
|
||||
static void printIpDesc(UInt n, Addr ip, void* uu_opaque)
|
||||
static void printIpDesc(UInt n, DiEpoch ep, Addr ip, void* uu_opaque)
|
||||
{
|
||||
InlIPCursor *iipc = VG_(new_IIPC)(ip);
|
||||
InlIPCursor *iipc = VG_(new_IIPC)(ep, ip);
|
||||
|
||||
do {
|
||||
const HChar *buf = VG_(describe_IP)(ip, iipc);
|
||||
const HChar *buf = VG_(describe_IP)(ep, ip, iipc);
|
||||
if (VG_(clo_xml)) {
|
||||
VG_(printf_xml)(" %s\n", buf);
|
||||
} else {
|
||||
@ -1577,14 +1579,14 @@ static void printIpDesc(UInt n, Addr ip, void* uu_opaque)
|
||||
}
|
||||
|
||||
/* Print a StackTrace. */
|
||||
void VG_(pp_StackTrace) ( StackTrace ips, UInt n_ips )
|
||||
void VG_(pp_StackTrace) ( DiEpoch ep, StackTrace ips, UInt n_ips )
|
||||
{
|
||||
vg_assert( n_ips > 0 );
|
||||
|
||||
if (VG_(clo_xml))
|
||||
VG_(printf_xml)(" <stack>\n");
|
||||
|
||||
VG_(apply_StackTrace)( printIpDesc, NULL, ips, n_ips );
|
||||
VG_(apply_StackTrace)( printIpDesc, NULL, ep, ips, n_ips );
|
||||
|
||||
if (VG_(clo_xml))
|
||||
VG_(printf_xml)(" </stack>\n");
|
||||
@ -1599,13 +1601,13 @@ void VG_(get_and_pp_StackTrace) ( ThreadId tid, UInt max_n_ips )
|
||||
NULL/*array to dump SP values in*/,
|
||||
NULL/*array to dump FP values in*/,
|
||||
0/*first_ip_delta*/);
|
||||
VG_(pp_StackTrace)(ips, n_ips);
|
||||
VG_(pp_StackTrace)(VG_(current_DiEpoch)(), ips, n_ips);
|
||||
}
|
||||
|
||||
void VG_(apply_StackTrace)(
|
||||
void(*action)(UInt n, Addr ip, void* opaque),
|
||||
void(*action)(UInt n, DiEpoch ep, Addr ip, void* opaque),
|
||||
void* opaque,
|
||||
StackTrace ips, UInt n_ips
|
||||
DiEpoch ep, StackTrace ips, UInt n_ips
|
||||
)
|
||||
{
|
||||
Int i;
|
||||
@ -1616,7 +1618,7 @@ void VG_(apply_StackTrace)(
|
||||
// or the last appearance of a below main function.
|
||||
// Then decrease n_ips so as to not call action for the below main
|
||||
for (i = n_ips - 1; i >= 0; i--) {
|
||||
Vg_FnNameKind kind = VG_(get_fnname_kind_from_IP)(ips[i]);
|
||||
Vg_FnNameKind kind = VG_(get_fnname_kind_from_IP)(ep, ips[i]);
|
||||
if (Vg_FnNameMain == kind || Vg_FnNameBelowMain == kind)
|
||||
n_ips = i + 1;
|
||||
if (Vg_FnNameMain == kind)
|
||||
@ -1626,7 +1628,7 @@ void VG_(apply_StackTrace)(
|
||||
|
||||
for (i = 0; i < n_ips; i++)
|
||||
// Act on the ip
|
||||
action(i, ips[i], opaque);
|
||||
action(i, ep, ips[i], opaque);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -328,7 +328,7 @@ void VG_(needs_print_stats) (
|
||||
}
|
||||
|
||||
void VG_(needs_info_location) (
|
||||
void (*info_location)(Addr)
|
||||
void (*info_location)(DiEpoch, Addr)
|
||||
)
|
||||
{
|
||||
VG_(needs).info_location = True;
|
||||
|
||||
@ -1363,7 +1363,8 @@ Bool mk_preamble__set_NRADDR_to_zero ( void* closureV, IRSB* bb )
|
||||
VG_WORDSIZE==8 ? mkU64(0) : mkU32(0)
|
||||
)
|
||||
);
|
||||
gen_push_and_set_LR_R2 ( bb, VG_(get_tocptr)( closure->readdr ) );
|
||||
gen_push_and_set_LR_R2 ( bb, VG_(get_tocptr)( VG_(current_DiEpoch)(),
|
||||
closure->readdr ) );
|
||||
}
|
||||
# endif
|
||||
|
||||
@ -1421,7 +1422,8 @@ Bool mk_preamble__set_NRADDR_to_nraddr ( void* closureV, IRSB* bb )
|
||||
VG_WORDSIZE==8 ? Ity_I64 : Ity_I32)
|
||||
)
|
||||
);
|
||||
gen_push_and_set_LR_R2 ( bb, VG_(get_tocptr)( closure->readdr ) );
|
||||
gen_push_and_set_LR_R2 ( bb, VG_(get_tocptr)( VG_(current_DiEpoch)(),
|
||||
closure->readdr ) );
|
||||
# endif
|
||||
#if defined(VGP_ppc64le_linux)
|
||||
/* This saves the r2 before leaving the function. We need to move
|
||||
@ -1538,24 +1540,25 @@ Bool VG_(translate) ( ThreadId tid,
|
||||
Bool ok;
|
||||
const HChar *buf;
|
||||
const HChar *name2;
|
||||
const DiEpoch ep = VG_(current_DiEpoch)();
|
||||
|
||||
/* Try also to get the soname (not the filename) of the "from"
|
||||
object. This makes it much easier to debug redirection
|
||||
problems. */
|
||||
const HChar* nraddr_soname = "???";
|
||||
DebugInfo* nraddr_di = VG_(find_DebugInfo)(nraddr);
|
||||
DebugInfo* nraddr_di = VG_(find_DebugInfo)(ep, nraddr);
|
||||
if (nraddr_di) {
|
||||
const HChar* t = VG_(DebugInfo_get_soname)(nraddr_di);
|
||||
if (t)
|
||||
nraddr_soname = t;
|
||||
}
|
||||
|
||||
ok = VG_(get_fnname_w_offset)(nraddr, &buf);
|
||||
ok = VG_(get_fnname_w_offset)(ep, nraddr, &buf);
|
||||
if (!ok) buf = "???";
|
||||
// Stash away name1
|
||||
HChar name1[VG_(strlen)(buf) + 1];
|
||||
VG_(strcpy)(name1, buf);
|
||||
ok = VG_(get_fnname_w_offset)(addr, &name2);
|
||||
ok = VG_(get_fnname_w_offset)(ep, addr, &name2);
|
||||
if (!ok) name2 = "???";
|
||||
|
||||
VG_(message)(Vg_DebugMsg,
|
||||
@ -1572,7 +1575,8 @@ Bool VG_(translate) ( ThreadId tid,
|
||||
if (VG_(clo_trace_flags) || debugging_translation) {
|
||||
const HChar* objname = "UNKNOWN_OBJECT";
|
||||
OffT objoff = 0;
|
||||
DebugInfo* di = VG_(find_DebugInfo)( addr );
|
||||
const DiEpoch ep = VG_(current_DiEpoch)();
|
||||
DebugInfo* di = VG_(find_DebugInfo)( ep, addr );
|
||||
if (di) {
|
||||
objname = VG_(DebugInfo_get_filename)(di);
|
||||
objoff = addr - VG_(DebugInfo_get_text_bias)(di);
|
||||
@ -1580,7 +1584,7 @@ Bool VG_(translate) ( ThreadId tid,
|
||||
vg_assert(objname);
|
||||
|
||||
const HChar *fnname;
|
||||
Bool ok = VG_(get_fnname_w_offset)(addr, &fnname);
|
||||
Bool ok = VG_(get_fnname_w_offset)(ep, addr, &fnname);
|
||||
if (!ok) fnname = "UNKNOWN_FUNCTION";
|
||||
VG_(printf)(
|
||||
"==== SB %u (evchecks %llu) [tid %u] 0x%lx %s %s%c0x%lx\n",
|
||||
|
||||
@ -501,7 +501,7 @@ void VG_(XT_callgrind_print)
|
||||
// the strings called_filename/called_fnname.
|
||||
#define CALLED_FLF(n) \
|
||||
if ((n) < 0 \
|
||||
|| !VG_(get_filename_linenum)(ips[(n)], \
|
||||
|| !VG_(get_filename_linenum)(ep, ips[(n)], \
|
||||
&filename_name, \
|
||||
&filename_dir, \
|
||||
&called_linenum)) { \
|
||||
@ -509,7 +509,7 @@ void VG_(XT_callgrind_print)
|
||||
called_linenum = 0; \
|
||||
} \
|
||||
if ((n) < 0 \
|
||||
|| !VG_(get_fnname)(ips[(n)], &called_fnname)) { \
|
||||
|| !VG_(get_fnname)(ep, ips[(n)], &called_fnname)) { \
|
||||
called_fnname = "UnknownFn???"; \
|
||||
} \
|
||||
{ \
|
||||
@ -550,6 +550,8 @@ void VG_(XT_callgrind_print)
|
||||
UInt prev_linenum;
|
||||
|
||||
const Addr* ips = VG_(get_ExeContext_StackTrace)(xe->ec) + xe->top;
|
||||
const DiEpoch ep = VG_(get_ExeContext_epoch)(xe->ec);
|
||||
|
||||
Int ips_idx = xe->n_ips_sel - 1;
|
||||
|
||||
if (0) {
|
||||
@ -762,11 +764,24 @@ static void ms_output_group (VgFile* fp, UInt depth, Ms_Group* group,
|
||||
ms_make_groups(depth+1, group->ms_ec, group->n_ec, sig_sz,
|
||||
&n_groups, &groups);
|
||||
|
||||
// FIXME JRS EPOCH 28 July 2017: HACK! Is this correct?
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
// // FIXME PW EPOCH : No, the above is not correct.
|
||||
// Xtree Massif output regroups execontext in the layout of a 'tree'.
|
||||
// So, possibly, the same IP address value can be in 2 different ec, but
|
||||
// the epoch to symbolise this address must be retrieved from the ec it
|
||||
// originates from.
|
||||
// So, to fix this, it is not enough to make a group based on identical
|
||||
// IP addr value, one must also find the di used to symbolise this address,
|
||||
// A group will then be defined as 'same IP and same di'.
|
||||
// Fix not trivial to do, so for the moment, --keep-debuginfo=yes will
|
||||
// have no impact on xtree massif output.
|
||||
|
||||
FP("%*s" "n%u: %ld %s\n",
|
||||
depth + 1, "",
|
||||
n_groups,
|
||||
group->total,
|
||||
VG_(describe_IP)(group->ms_ec->ips[depth] - 1, NULL));
|
||||
VG_(describe_IP)(cur_ep, group->ms_ec->ips[depth] - 1, NULL));
|
||||
/* XTREE??? Massif original code removes 1 to get the IP description. I am
|
||||
wondering if this is not something that predates revision r8818,
|
||||
which introduced a -1 in the stack unwind (see m_stacktrace.c)
|
||||
@ -955,7 +970,7 @@ void VG_(XT_massif_print)
|
||||
}
|
||||
}
|
||||
|
||||
Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips)
|
||||
Int VG_(XT_offset_main_or_below_main)(DiEpoch ep, Addr* ips, Int n_ips)
|
||||
{
|
||||
/* Search for main or below main function.
|
||||
To limit the nr of ips to examine, we maintain the deepest
|
||||
@ -972,7 +987,7 @@ Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips)
|
||||
for (i = n_ips - 1 - deepest_main;
|
||||
i < n_ips;
|
||||
i++) {
|
||||
mbmkind = VG_(get_fnname_kind_from_IP)(ips[i]);
|
||||
mbmkind = VG_(get_fnname_kind_from_IP)(ep, ips[i]);
|
||||
if (mbmkind != Vg_FnNameNormal) {
|
||||
mbm = i;
|
||||
break;
|
||||
@ -983,7 +998,7 @@ Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips)
|
||||
for (i = mbm - 1;
|
||||
i >= 0 && mbmkind != Vg_FnNameMain;
|
||||
i--) {
|
||||
kind = VG_(get_fnname_kind_from_IP)(ips[i]);
|
||||
kind = VG_(get_fnname_kind_from_IP)(ep, ips[i]);
|
||||
if (kind != Vg_FnNameNormal) {
|
||||
mbm = i;
|
||||
mbmkind = kind;
|
||||
@ -1014,8 +1029,11 @@ void VG_(XT_filter_1top_and_maybe_below_main)
|
||||
|
||||
if (VG_(clo_show_below_main))
|
||||
mbm = n_ips - 1;
|
||||
else
|
||||
mbm = VG_(XT_offset_main_or_below_main)(ips, n_ips);
|
||||
else {
|
||||
// FIXME PW EPOCH : use the real ips epoch
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
mbm = VG_(XT_offset_main_or_below_main)(cur_ep, ips, n_ips);
|
||||
}
|
||||
|
||||
*n_ips_sel = mbm - *top + 1;
|
||||
}
|
||||
@ -1033,9 +1051,11 @@ void VG_(XT_filter_maybe_below_main)
|
||||
|
||||
if (VG_(clo_show_below_main))
|
||||
mbm = n_ips - 1;
|
||||
else
|
||||
mbm = VG_(XT_offset_main_or_below_main)(ips, n_ips);
|
||||
|
||||
else {
|
||||
// FIXME PW EPOCH : use the real ips epoch
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
mbm = VG_(XT_offset_main_or_below_main)(cur_ep, ips, n_ips);
|
||||
}
|
||||
*n_ips_sel = mbm - *top + 1;
|
||||
}
|
||||
|
||||
|
||||
@ -86,20 +86,21 @@ extern void VG_(di_discard_ALL_debuginfo)( void );
|
||||
* It should only be used in cases where the names of interest will have
|
||||
* particular (ie. non-mangled) forms, or the mangled form is acceptable. */
|
||||
extern
|
||||
Bool VG_(get_fnname_raw) ( Addr a, const HChar** buf );
|
||||
Bool VG_(get_fnname_raw) ( DiEpoch ep, Addr a, const HChar** buf );
|
||||
|
||||
/* Like VG_(get_fnname), but without C++ demangling. (But it does
|
||||
Z-demangling and below-main renaming.)
|
||||
iipc argument: same usage as in VG_(describe_IP) in pub_tool_debuginfo.h. */
|
||||
extern
|
||||
Bool VG_(get_fnname_no_cxx_demangle) ( Addr a, const HChar** buf,
|
||||
Bool VG_(get_fnname_no_cxx_demangle) ( DiEpoch ep, Addr a, const HChar** buf,
|
||||
const InlIPCursor* iipc );
|
||||
|
||||
/* mips-linux only: find the offset of current address. This is needed for
|
||||
stack unwinding for MIPS.
|
||||
*/
|
||||
extern
|
||||
Bool VG_(get_inst_offset_in_function)( Addr a, /*OUT*/PtrdiffT* offset );
|
||||
Bool VG_(get_inst_offset_in_function)( DiEpoch ep, Addr a,
|
||||
/*OUT*/PtrdiffT* offset );
|
||||
|
||||
|
||||
/* Use DWARF2/3 CFA information to do one step of stack unwinding.
|
||||
@ -158,6 +159,7 @@ extern Bool VG_(FPO_info_present)(void);
|
||||
extern Bool VG_(use_FPO_info) ( /*MOD*/Addr* ipP,
|
||||
/*MOD*/Addr* spP,
|
||||
/*MOD*/Addr* fpP,
|
||||
DiEpoch ep,
|
||||
Addr min_accessible,
|
||||
Addr max_accessible );
|
||||
|
||||
@ -217,7 +219,7 @@ void VG_(DebugInfo_syms_getidx) ( const DebugInfo *di,
|
||||
/* ppc64-linux only: find the TOC pointer (R2 value) that should be in
|
||||
force at the entry point address of the function containing
|
||||
guest_code_addr. Returns 0 if not known. */
|
||||
extern Addr VG_(get_tocptr) ( Addr guest_code_addr );
|
||||
extern Addr VG_(get_tocptr) ( DiEpoch ep, Addr guest_code_addr );
|
||||
|
||||
/* Map a function name to its SymAVMAs. Is done by
|
||||
sequential search of all symbol tables, so is very slow. To
|
||||
@ -227,7 +229,8 @@ extern Addr VG_(get_tocptr) ( Addr guest_code_addr );
|
||||
platforms, a symbol is deemed to be found only if it has a nonzero
|
||||
TOC pointer. */
|
||||
extern
|
||||
Bool VG_(lookup_symbol_SLOW)(const HChar* sopatt, const HChar* name,
|
||||
Bool VG_(lookup_symbol_SLOW)(DiEpoch ep,
|
||||
const HChar* sopatt, const HChar* name,
|
||||
SymAVMAs* avmas);
|
||||
|
||||
#endif // __PUB_CORE_DEBUGINFO_H
|
||||
|
||||
@ -47,6 +47,12 @@
|
||||
// If with_stacktraces, outputs all the recorded stacktraces.
|
||||
extern void VG_(print_ExeContext_stats) ( Bool with_stacktraces );
|
||||
|
||||
// All ExeContext that are valid in the current epoch and have one or more
|
||||
// ips in the given range are archived, i.e. their epoch is frozen to
|
||||
// the given last_epoch.
|
||||
extern void VG_(archive_ExeContext_in_range) (DiEpoch last_epoch,
|
||||
Addr text_avma, SizeT length );
|
||||
|
||||
// Extract the StackTrace from an ExeContext.
|
||||
// (Minor hack: we use Addr* as the return type instead of StackTrace so
|
||||
// that modules #including this file don't also have to #include
|
||||
|
||||
@ -156,7 +156,7 @@ typedef struct {
|
||||
void (*tool_print_stats)(void);
|
||||
|
||||
// VG_(needs).info_location
|
||||
void (*tool_info_location)(Addr a);
|
||||
void (*tool_info_location)(DiEpoch ep, Addr a);
|
||||
|
||||
// VG_(needs).malloc_replacement
|
||||
void* (*tool_malloc) (ThreadId, SizeT);
|
||||
|
||||
@ -1232,6 +1232,28 @@ that can report errors, e.g. Memcheck, but not Cachegrind.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="opt.keep-debuginfo" xreflabel="--keep-debuginfo">
|
||||
<term>
|
||||
<option><![CDATA[--keep-debuginfo=<yes|no> [default: no] ]]></option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>When enabled, keep ("archive") symbols and all other debuginfo
|
||||
for unloaded code. This allows saved stack traces to include file/line
|
||||
info for code that has been dlclose'd (or similar). Be careful with
|
||||
this, since it can lead to unbounded memory use for programs which
|
||||
repeatedly load and unload shared objects.</para>
|
||||
<para>Some tools and some functionalities have only limited support
|
||||
for archived debug info. Memcheck fully supports it. Generally,
|
||||
tools that report errors can use archived debug info to show the error
|
||||
stack traces. The known limitations are: Helgrind's past access stack
|
||||
trace of a race condition is does not use archived debug info. Massif
|
||||
(and more generally the xtree Massif output format) does not make use
|
||||
of archived debug info. Only Memcheck has been (somewhat) tested
|
||||
with <option>--keep-debuginfo=yes</option>, so other tools may have
|
||||
unknown limitations. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="opt.show-below-main" xreflabel="--show-below-main">
|
||||
<term>
|
||||
<option><![CDATA[--show-below-main=<yes|no> [default: no] ]]></option>
|
||||
|
||||
@ -161,6 +161,7 @@ void drd_report_data_race(const Error* const err,
|
||||
const HChar* const indent = xml ? " " : "";
|
||||
AddrInfo ai;
|
||||
|
||||
DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
XArray* /* of HChar */ descr1
|
||||
= VG_(newXA)( VG_(malloc), "drd.error.drdr2.1",
|
||||
VG_(free), sizeof(HChar) );
|
||||
@ -172,7 +173,7 @@ void drd_report_data_race(const Error* const err,
|
||||
tl_assert(dri->addr);
|
||||
tl_assert(dri->size > 0);
|
||||
|
||||
(void) VG_(get_data_description)(descr1, descr2, dri->addr);
|
||||
(void) VG_(get_data_description)(descr1, descr2, cur_ep, dri->addr);
|
||||
/* If there's nothing in descr1/2, free them. Why is it safe to
|
||||
VG_(indexXA) at zero here? Because VG_(get_data_description)
|
||||
guarantees to zero terminate descr1/2 regardless of the outcome
|
||||
|
||||
@ -346,6 +346,7 @@ static IRSB* bbv_instrument ( VgCallbackClosure* closure,
|
||||
IRDirty *di;
|
||||
IRExpr **argv, *arg1;
|
||||
Int regparms,opcode_type;
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
|
||||
/* We don't handle a host/guest word size mismatch */
|
||||
if (gWordTy != hWordTy) {
|
||||
@ -392,8 +393,8 @@ static IRSB* bbv_instrument ( VgCallbackClosure* closure,
|
||||
block_num++;
|
||||
/* get function name and entry point information */
|
||||
const HChar *fn_name;
|
||||
VG_(get_fnname)(origAddr, &fn_name);
|
||||
bbInfo->is_entry=VG_(get_fnname_if_entry)(origAddr, &fn_name);
|
||||
VG_(get_fnname)(ep, origAddr, &fn_name);
|
||||
bbInfo->is_entry=VG_(get_fnname_if_entry)(ep, origAddr, &fn_name);
|
||||
bbInfo->fn_name =VG_(strdup)("bbv_strings", fn_name);
|
||||
/* insert structure into table */
|
||||
VG_(OSetGen_Insert)( instr_info_table, bbInfo );
|
||||
|
||||
@ -650,6 +650,8 @@ void pc_pp_Error ( const Error* err )
|
||||
UInt pc_update_Error_extra ( const Error* err )
|
||||
{
|
||||
XError *xe = (XError*)VG_(get_error_extra)(err);
|
||||
const DiEpoch ep = VG_(get_ExeContext_epoch)(VG_(get_error_where)(err));
|
||||
|
||||
tl_assert(xe);
|
||||
switch (xe->tag) {
|
||||
case XE_SorG:
|
||||
@ -675,7 +677,7 @@ UInt pc_update_Error_extra ( const Error* err )
|
||||
have_descr
|
||||
= VG_(get_data_description)( xe->XE.Heap.descr1,
|
||||
xe->XE.Heap.descr2,
|
||||
xe->XE.Heap.addr );
|
||||
ep, xe->XE.Heap.addr );
|
||||
|
||||
/* If there's nothing in descr1/2, free it. Why is it safe to
|
||||
to VG_(indexXA) at zero here? Because
|
||||
@ -699,7 +701,7 @@ UInt pc_update_Error_extra ( const Error* err )
|
||||
if (!have_descr) {
|
||||
const HChar *name;
|
||||
if (VG_(get_datasym_and_offset)(
|
||||
xe->XE.Heap.addr, &name,
|
||||
ep, xe->XE.Heap.addr, &name,
|
||||
&xe->XE.Heap.datasymoff )
|
||||
) {
|
||||
xe->XE.Heap.datasym =
|
||||
|
||||
@ -1936,7 +1936,8 @@ void shadowStack_new_frame ( ThreadId tid,
|
||||
const HChar *fnname;
|
||||
Bool ok;
|
||||
Addr ip = ip_post_call_insn;
|
||||
ok = VG_(get_fnname_w_offset)( ip, &fnname );
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
ok = VG_(get_fnname_w_offset)( ep, ip, &fnname );
|
||||
while (d > 0) {
|
||||
VG_(printf)(" ");
|
||||
d--;
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
#include "hg_lock_n_thread.h"
|
||||
#include "hg_addrdescr.h" /* self */
|
||||
|
||||
void HG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
void HG_(describe_addr) ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai )
|
||||
{
|
||||
tl_assert(ai->tag == Addr_Undescribed);
|
||||
|
||||
@ -81,7 +81,7 @@ void HG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
ai->Addr.Block.freed_at = VG_(null_ExeContext)();;
|
||||
} else {
|
||||
/* No block found. Search a non-heap block description. */
|
||||
VG_(describe_addr) (a, ai);
|
||||
VG_(describe_addr) (ep, a, ai);
|
||||
|
||||
/* In case ai contains a tid, set tnr to the corresponding helgrind
|
||||
thread number. */
|
||||
@ -100,14 +100,14 @@ void HG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
}
|
||||
}
|
||||
|
||||
Bool HG_(get_and_pp_addrdescr) (Addr addr)
|
||||
Bool HG_(get_and_pp_addrdescr) (DiEpoch ep, Addr addr)
|
||||
{
|
||||
|
||||
Bool ret;
|
||||
AddrInfo glai;
|
||||
|
||||
glai.tag = Addr_Undescribed;
|
||||
HG_(describe_addr) (addr, &glai);
|
||||
HG_(describe_addr) (ep, addr, &glai);
|
||||
VG_(pp_addrinfo) (addr, &glai);
|
||||
ret = glai.tag != Addr_Unknown;
|
||||
|
||||
|
||||
@ -37,12 +37,12 @@
|
||||
lock description, putting the result in ai.
|
||||
This might allocate some memory in ai, to be cleared with
|
||||
VG_(clear_addrinfo). */
|
||||
extern void HG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai );
|
||||
extern void HG_(describe_addr) ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai );
|
||||
|
||||
/* Get a readable description of addr, then print it using HG_(pp_addrdescr)
|
||||
using xml False and VG_(printf) to emit the characters.
|
||||
Returns True if a description was found/printed, False otherwise. */
|
||||
extern Bool HG_(get_and_pp_addrdescr) (Addr a);
|
||||
extern Bool HG_(get_and_pp_addrdescr) (DiEpoch ep, Addr a);
|
||||
|
||||
/* For error creation/address description:
|
||||
map 'data_addr' to a malloc'd chunk, if any.
|
||||
|
||||
@ -421,7 +421,8 @@ UInt HG_(update_extra) ( const Error* err )
|
||||
VG_(printf)("HG_(update_extra): "
|
||||
"%d conflicting-event queries\n", xxx);
|
||||
|
||||
HG_(describe_addr) (xe->XE.Race.data_addr, &xe->XE.Race.data_addrinfo);
|
||||
HG_(describe_addr) (VG_(get_ExeContext_epoch)(VG_(get_error_where)(err)),
|
||||
xe->XE.Race.data_addr, &xe->XE.Race.data_addrinfo);
|
||||
|
||||
/* And poke around in the conflicting-event map, to see if we
|
||||
can rustle up a plausible-looking conflicting memory access
|
||||
@ -801,7 +802,11 @@ static void announce_LockP ( Lock* lk )
|
||||
VG_(umsg)( " Lock at %p : no stacktrace for first observation\n",
|
||||
(void*)lk->guestaddr );
|
||||
}
|
||||
HG_(get_and_pp_addrdescr) (lk->guestaddr);
|
||||
HG_(get_and_pp_addrdescr)
|
||||
(lk->appeared_at
|
||||
? VG_(get_ExeContext_epoch)(lk->appeared_at)
|
||||
: VG_(current_DiEpoch)(),
|
||||
lk->guestaddr);
|
||||
VG_(umsg)("\n");
|
||||
}
|
||||
}
|
||||
@ -1307,7 +1312,8 @@ void HG_(print_access) (StackTrace ips, UInt n_ips,
|
||||
show_LockP_summary_textmode( locksHeldW_P, "" );
|
||||
HG_(free) (locksHeldW_P);
|
||||
}
|
||||
VG_(pp_StackTrace) (ips, n_ips);
|
||||
// FIXME PW EPOCH : need the real ips epoch.
|
||||
VG_(pp_StackTrace)( VG_(current_DiEpoch)(), ips, n_ips );
|
||||
VG_(printf) ("\n");
|
||||
}
|
||||
|
||||
|
||||
@ -484,13 +484,15 @@ static void pp_Lock ( Int d, Lock* lk,
|
||||
Bool show_lock_addrdescr,
|
||||
Bool show_internal_data)
|
||||
{
|
||||
// FIXME PW EPOCH should use the epoch of the allocated_at ec.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
space(d+0);
|
||||
if (show_internal_data)
|
||||
VG_(printf)("Lock %p (ga %#lx) {\n", lk, lk->guestaddr);
|
||||
else
|
||||
VG_(printf)("Lock ga %#lx {\n", lk->guestaddr);
|
||||
if (!show_lock_addrdescr
|
||||
|| !HG_(get_and_pp_addrdescr) ((Addr) lk->guestaddr))
|
||||
|| !HG_(get_and_pp_addrdescr) (cur_ep, (Addr) lk->guestaddr))
|
||||
VG_(printf)("\n");
|
||||
|
||||
if (sHOW_ADMIN) {
|
||||
@ -4688,7 +4690,7 @@ static Bool is_in_dynamic_linker_shared_object( Addr ga )
|
||||
DebugInfo* dinfo;
|
||||
const HChar* soname;
|
||||
|
||||
dinfo = VG_(find_DebugInfo)( ga );
|
||||
dinfo = VG_(find_DebugInfo)( VG_(current_DiEpoch)(), ga );
|
||||
if (!dinfo) return False;
|
||||
|
||||
soname = VG_(DebugInfo_get_soname)(dinfo);
|
||||
@ -5985,9 +5987,9 @@ static void hg_post_clo_init ( void )
|
||||
VG_(XTMemory_Full_init)(VG_(XT_filter_1top_and_maybe_below_main));
|
||||
}
|
||||
|
||||
static void hg_info_location (Addr a)
|
||||
static void hg_info_location (DiEpoch ep, Addr a)
|
||||
{
|
||||
(void) HG_(get_and_pp_addrdescr) (a);
|
||||
(void) HG_(get_and_pp_addrdescr) (ep, a);
|
||||
}
|
||||
|
||||
static void hg_pre_clo_init ( void )
|
||||
|
||||
@ -4462,6 +4462,7 @@ static Bool check_cached_rcec_ok (Thr* thr, Addr previous_frame0)
|
||||
UWord frames[N_FRAMES];
|
||||
UWord sps[N_FRAMES];
|
||||
UWord fps[N_FRAMES];
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
|
||||
for (i = 0; i < N_FRAMES; i++)
|
||||
frames[i] = sps[i] = fps[i] = 0;
|
||||
@ -4510,7 +4511,7 @@ static Bool check_cached_rcec_ok (Thr* thr, Addr previous_frame0)
|
||||
Vg_FnNameKind fr_kind;
|
||||
for (fr_main = 0; fr_main < N_FRAMES; fr_main++) {
|
||||
fr_kind = VG_(get_fnname_kind_from_IP)
|
||||
(frames[fr_main]);
|
||||
(cur_ep, frames[fr_main]);
|
||||
if (fr_kind == Vg_FnNameMain || fr_kind == Vg_FnNameBelowMain)
|
||||
break;
|
||||
}
|
||||
@ -4518,7 +4519,7 @@ static Bool check_cached_rcec_ok (Thr* thr, Addr previous_frame0)
|
||||
Vg_FnNameKind kh_kind;
|
||||
for (kh_main = 0; kh_main < N_FRAMES; kh_main++) {
|
||||
kh_kind = VG_(get_fnname_kind_from_IP)
|
||||
(thr->cached_rcec.frames[kh_main]);
|
||||
(cur_ep, thr->cached_rcec.frames[kh_main]);
|
||||
if (kh_kind == Vg_FnNameMain || kh_kind == Vg_FnNameBelowMain)
|
||||
break;
|
||||
}
|
||||
@ -4587,7 +4588,7 @@ static Bool check_cached_rcec_ok (Thr* thr, Addr previous_frame0)
|
||||
if (reason == NULL) {
|
||||
const HChar *fnname;
|
||||
for (UInt f = 0; f < N_FRAMES; f++) {
|
||||
if (VG_(get_fnname)( frames[f], &fnname)
|
||||
if (VG_(get_fnname)( cur_ep, frames[f], &fnname)
|
||||
&& VG_(strcmp) ("__run_exit_handlers", fnname) == 0) {
|
||||
reason = "exit handlers";
|
||||
break;
|
||||
@ -4632,11 +4633,11 @@ static Bool check_cached_rcec_ok (Thr* thr, Addr previous_frame0)
|
||||
(void*)frames[i]);
|
||||
VG_(printf)("cached stack trace previous_frame0 %p\n",
|
||||
(void*)previous_frame0);
|
||||
VG_(pp_StackTrace)(&previous_frame0, 1);
|
||||
VG_(pp_StackTrace)(cur_ep, &previous_frame0, 1);
|
||||
VG_(printf)("resulting cached stack trace:\n");
|
||||
VG_(pp_StackTrace)(thr->cached_rcec.frames, N_FRAMES);
|
||||
VG_(pp_StackTrace)(cur_ep, thr->cached_rcec.frames, N_FRAMES);
|
||||
VG_(printf)("check stack trace:\n");
|
||||
VG_(pp_StackTrace)(frames, N_FRAMES);
|
||||
VG_(pp_StackTrace)(cur_ep, frames, N_FRAMES);
|
||||
|
||||
VG_(show_sched_status) (False, // host_stacktrace
|
||||
False, // stack_usage
|
||||
@ -4703,7 +4704,8 @@ static RCEC* get_RCEC ( Thr* thr )
|
||||
Bool save_show_below_main = VG_(clo_show_below_main);
|
||||
VG_(clo_show_below_main) = True;
|
||||
VG_(printf)("caching stack trace:\n");
|
||||
VG_(pp_StackTrace)(&thr->cached_rcec.frames[0], N_FRAMES);
|
||||
VG_(pp_StackTrace)(VG_(current_DiEpoch)(),
|
||||
&thr->cached_rcec.frames[0], N_FRAMES);
|
||||
VG_(clo_show_below_main) = save_show_below_main;
|
||||
}
|
||||
stats__cached_rcec_fresh++;
|
||||
|
||||
@ -126,7 +126,7 @@ struct _AddrInfo {
|
||||
|
||||
// On a stack. tinfo indicates which thread's stack?
|
||||
// IP is the address of an instruction of the function where the
|
||||
// stack address was. 0 if not found.
|
||||
// stack address was. 0 if not found. IP can be symbolised using epoch.
|
||||
// frameNo is the frame nr of the call where the stack address was.
|
||||
// -1 if not found.
|
||||
// stackPos describes the address 'position' in the stack.
|
||||
@ -135,6 +135,7 @@ struct _AddrInfo {
|
||||
// (spoffset will be negative, as stacks are assumed growing down).
|
||||
struct {
|
||||
ThreadInfo tinfo;
|
||||
DiEpoch epoch;
|
||||
Addr IP;
|
||||
Int frameNo;
|
||||
StackPos stackPos;
|
||||
@ -151,9 +152,9 @@ struct _AddrInfo {
|
||||
const HChar* block_desc; // "block","mempool","user-defined",arena
|
||||
SizeT block_szB;
|
||||
PtrdiffT rwoffset;
|
||||
ExeContext* allocated_at; // might be null_ExeContext.
|
||||
ThreadInfo alloc_tinfo; // which thread did alloc this block.
|
||||
ExeContext* freed_at; // might be null_ExeContext.
|
||||
ExeContext* allocated_at; // might contain null_ExeContext.
|
||||
ThreadInfo alloc_tinfo; // which thread alloc'd this block.
|
||||
ExeContext* freed_at; // might contain null_ExeContext.
|
||||
} Block;
|
||||
|
||||
// In a global .data symbol. This holds
|
||||
@ -204,7 +205,7 @@ struct _AddrInfo {
|
||||
On entry, ai->tag must be equal to Addr_Undescribed.
|
||||
This might allocate some memory, that can be cleared with
|
||||
VG_(clear_addrinfo). */
|
||||
extern void VG_(describe_addr) ( Addr a, /*OUT*/AddrInfo* ai );
|
||||
extern void VG_(describe_addr) ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai );
|
||||
|
||||
extern void VG_(clear_addrinfo) ( AddrInfo* ai);
|
||||
|
||||
|
||||
@ -129,6 +129,24 @@ typedef struct { UWord uw1; UWord uw2; } UWordPair;
|
||||
/* ThreadIds are simply indices into the VG_(threads)[] array. */
|
||||
typedef UInt ThreadId;
|
||||
|
||||
|
||||
/* You need a debuginfo epoch in order to convert an address into any source
|
||||
level entity, since that conversion depends on what objects were mapped
|
||||
in at the time. An epoch is simply a monotonically increasing counter,
|
||||
which we wrap up in a struct so as to enable the C type system to
|
||||
distinguish it from other kinds of numbers. m_debuginfo holds and
|
||||
maintains the current epoch number. */
|
||||
typedef struct { UInt n; } DiEpoch;
|
||||
|
||||
static inline DiEpoch DiEpoch_INVALID ( void ) {
|
||||
DiEpoch dep; dep.n = 0; return dep;
|
||||
}
|
||||
|
||||
static inline Bool is_DiEpoch_INVALID ( DiEpoch dep ) {
|
||||
return dep.n == 0;
|
||||
}
|
||||
|
||||
|
||||
/* Many data structures need to allocate and release memory.
|
||||
The allocation/release functions must be provided by the caller.
|
||||
The Alloc_Fn_t function must allocate a chunk of memory of size szB.
|
||||
|
||||
@ -31,11 +31,20 @@
|
||||
#ifndef __PUB_TOOL_DEBUGINFO_H
|
||||
#define __PUB_TOOL_DEBUGINFO_H
|
||||
|
||||
#include "pub_tool_basics.h" // VG_ macro
|
||||
#include "pub_tool_basics.h" // VG_ macro, DiEpoch
|
||||
#include "pub_tool_xarray.h" // XArray
|
||||
|
||||
|
||||
/*====================================================================*/
|
||||
/*=== Obtaining debug information ===*/
|
||||
/*=== Debuginfo epochs. ===*/
|
||||
/*====================================================================*/
|
||||
|
||||
// This returns the current epoch.
|
||||
DiEpoch VG_(current_DiEpoch)(void);
|
||||
|
||||
|
||||
/*====================================================================*/
|
||||
/*=== Obtaining information pertaining to source artefacts. ===*/
|
||||
/*====================================================================*/
|
||||
|
||||
/* IMPORTANT COMMENT about memory persistence and ownership.
|
||||
@ -76,11 +85,11 @@
|
||||
demangles C++ function names. VG_(get_fnname_w_offset) is the
|
||||
same, except it appends "+N" to symbol names to indicate offsets.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership. */
|
||||
extern Bool VG_(get_filename) ( Addr a, const HChar** filename );
|
||||
extern Bool VG_(get_fnname) ( Addr a, const HChar** fnname );
|
||||
extern Bool VG_(get_linenum) ( Addr a, UInt* linenum );
|
||||
extern Bool VG_(get_filename) ( DiEpoch ep, Addr a, const HChar** filename );
|
||||
extern Bool VG_(get_fnname) ( DiEpoch ep, Addr a, const HChar** fnname );
|
||||
extern Bool VG_(get_linenum) ( DiEpoch ep, Addr a, UInt* linenum );
|
||||
extern Bool VG_(get_fnname_w_offset)
|
||||
( Addr a, const HChar** fnname );
|
||||
( DiEpoch ep, Addr a, const HChar** fnname );
|
||||
|
||||
/* This one is the most general. It gives filename, line number and
|
||||
optionally directory name. filename and linenum may not be NULL.
|
||||
@ -95,7 +104,7 @@ extern Bool VG_(get_fnname_w_offset)
|
||||
Returned value indicates whether any filename/line info could be
|
||||
found. */
|
||||
extern Bool VG_(get_filename_linenum)
|
||||
( Addr a,
|
||||
( DiEpoch ep, Addr a,
|
||||
/*OUT*/const HChar** filename,
|
||||
/*OUT*/const HChar** dirname,
|
||||
/*OUT*/UInt* linenum );
|
||||
@ -108,7 +117,8 @@ extern Bool VG_(get_filename_linenum)
|
||||
of its symbols, this function will not be able to recognise function
|
||||
entry points within it.
|
||||
NOTE: See IMPORTANT COMMENT above about persistence and ownership. */
|
||||
extern Bool VG_(get_fnname_if_entry) ( Addr a, const HChar** fnname );
|
||||
extern Bool VG_(get_fnname_if_entry) ( DiEpoch ep, Addr a,
|
||||
const HChar** fnname );
|
||||
|
||||
typedef
|
||||
enum {
|
||||
@ -121,13 +131,13 @@ typedef
|
||||
extern Vg_FnNameKind VG_(get_fnname_kind) ( const HChar* name );
|
||||
|
||||
/* Like VG_(get_fnname_kind), but takes a code address. */
|
||||
extern Vg_FnNameKind VG_(get_fnname_kind_from_IP) ( Addr ip );
|
||||
extern Vg_FnNameKind VG_(get_fnname_kind_from_IP) ( DiEpoch ep, Addr ip );
|
||||
|
||||
/* Looks up data_addr in the collection of data symbols, and if found
|
||||
puts its name (or as much as will fit) into dname[0 .. n_dname-1],
|
||||
which is guaranteed to be zero terminated. Also data_addr's offset
|
||||
from the symbol start is put into *offset. */
|
||||
extern Bool VG_(get_datasym_and_offset)( Addr data_addr,
|
||||
extern Bool VG_(get_datasym_and_offset)( DiEpoch ep, Addr data_addr,
|
||||
/*OUT*/const HChar** dname,
|
||||
/*OUT*/PtrdiffT* offset );
|
||||
|
||||
@ -147,7 +157,7 @@ extern Bool VG_(get_datasym_and_offset)( Addr data_addr,
|
||||
Bool VG_(get_data_description)(
|
||||
/*MOD*/ XArray* /* of HChar */ dname1v,
|
||||
/*MOD*/ XArray* /* of HChar */ dname2v,
|
||||
Addr data_addr
|
||||
DiEpoch ep, Addr data_addr
|
||||
);
|
||||
|
||||
/* True if we have some Call Frame unwindo debuginfo for Addr a */
|
||||
@ -157,7 +167,7 @@ extern Bool VG_(has_CF_info)(Addr a);
|
||||
It first searches if Addr a belongs to the text segment of debug info.
|
||||
If not found, it asks the address space manager whether it
|
||||
knows the name of the file associated with this mapping. */
|
||||
extern Bool VG_(get_objname) ( Addr a, const HChar** objname );
|
||||
extern Bool VG_(get_objname) ( DiEpoch ep, Addr a, const HChar** objname );
|
||||
|
||||
|
||||
/* Cursor allowing to describe inlined function calls at an IP,
|
||||
@ -172,7 +182,7 @@ typedef struct _InlIPCursor InlIPCursor;
|
||||
eip can possibly corresponds to inlined function call(s).
|
||||
To describe eip and the inlined function calls, the following must
|
||||
be done:
|
||||
InlIPCursor *iipc = VG_(new_IIPC)(eip);
|
||||
InlIPCursor *iipc = VG_(new_IIPC)(ep, eip);
|
||||
do {
|
||||
buf = VG_(describe_IP)(eip, iipc);
|
||||
... use buf ...
|
||||
@ -185,12 +195,16 @@ typedef struct _InlIPCursor InlIPCursor;
|
||||
Note, that the returned string is allocated in a static buffer local to
|
||||
VG_(describe_IP). That buffer will be overwritten with every invocation.
|
||||
Therefore, callers need to possibly stash away the string.
|
||||
|
||||
Since this maps a code location to a source artefact (function names),
|
||||
new_IIPC requires a DiEpoch argument (ep) too.
|
||||
*/
|
||||
extern const HChar* VG_(describe_IP)(Addr eip, const InlIPCursor* iipc);
|
||||
extern const HChar* VG_(describe_IP)(DiEpoch ep, Addr eip,
|
||||
const InlIPCursor* iipc);
|
||||
|
||||
/* Builds a IIPC (Inlined IP Cursor) to describe eip and all the inlined calls
|
||||
at eip. Such a cursor must be deleted after use using VG_(delete_IIPC). */
|
||||
extern InlIPCursor* VG_(new_IIPC)(Addr eip);
|
||||
extern InlIPCursor* VG_(new_IIPC)(DiEpoch ep, Addr eip);
|
||||
/* Move the cursor to the next call to describe.
|
||||
Returns True if there are still calls to describe.
|
||||
False if nothing to describe anymore. */
|
||||
@ -242,7 +256,7 @@ VG_(di_get_global_blocks_from_dihandle) ( ULong di_handle,
|
||||
|
||||
|
||||
/*====================================================================*/
|
||||
/*=== Obtaining debug information ===*/
|
||||
/*=== Obtaining information pertaining to shared objects. ===*/
|
||||
/*====================================================================*/
|
||||
|
||||
/* A way to make limited debuginfo queries on a per-mapped-object
|
||||
@ -251,7 +265,7 @@ typedef struct _DebugInfo DebugInfo;
|
||||
|
||||
/* Returns NULL if the DebugInfo isn't found. It doesn't matter if
|
||||
debug info is present or not. */
|
||||
DebugInfo* VG_(find_DebugInfo) ( Addr a );
|
||||
DebugInfo* VG_(find_DebugInfo) ( DiEpoch ep, Addr a );
|
||||
|
||||
/* Fish bits out of DebugInfos. */
|
||||
Addr VG_(DebugInfo_get_text_avma) ( const DebugInfo *di );
|
||||
|
||||
@ -30,7 +30,13 @@
|
||||
#ifndef __PUB_TOOL_EXECONTEXT_H
|
||||
#define __PUB_TOOL_EXECONTEXT_H
|
||||
|
||||
#include "pub_tool_basics.h" // ThreadID
|
||||
#include "pub_tool_basics.h" // ThreadID
|
||||
#include "pub_tool_debuginfo.h" // DiEpoch
|
||||
|
||||
|
||||
/*====================================================================*/
|
||||
/*=== ExeContext ===*/
|
||||
/*====================================================================*/
|
||||
|
||||
// It's an abstract type.
|
||||
typedef
|
||||
@ -74,6 +80,9 @@ ExeContext* VG_(record_depth_1_ExeContext)(ThreadId tid, Word first_ip_delta);
|
||||
// Apply a function to every element in the ExeContext. The parameter 'n'
|
||||
// gives the index of the passed ip. Doesn't go below main() unless
|
||||
// --show-below-main=yes is set.
|
||||
// Currently, the below function is unused. If ever it is used one day,
|
||||
// we should add epoch args similarly to function VG_(apply_StackTrace)
|
||||
// in pub_tool_stacktrace.h.
|
||||
extern void VG_(apply_ExeContext)( void(*action)(UInt n, Addr ip),
|
||||
ExeContext* ec, UInt n_ips );
|
||||
|
||||
@ -93,6 +102,9 @@ extern void VG_(pp_ExeContext) ( ExeContext* ec );
|
||||
// be zero, so that callers can store other information there.
|
||||
extern UInt VG_(get_ECU_from_ExeContext)( const ExeContext* e );
|
||||
|
||||
// Returns the epoch in which the ips of e can be symbolised.
|
||||
extern DiEpoch VG_(get_ExeContext_epoch)( const ExeContext* e );
|
||||
|
||||
// How many entries (frames) in this ExeContext?
|
||||
extern Int VG_(get_ExeContext_n_ips)( const ExeContext* e );
|
||||
|
||||
|
||||
@ -249,6 +249,12 @@ extern Int VG_(clo_backtrace_size);
|
||||
/* Continue stack traces below main()? Default: NO */
|
||||
extern Bool VG_(clo_show_below_main);
|
||||
|
||||
/* Keep symbols (and all other debuginfo) for code that is unloaded (dlclose
|
||||
or similar) so that stack traces can still give line/file info for
|
||||
previously captured stack traces. e.g. ... showing where a block was
|
||||
allocated e.g. leaks of or accesses just outside a block. */
|
||||
extern Bool VG_(clo_keep_debuginfo);
|
||||
|
||||
|
||||
/* Used to expand file names. "option_name" is the option name, eg.
|
||||
"--log-file". 'format' is what follows, eg. "cachegrind.out.%p". In
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
#ifndef __PUB_TOOL_STACKTRACE_H
|
||||
#define __PUB_TOOL_STACKTRACE_H
|
||||
|
||||
#include "pub_tool_basics.h" // Addr
|
||||
#include "pub_tool_basics.h" // Addr, DiEpoch
|
||||
|
||||
// The basic stack trace type: just an array of code addresses.
|
||||
typedef Addr* StackTrace;
|
||||
@ -75,19 +75,19 @@ extern UInt VG_(get_StackTrace_with_deltas)(
|
||||
Word first_sp_delta
|
||||
);
|
||||
|
||||
// Apply a function to every element in the StackTrace. The parameter
|
||||
// 'n' gives the index of the passed ip. 'opaque' is an arbitrary
|
||||
// pointer provided to each invocation of 'action' (a poor man's
|
||||
// closure). Doesn't go below main() unless --show-below-main=yes is
|
||||
// set.
|
||||
// Apply a function to every element in the StackTrace. The parameter 'n'
|
||||
// gives the index of the passed ip. 'opaque' is an arbitrary pointer
|
||||
// provided to each invocation of 'action' (a poor man's closure). 'ep' is
|
||||
// the debuginfo epoch assumed to apply to all code addresses in the stack
|
||||
// trace. Doesn't go below main() unless --show-below-main=yes is set.
|
||||
extern void VG_(apply_StackTrace)(
|
||||
void(*action)(UInt n, Addr ip, void* opaque),
|
||||
void(*action)(UInt n, DiEpoch ep, Addr ip, void* opaque),
|
||||
void* opaque,
|
||||
StackTrace ips, UInt n_ips
|
||||
DiEpoch ep, StackTrace ips, UInt n_ips
|
||||
);
|
||||
|
||||
// Print a StackTrace.
|
||||
extern void VG_(pp_StackTrace) ( StackTrace ips, UInt n_ips );
|
||||
extern void VG_(pp_StackTrace) ( DiEpoch ep, StackTrace ips, UInt n_ips );
|
||||
|
||||
// Gets and immediately prints a StackTrace. Just a bit simpler than
|
||||
// calling VG_(get_StackTrace)() then VG_(pp_StackTrace)().
|
||||
|
||||
@ -463,7 +463,7 @@ extern void VG_(needs_print_stats) (
|
||||
of an address ? */
|
||||
extern void VG_(needs_info_location) (
|
||||
// Get and pp information about Addr
|
||||
void (*info_location)(Addr)
|
||||
void (*info_location)(DiEpoch, Addr)
|
||||
);
|
||||
|
||||
/* Do we need to see variable type and location information? */
|
||||
|
||||
@ -121,7 +121,7 @@ extern void VG_(XT_filter_1top_and_maybe_below_main)
|
||||
/* Search in ips[0..n_ips-1] the first function which is main or below main
|
||||
and return its offset.
|
||||
If no main or below main is found, return n_ips-1 */
|
||||
extern Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips);
|
||||
extern Int VG_(XT_offset_main_or_below_main)(DiEpoch ep, Addr* ips, Int n_ips);
|
||||
|
||||
|
||||
/* Take a (frozen) snapshot of xt.
|
||||
|
||||
@ -664,6 +664,7 @@ IRSB* lk_instrument ( VgCallbackClosure* closure,
|
||||
Addr iaddr = 0, dst;
|
||||
UInt ilen = 0;
|
||||
Bool condition_inverted = False;
|
||||
DiEpoch ep = VG_(current_DiEpoch)();
|
||||
|
||||
if (gWordTy != hWordTy) {
|
||||
/* We don't currently support this case. */
|
||||
@ -750,7 +751,7 @@ IRSB* lk_instrument ( VgCallbackClosure* closure,
|
||||
tl_assert(clo_fnname);
|
||||
tl_assert(clo_fnname[0]);
|
||||
const HChar *fnname;
|
||||
if (VG_(get_fnname_if_entry)(st->Ist.IMark.addr,
|
||||
if (VG_(get_fnname_if_entry)(ep, st->Ist.IMark.addr,
|
||||
&fnname)
|
||||
&& 0 == VG_(strcmp)(fnname, clo_fnname)) {
|
||||
di = unsafeIRDirty_0_N(
|
||||
|
||||
@ -520,8 +520,9 @@ void filter_IPs (Addr* ips, Int n_ips,
|
||||
// alloc function 'inside' a stacktrace e.g.
|
||||
// 0x1 0x2 0x3 alloc func1 main
|
||||
// became 0x1 0x2 0x3 func1 main
|
||||
const DiEpoch ep = VG_(current_DiEpoch)();
|
||||
for (i = *top; i < n_ips; i++) {
|
||||
top_has_fnname = VG_(get_fnname)(ips[*top], &fnname);
|
||||
top_has_fnname = VG_(get_fnname)(ep, ips[*top], &fnname);
|
||||
if (top_has_fnname && VG_(strIsMemberXA)(alloc_fns, fnname)) {
|
||||
VERB(4, "filtering alloc fn %s\n", fnname);
|
||||
(*top)++;
|
||||
@ -536,7 +537,7 @@ void filter_IPs (Addr* ips, Int n_ips,
|
||||
if (!top_has_fnname) {
|
||||
// top has no fnname => search for the first entry that has a fnname
|
||||
for (i = *top; i < n_ips && !top_has_fnname; i++) {
|
||||
top_has_fnname = VG_(get_fnname)(ips[i], &fnname);
|
||||
top_has_fnname = VG_(get_fnname)(ep, ips[i], &fnname);
|
||||
}
|
||||
}
|
||||
if (top_has_fnname && VG_(strIsMemberXA)(ignore_fns, fnname)) {
|
||||
@ -544,10 +545,18 @@ void filter_IPs (Addr* ips, Int n_ips,
|
||||
*top = n_ips;
|
||||
*n_ips_sel = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!VG_(clo_show_below_main) && *n_ips_sel > 0 ) {
|
||||
Int mbm = VG_(XT_offset_main_or_below_main)(ips, n_ips);
|
||||
// Technically, it would be better to use the 'real' epoch that
|
||||
// was used to capture ips/n_ips. However, this searches
|
||||
// for a main or below_main function. It is technically possible
|
||||
// but unlikely that main or below main fn is in a dlclose-d library,
|
||||
// so current epoch is reasonable enough, even if not perfect.
|
||||
// FIXME PW EPOCH: would be better to also use the real ips epoch here,
|
||||
// once m_xtree.c massif output format properly supports epoch.
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
Int mbm = VG_(XT_offset_main_or_below_main)(cur_ep, ips, n_ips);
|
||||
|
||||
if (mbm < *top) {
|
||||
// Special case: the first main (or below main) function is an
|
||||
@ -581,7 +590,8 @@ static ExeContext* make_ec(ThreadId tid, Bool exclude_first_entry)
|
||||
if (exclude_first_entry && n_ips > 0) {
|
||||
const HChar *fnname;
|
||||
VERB(4, "removing top fn %s from stacktrace\n",
|
||||
VG_(get_fnname)(ips[0], &fnname) ? fnname : "???");
|
||||
VG_(get_fnname)(VG_(current_DiEpoch)(), ips[0], &fnname)
|
||||
? fnname : "???");
|
||||
return VG_(make_ExeContext_from_StackTrace)(ips+1, n_ips-1);
|
||||
} else
|
||||
return VG_(make_ExeContext_from_StackTrace)(ips, n_ips);
|
||||
|
||||
@ -1053,7 +1053,7 @@ static Bool mempool_block_maybe_describe( Addr a, Bool is_metapool,
|
||||
|
||||
/* Describe an address as best you can, for error messages,
|
||||
putting the result in ai. */
|
||||
static void describe_addr ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
static void describe_addr ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai )
|
||||
{
|
||||
MC_Chunk* mc;
|
||||
|
||||
@ -1121,15 +1121,15 @@ static void describe_addr ( Addr a, /*OUT*/AddrInfo* ai )
|
||||
}
|
||||
|
||||
/* No block found. Search a non-heap block description. */
|
||||
VG_(describe_addr) (a, ai);
|
||||
VG_(describe_addr) (ep, a, ai);
|
||||
}
|
||||
|
||||
void MC_(pp_describe_addr) ( Addr a )
|
||||
void MC_(pp_describe_addr) ( DiEpoch ep, Addr a )
|
||||
{
|
||||
AddrInfo ai;
|
||||
|
||||
ai.tag = Addr_Undescribed;
|
||||
describe_addr (a, &ai);
|
||||
describe_addr (ep, a, &ai);
|
||||
VG_(pp_addrinfo_mc) (a, &ai, /* maybe_gcc */ False);
|
||||
VG_(clear_addrinfo) (&ai);
|
||||
}
|
||||
@ -1150,6 +1150,7 @@ static void update_origin ( /*OUT*/ExeContext** origin_ec,
|
||||
UInt MC_(update_Error_extra)( const Error* err )
|
||||
{
|
||||
MC_Error* extra = VG_(get_error_extra)(err);
|
||||
DiEpoch ep = VG_(get_ExeContext_epoch)(VG_(get_error_where)(err));
|
||||
|
||||
switch (VG_(get_error_kind)(err)) {
|
||||
// These ones don't have addresses associated with them, and so don't
|
||||
@ -1183,31 +1184,31 @@ UInt MC_(update_Error_extra)( const Error* err )
|
||||
|
||||
// These ones always involve a memory address.
|
||||
case Err_Addr:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.Addr.ai );
|
||||
return sizeof(MC_Error);
|
||||
case Err_MemParam:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.MemParam.ai );
|
||||
update_origin( &extra->Err.MemParam.origin_ec,
|
||||
extra->Err.MemParam.otag );
|
||||
return sizeof(MC_Error);
|
||||
case Err_Jump:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.Jump.ai );
|
||||
return sizeof(MC_Error);
|
||||
case Err_User:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.User.ai );
|
||||
update_origin( &extra->Err.User.origin_ec,
|
||||
extra->Err.User.otag );
|
||||
return sizeof(MC_Error);
|
||||
case Err_Free:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.Free.ai );
|
||||
return sizeof(MC_Error);
|
||||
case Err_IllegalMempool:
|
||||
describe_addr ( VG_(get_error_address)(err),
|
||||
describe_addr ( ep, VG_(get_error_address)(err),
|
||||
&extra->Err.IllegalMempool.ai );
|
||||
return sizeof(MC_Error);
|
||||
|
||||
|
||||
@ -569,8 +569,8 @@ Bool MC_(record_fishy_value_error) ( ThreadId tid, const HChar* function,
|
||||
/* Leak kinds tokens to call VG_(parse_enum_set). */
|
||||
extern const HChar* MC_(parse_leak_kinds_tokens);
|
||||
|
||||
/* prints a description of address a */
|
||||
void MC_(pp_describe_addr) (Addr a);
|
||||
/* prints a description of address a in the specified debuginfo epoch */
|
||||
void MC_(pp_describe_addr) ( DiEpoch ep, Addr a );
|
||||
|
||||
/* Is this address in a user-specified "ignored range" ? */
|
||||
Bool MC_(in_ignored_range) ( Addr a );
|
||||
|
||||
@ -1137,16 +1137,18 @@ lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite,
|
||||
// let's see if its contents point to a chunk.
|
||||
if (UNLIKELY(searched)) {
|
||||
if (addr >= searched && addr < searched + szB) {
|
||||
const DiEpoch cur_ep = VG_(current_DiEpoch)();
|
||||
// The found addr is 'live', so use cur_ep to describe it.
|
||||
if (addr == searched) {
|
||||
VG_(umsg)("*%#lx points at %#lx\n", ptr, searched);
|
||||
MC_(pp_describe_addr) (ptr);
|
||||
MC_(pp_describe_addr) (cur_ep, ptr);
|
||||
} else {
|
||||
Int ch_no;
|
||||
MC_Chunk *ch;
|
||||
LC_Extra *ex;
|
||||
VG_(umsg)("*%#lx interior points at %lu bytes inside %#lx\n",
|
||||
ptr, (long unsigned) addr - searched, searched);
|
||||
MC_(pp_describe_addr) (ptr);
|
||||
MC_(pp_describe_addr) (cur_ep, ptr);
|
||||
if (lc_is_a_chunk_ptr(addr, &ch_no, &ch, &ex) ) {
|
||||
Int h;
|
||||
for (h = LchStdString; h < N_LEAK_CHECK_HEURISTICS; h++) {
|
||||
|
||||
@ -6746,7 +6746,8 @@ static Bool handle_gdb_monitor_command (ThreadId tid, HChar *req)
|
||||
VG_(printf)
|
||||
("Address %p len %lu not addressable:\nbad address %p\n",
|
||||
(void *)address, szB, (void *) bad_addr);
|
||||
MC_(pp_describe_addr) (address);
|
||||
// Describe this (probably live) address with current epoch
|
||||
MC_(pp_describe_addr) (VG_(current_DiEpoch)(), address);
|
||||
break;
|
||||
case 1: /* defined */
|
||||
res = is_mem_defined ( address, szB, &bad_addr, &otag );
|
||||
@ -6780,7 +6781,8 @@ static Bool handle_gdb_monitor_command (ThreadId tid, HChar *req)
|
||||
else
|
||||
VG_(printf) ("Address %p len %lu defined\n",
|
||||
(void *)address, szB);
|
||||
MC_(pp_describe_addr) (address);
|
||||
// Describe this (probably live) address with current epoch
|
||||
MC_(pp_describe_addr) (VG_(current_DiEpoch)(), address);
|
||||
break;
|
||||
default: tl_assert(0);
|
||||
}
|
||||
|
||||
@ -6,6 +6,10 @@ dist_noinst_SCRIPTS = filter_stderr
|
||||
EXTRA_DIST = \
|
||||
brk.stderr.exp brk.vgtest \
|
||||
capget.vgtest capget.stderr.exp capget.stderr.exp2 capget.stderr.exp3 \
|
||||
dlclose_leak-no-keep.stderr.exp dlclose_leak-no-keep.stdout.exp \
|
||||
dlclose_leak-no-keep.vgtest \
|
||||
dlclose_leak.stderr.exp dlclose_leak.stdout.exp \
|
||||
dlclose_leak.vgtest \
|
||||
ioctl-tiocsig.vgtest ioctl-tiocsig.stderr.exp \
|
||||
lsframe1.vgtest lsframe1.stdout.exp lsframe1.stderr.exp \
|
||||
lsframe2.vgtest lsframe2.stdout.exp lsframe2.stderr.exp \
|
||||
@ -25,6 +29,7 @@ EXTRA_DIST = \
|
||||
check_PROGRAMS = \
|
||||
brk \
|
||||
capget \
|
||||
dlclose_leak dlclose_leak_so.so \
|
||||
ioctl-tiocsig \
|
||||
getregset \
|
||||
lsframe1 \
|
||||
@ -48,3 +53,17 @@ AM_CXXFLAGS += $(AM_FLAG_M3264_PRI)
|
||||
stack_switch_LDADD = -lpthread
|
||||
timerfd_syscall_LDADD = -lrt
|
||||
|
||||
# Build shared object for dlclose_leak
|
||||
dlclose_leak_so_so_SOURCES = dlclose_leak_so.c
|
||||
dlclose_leak_so_so_CFLAGS = $(AM_CFLAGS) -fpic -g -O0
|
||||
dlclose_leak_so_so_LDFLAGS = -fpic $(AM_FLAG_M3264_PRI) -shared -Wl,-soname \
|
||||
-Wl,dlclose_leak_so.so
|
||||
|
||||
dlclose_leak_SOURCES = dlclose_leak.c
|
||||
dlclose_leak_DEPENDENCIES = dlclose_leak_so.so
|
||||
# Do NOT uncomment the below line: we must not link with the .so,
|
||||
# in order to properly test a 'fully dynamic' use of dlopen/dlclose
|
||||
# dlclose_leak_LDADD = dlclose_leak_so.so
|
||||
dlclose_leak_LDADD = -ldl
|
||||
dlclose_leak_LDFLAGS = $(AM_FLAG_M3264_PRI) \
|
||||
-Wl,-rpath,$(top_builddir)/memcheck/tests/linux
|
||||
|
||||
17
memcheck/tests/linux/dlclose_leak-no-keep.stderr.exp
Normal file
17
memcheck/tests/linux/dlclose_leak-no-keep.stderr.exp
Normal file
@ -0,0 +1,17 @@
|
||||
Conditional jump or move depends on uninitialised value(s)
|
||||
at 0x........: jmp_on_uninit (dlclose_leak_so.c:10)
|
||||
by 0x........: main (dlclose_leak.c:26)
|
||||
|
||||
Invalid read of size 1
|
||||
at 0x........: main (dlclose_leak.c:29)
|
||||
Address 0x........ is 1 bytes before a block of size 1 alloc'd
|
||||
at 0x........: malloc (vg_replace_malloc.c:...)
|
||||
...
|
||||
by 0x........: main (dlclose_leak.c:27)
|
||||
|
||||
done!
|
||||
1 bytes in 1 blocks are definitely lost in loss record ... of ...
|
||||
at 0x........: malloc (vg_replace_malloc.c:...)
|
||||
...
|
||||
by 0x........: main (dlclose_leak.c:27)
|
||||
|
||||
4
memcheck/tests/linux/dlclose_leak-no-keep.vgtest
Normal file
4
memcheck/tests/linux/dlclose_leak-no-keep.vgtest
Normal file
@ -0,0 +1,4 @@
|
||||
prog: dlclose_leak
|
||||
stderr_filter: ../filter_stderr
|
||||
stderr_filter_args: dlclose_leak
|
||||
vgopts: -q --leak-check=yes --keep-debuginfo=no
|
||||
32
memcheck/tests/linux/dlclose_leak.c
Normal file
32
memcheck/tests/linux/dlclose_leak.c
Normal file
@ -0,0 +1,32 @@
|
||||
/* Test reporting of memory leaks in objects that have been dlopen'ed.
|
||||
* File: dlclose_leak.c */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <assert.h>
|
||||
|
||||
int (*jmp_on_uninit)(void);
|
||||
char* (*alloc_1_byte)(void);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
char* memToLeak;
|
||||
char x __attribute__((unused));
|
||||
void* handle = dlopen("./dlclose_leak_so.so", RTLD_NOW);
|
||||
if(!handle) {
|
||||
printf("FAILURE to dlopen dlclose_leak_so.so\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
jmp_on_uninit = dlsym(handle,"jmp_on_uninit");
|
||||
//fprintf(stderr, "jmp_on_uninit: %p\n", jmp_on_uninit);
|
||||
assert(jmp_on_uninit);
|
||||
alloc_1_byte = dlsym(handle,"alloc_1_byte");
|
||||
//fprintf(stderr, "alloc_1_byte: %p\n", alloc_1_byte);
|
||||
assert(alloc_1_byte);
|
||||
(void)jmp_on_uninit();
|
||||
memToLeak = alloc_1_byte();
|
||||
dlclose(handle);
|
||||
x = memToLeak[-1];
|
||||
fprintf(stderr, "done!\n");
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
17
memcheck/tests/linux/dlclose_leak.stderr.exp
Normal file
17
memcheck/tests/linux/dlclose_leak.stderr.exp
Normal file
@ -0,0 +1,17 @@
|
||||
Conditional jump or move depends on uninitialised value(s)
|
||||
at 0x........: jmp_on_uninit (dlclose_leak_so.c:10)
|
||||
by 0x........: main (dlclose_leak.c:26)
|
||||
|
||||
Invalid read of size 1
|
||||
at 0x........: main (dlclose_leak.c:29)
|
||||
Address 0x........ is 1 bytes before a block of size 1 alloc'd
|
||||
at 0x........: malloc (vg_replace_malloc.c:...)
|
||||
by 0x........: alloc_1_byte (dlclose_leak_so.c:20)
|
||||
by 0x........: main (dlclose_leak.c:27)
|
||||
|
||||
done!
|
||||
1 bytes in 1 blocks are definitely lost in loss record ... of ...
|
||||
at 0x........: malloc (vg_replace_malloc.c:...)
|
||||
by 0x........: alloc_1_byte (dlclose_leak_so.c:20)
|
||||
by 0x........: main (dlclose_leak.c:27)
|
||||
|
||||
0
memcheck/tests/linux/dlclose_leak.stdout.exp
Normal file
0
memcheck/tests/linux/dlclose_leak.stdout.exp
Normal file
3
memcheck/tests/linux/dlclose_leak.vgtest
Normal file
3
memcheck/tests/linux/dlclose_leak.vgtest
Normal file
@ -0,0 +1,3 @@
|
||||
prog: dlclose_leak
|
||||
stderr_filter: ../filter_stderr
|
||||
vgopts: -q --leak-check=yes --keep-debuginfo=yes
|
||||
21
memcheck/tests/linux/dlclose_leak_so.c
Normal file
21
memcheck/tests/linux/dlclose_leak_so.c
Normal file
@ -0,0 +1,21 @@
|
||||
/* dlclose_leak_so.c */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/** Makes a jump based on an uninitialized variable in order to make sure
|
||||
* errors reported while the dlopen'ed object is loaded work. */
|
||||
int jmp_on_uninit(void) {
|
||||
int uninit[27];
|
||||
__asm__ __volatile("":::"cc","memory");
|
||||
if(uninit[13]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Leak 1 byte of memory. This is to test the stack check reported after the
|
||||
* object has been dlclose'd. */
|
||||
char* alloc_1_byte(void) {
|
||||
return (char*)malloc(1);
|
||||
}
|
||||
@ -43,6 +43,10 @@ usage: valgrind [options] prog-and-args
|
||||
--error-exitcode=<number> exit code to return if errors found [0=disable]
|
||||
--error-markers=<begin>,<end> add lines with begin/end markers before/after
|
||||
each error output in plain text mode [none]
|
||||
--keep-debuginfo=no|yes Keep symbols etc for unloaded code [no]
|
||||
This allows saved stack traces (e.g. memory leaks)
|
||||
to include file/line info for code that has been
|
||||
dlclose'd (or similar)
|
||||
--show-below-main=no|yes continue stack traces below main() [no]
|
||||
--default-suppressions=yes|no
|
||||
load default suppressions [yes]
|
||||
|
||||
@ -43,6 +43,10 @@ usage: valgrind [options] prog-and-args
|
||||
--error-exitcode=<number> exit code to return if errors found [0=disable]
|
||||
--error-markers=<begin>,<end> add lines with begin/end markers before/after
|
||||
each error output in plain text mode [none]
|
||||
--keep-debuginfo=no|yes Keep symbols etc for unloaded code [no]
|
||||
This allows saved stack traces (e.g. memory leaks)
|
||||
to include file/line info for code that has been
|
||||
dlclose'd (or similar)
|
||||
--show-below-main=no|yes continue stack traces below main() [no]
|
||||
--default-suppressions=yes|no
|
||||
load default suppressions [yes]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user