/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
* ==================== 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 =================
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
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 = "???";
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 */
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));
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++;
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
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;
}
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;
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
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;
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;
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);
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",
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;
#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"
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 ---*/
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 ---*/
/*------------------------------------------------------------*/
}
+// 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 ---*/
/*------------------------------------------------------------*/
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",
}
-/* '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;
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;
if (!found) break;
anyFound = True;
- discard_DebugInfo( curr );
+ discard_or_archive_DebugInfo( curr );
}
return anyFound;
}
-/* 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;
}
if (!curr) break;
- discard_DebugInfo( curr );
+ discard_or_archive_DebugInfo( curr );
}
}
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();
}
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;
di->next = debugInfo_list;
debugInfo_list = di;
}
+ vg_assert(!is_DebugInfo_archived(di));
return 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 "
/* 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
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 );
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)");
+ }
}
# 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)");
+ }
}
}
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);
} 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);
/*------------------------------------------------------------*/
/*--- 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
}
/* 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).
return lo - 1;
}
-InlIPCursor* VG_(new_IIPC)(Addr eip)
+InlIPCursor* VG_(new_IIPC)(DiEpoch ep, Addr eip)
{
DebugInfo* di;
Word locno;
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.
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;
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
}
-/* 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
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];
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
(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];
-
- if (UNLIKELY(se->sym_avma != a || se->isText != findText)) {
+ // 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;
+
+ 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)
/* 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)
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,
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,
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,
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,
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,
/* 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,
}
}
-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 {
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,
(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;
/* 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
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;
/* 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
/* 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);
}
/* 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;
/* 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 )
Word locno;
UInt fndn_ix;
- search_all_loctabs ( a, &si, &locno );
+ search_all_loctabs ( ep, a, &si, &locno );
if (si == NULL) {
if (dirname) {
*dirname = "";
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;
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");
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;
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;
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]
// ??? 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 {
/*--- ---*/
/*--------------------------------------------------------------*/
+/* 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
*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,
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)
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);
Bool VG_(use_FPO_info) ( /*MOD*/Addr* ipP,
/*MOD*/Addr* spP,
/*MOD*/Addr* fpP,
+ DiEpoch ep,
Addr min_accessible,
Addr max_accessible )
{
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)
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: */
/* 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;
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
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 );
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 );
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
*/
#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");
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");
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);
// 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));
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;
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.
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);
// 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 = "???";
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
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
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 = "???";
}
}
-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;
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;
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. */
/*------------------------------------------------------------*/
-/*--- Exported functions. ---*/
+/*--- ExeContext functions. ---*/
/*------------------------------------------------------------*/
static ExeContext* record_ExeContext_wrk2 ( const Addr* ips, UInt n_ips );
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 )
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,
/* 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 )
// 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. */
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. */
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++;
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];
}
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);
}
int kwdid;
int int_value;
+ const DiEpoch cur_ep = VG_(current_DiEpoch)();
+
vg_assert (initial_valgrind_sink_saved);
strcpy (s, mon);
}
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
&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);
}
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;
// 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"));
/* 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;
/* 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;
);
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;
}
" --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"
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)) {}
}
# 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");
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;
{
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",
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;
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;
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;
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;
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
/* 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++;
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;
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;
}
);
}
-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 {
}
/* 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");
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;
// 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)
for (i = 0; i < n_ips; i++)
// Act on the ip
- action(i, ips[i], opaque);
+ action(i, ep, ips[i], opaque);
}
}
void VG_(needs_info_location) (
- void (*info_location)(Addr)
+ void (*info_location)(DiEpoch, Addr)
)
{
VG_(needs).info_location = True;
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
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
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,
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);
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",
// 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)) { \
called_linenum = 0; \
} \
if ((n) < 0 \
- || !VG_(get_fnname)(ips[(n)], &called_fnname)) { \
+ || !VG_(get_fnname)(ep, ips[(n)], &called_fnname)) { \
called_fnname = "UnknownFn???"; \
} \
{ \
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) {
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)
}
}
-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
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;
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;
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;
}
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;
}
* 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.
extern Bool VG_(use_FPO_info) ( /*MOD*/Addr* ipP,
/*MOD*/Addr* spP,
/*MOD*/Addr* fpP,
+ DiEpoch ep,
Addr min_accessible,
Addr max_accessible );
/* 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
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
// 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
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);
</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>
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) );
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
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) {
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 );
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:
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
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 =
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--;
#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);
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. */
}
}
-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;
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.
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
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");
}
}
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");
}
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) {
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);
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 )
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;
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;
}
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;
}
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;
(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
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++;
// 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.
// (spoffset will be negative, as stacks are assumed growing down).
struct {
ThreadInfo tinfo;
+ DiEpoch epoch;
Addr IP;
Int frameNo;
StackPos stackPos;
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
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);
/* 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.
#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.
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.
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 );
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 {
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 );
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 */
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,
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 ...
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. */
/*====================================================================*/
-/*=== Obtaining debug information ===*/
+/*=== Obtaining information pertaining to shared objects. ===*/
/*====================================================================*/
/* A way to make limited debuginfo queries on a per-mapped-object
/* 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 );
#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
// 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 );
// 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 );
/* 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
#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;
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)().
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? */
/* 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.
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. */
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(
// 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)++;
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)) {
*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
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);
/* 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;
}
/* 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);
}
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
// 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);
/* 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 );
// 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++) {
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 );
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);
}
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 \
check_PROGRAMS = \
brk \
capget \
+ dlclose_leak dlclose_leak_so.so \
ioctl-tiocsig \
getregset \
lsframe1 \
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
--- /dev/null
+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)
+
--- /dev/null
+prog: dlclose_leak
+stderr_filter: ../filter_stderr
+stderr_filter_args: dlclose_leak
+vgopts: -q --leak-check=yes --keep-debuginfo=no
--- /dev/null
+/* 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);
+}
--- /dev/null
+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)
+
--- /dev/null
+prog: dlclose_leak
+stderr_filter: ../filter_stderr
+vgopts: -q --leak-check=yes --keep-debuginfo=yes
--- /dev/null
+/* 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);
+}
--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]
--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]