Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions callgrind/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ CALLGRIND_SOURCES_COMMON = \
jumps.c \
main.c \
sim.c \
subprocess.c \
threads.c

# We sneakily include "cg_branchpred.c" and "cg_arch.c" from cachegrind
Expand Down
50 changes: 46 additions & 4 deletions callgrind/callstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "global.h"
#include "pub_tool_stacktrace.h"
#include "pub_tool_threadstate.h"
#if defined(VGA_arm64)
#include "pub_tool_guest.h" /* VexGuestArchState, for guest_X30 */
#endif
Expand Down Expand Up @@ -500,9 +501,10 @@ Int CLG_(unwind_call_stack)(Addr sp, Int minpops)
*
* Called on the OFF->ON instrumentation transition: the client (e.g.
* pytest_codspeed) typically reaches CALLGRIND_START_INSTRUMENTATION several
* libpython frames deep. Without seeding, csp stays at 0 while the real
* stack is non-empty, and every subsequent ret trips handleUnderflow and
* leaks the returned-into fn as a top-level fn= block.
* libpython frames deep, and other threads sit parked mid-stack. Without
* seeding, csp stays at 0 while the real stack is non-empty, and every
* subsequent ret trips handleUnderflow and leaks the returned-into fn as a
* top-level fn= block.
*
* We push a (jcc=0, skip-style) call_entry for every native frame so
* SP-based unwind works. For frames that should appear in the output
Expand All @@ -512,7 +514,9 @@ Int CLG_(unwind_call_stack)(Addr sp, Int minpops)
* deliberately excluded from the cxt chain — they get SP-only entries. */
#define CLG_RECON_MAX_FRAMES 256

void CLG_(reconstruct_call_stack_from_native)(ThreadId tid)
/* Seeds the call stack currently installed in the globals, which must be the
* one belonging to tid. */
static void seed_call_stack_from_native(ThreadId tid)
{
Addr ips[CLG_RECON_MAX_FRAMES];
Addr sps[CLG_RECON_MAX_FRAMES];
Expand All @@ -523,6 +527,18 @@ void CLG_(reconstruct_call_stack_from_native)(ThreadId tid)
UInt n = VG_(get_StackTrace)(tid, ips, CLG_RECON_MAX_FRAMES, sps, NULL, 0);
if (n == 0) return;

/* A thread other than the running one is unwound from the register state
* saved when it was descheduled, usually inside a syscall. A single frame
* means the unwinder got no further than that point: the one entry would
* carry a made-up entry SP (see the ce->sp comment below), and everything
* the thread runs next would be re-parented under it. Starting empty is
* the lesser evil. */
if (n < 2 && tid != VG_(get_running_tid)()) {
CLG_DEBUG(1, " seed: thread %u unwound to a single frame, skipping\n",
tid);
return;
}

/* Push bottom-up: oldest caller first, current frame last. */
for (Int frame = n - 1; frame >= 0; frame--) {
fn_node* fn = CLG_(get_fn_node_for_addr)(ips[frame]);
Expand Down Expand Up @@ -581,4 +597,30 @@ void CLG_(reconstruct_call_stack_from_native)(ThreadId tid)
ensure_stack_size(cs->sp + 1);
cs->entry[cs->sp].cxt = 0;
}

CLG_DEBUG(1, " seed: thread %u seeded with %u frame(s), csp=%d\n",
tid, n, cs->sp);
}

void CLG_(reconstruct_call_stack_from_native)(ThreadId requesting_tid)
{
/* Every live thread is seeded, not only the requesting one. A thread parked
* in a syscall at the transition has a non-empty native stack and an empty
* shadow stack just the same, and its first ret underflows: handleUnderflow
* pushes the returned-into fn as a fresh top-level context without
* consulting fn->skip, so an obj-skipped frame becomes a visible root and
* everything the thread does afterwards is recorded under it.
*
* The call stack, context chain and fn stack live in globals belonging to
* whichever thread is switched in, so each thread is seeded under its own
* switch_thread. */
for (ThreadId tid = 1; tid < VG_N_THREADS; tid++) {
/* Zero for any tid that is not a live thread. */
if (VG_(get_thread_lwpid)(tid) == 0) continue;

CLG_(switch_thread)(tid);
seed_call_stack_from_native(tid);
}

CLG_(switch_thread)(requesting_tid);
}
5 changes: 5 additions & 0 deletions callgrind/clo.c
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ Bool CLG_(process_cmd_line_option)(const HChar* arg)

else if VG_BOOL_CLO(arg, "--collect-atstart", CLG_(clo).collect_atstart) {}

else if VG_XACT_CLO(arg, "--instr-atstart=inherit",
CLG_(clo).instrument_atstart_inherit, True) {}
else if VG_BOOL_CLO(arg, "--instr-atstart", CLG_(clo).instrument_atstart) {}

else if VG_BOOL_CLO(arg, "--separate-threads", CLG_(clo).separate_threads) {}
Expand Down Expand Up @@ -605,6 +607,8 @@ void CLG_(print_usage)(void)

"\n data collection options:\n"
" --instr-atstart=no|yes Do instrumentation at callgrind start [yes]\n"
" --instr-atstart=inherit Like 'no', but inherit the instrumentation\n"
" state across a traced exec\n"
" --collect-atstart=no|yes Collect at process/thread start [yes]\n"
" --toggle-collect=<func> Toggle collection on enter/leave function\n"
" --collect-jumps=no|yes Collect jumps? [no]\n"
Expand Down Expand Up @@ -694,6 +698,7 @@ void CLG_(set_clo_defaults)(void)

/* Instrumentation */
CLG_(clo).instrument_atstart = True;
CLG_(clo).instrument_atstart_inherit = False;
CLG_(clo).simulate_cache = False;
CLG_(clo).simulate_branch = False;
CLG_(clo).cycle_estimation = False;
Expand Down
18 changes: 18 additions & 0 deletions callgrind/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ Int CLG_(get_dump_counter)(void)
return out_counter;
}

/* Restart part numbering in a fork child: its dumps go to a fresh per-pid file
* whose parts start at 1, like an exec'd child. init_dumps() would reset the
* counter anyway on the pid change, but only at the first dump - too late for
* the part number this process forwards to its own children in the meantime. */
void CLG_(reset_dump_counter)(void)
{
out_counter = 0;
}

/*------------------------------------------------------------*/
/*--- Output file related stuff ---*/
/*------------------------------------------------------------*/
Expand Down Expand Up @@ -1369,6 +1378,11 @@ static VgFile *new_dumpfile(thread_info* ti, const HChar* trigger)
}

VG_(fprintf)(fp, "\npart: %d\n", out_counter);

/* Per-part, not in the once-per-file header, so a child spawned during a
* later part is still recorded. */
CLG_(print_spawned_children)(fp);

if (CLG_(clo).separate_threads) {
const HChar* tname = CLG_(thread_name)(ti);

Expand Down Expand Up @@ -1684,6 +1698,10 @@ static void print_bbccs(const HChar* trigger, Bool only_current_thread)
CLG_(forall_threads_incl_exited)(print_one_empty_section);
}

/* All of these have just been emitted under the part being dumped; a later
* part cannot reference them. */
CLG_(forget_spawned_children)();

free_dump_array();
}

Expand Down
16 changes: 16 additions & 0 deletions callgrind/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ struct _CommandLineOptions {

/* Instrument options */
Bool instrument_atstart; /* Instrument at start? */
Bool instrument_atstart_inherit; /* "inherit": like "no", but adopt the
state advertised by this PID's pre-exec
image, and advertise ours likewise */
Bool simulate_cache; /* Call into cache simulator ? */
Bool simulate_branch; /* Call into branch prediction simulator ? */
Bool cycle_estimation; /* Estimate per-instruction cycles (Ct/Cl) ? */
Expand Down Expand Up @@ -730,6 +733,8 @@ void CLG_(zero_all_cost)(Bool only_current_thread);
* an already-unwound or empty state. */
void CLG_(unwind_thread)(thread_info* t);
Int CLG_(get_dump_counter)(void);
void CLG_(reset_dump_counter)(void);
void CLG_(restamp_syscall_time)(ThreadId tid);
void CLG_(fini)(Int exitcode);

/* from bb.c */
Expand Down Expand Up @@ -825,6 +830,17 @@ void CLG_(run_post_signal_on_call_stack_bottom)(void);
/* from dump.c */
void CLG_(init_dumps)(void);

/* from subprocess.c */
void CLG_(init_subprocess)(void);
/* Fed the result of every syscall, to spot the ones spawning a process. */
void CLG_(syscall_return)(SysRes res);
void CLG_(print_spawned_children)(VgFile* fp);
void CLG_(forget_spawned_children)(void);
/* Advertise the instrumentation state this process is in to a process
* inheriting from it, and adopt what was advertised to this one. */
void CLG_(publish_instr_state)(Bool on);
Bool CLG_(inherited_instr_state)(void);

/*------------------------------------------------------------*/
/*--- Exported global variables ---*/
/*------------------------------------------------------------*/
Expand Down
38 changes: 34 additions & 4 deletions callgrind/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,9 @@ void CLG_(set_instrument_state)(const HChar* reason, Bool state)
reason, state ? "ON" : "OFF");
return;
}
/* advertise before switching: the advertised state must never lag behind
* the state this process is actually in */
CLG_(publish_instr_state)(state);
CLG_(instrument_state) = state;
CLG_DEBUG(2, "%s: Switching instrumentation %s ...\n",
reason, state ? "ON" : "OFF");
Expand Down Expand Up @@ -1779,12 +1782,22 @@ void collect_time (struct vki_timespec *systime, struct vki_timespec *syscputime
}
}

/* Stamp the start time of the syscall a thread is entering. Also used to
re-stamp it, when what was stamped no longer applies to the clocks the delta
will be computed against. */
void CLG_(restamp_syscall_time)(ThreadId tid)
{
if (CLG_(clo).collect_systime == systime_no) return;

collect_time(&syscalltime[tid],
CLG_(clo).collect_systime == systime_nsec ? &syscallcputime[tid] : NULL);
}

static
void CLG_(pre_syscalltime)(ThreadId tid, UInt syscallno,
UWord* args, UInt nArgs)
{
collect_time(&syscalltime[tid],
CLG_(clo).collect_systime == systime_nsec ? &syscallcputime[tid] : NULL);
CLG_(restamp_syscall_time)(tid);
}

/* Returns "after - before" in the unit as specified by --collect-systime.
Expand Down Expand Up @@ -1814,6 +1827,11 @@ static
void CLG_(post_syscalltime)(ThreadId tid, UInt syscallno,
UWord* args, UInt nArgs, SysRes res)
{
/* The result of a fork is the only place its child's pid appears. */
CLG_(syscall_return)(res);

if (CLG_(clo).collect_systime == systime_no) return;

if (CLG_(current_state).bbcc) {
Int o;
struct vki_timespec ts_now;
Expand Down Expand Up @@ -2054,6 +2072,9 @@ void finish(void)
void CLG_(fini)(Int exitcode)
{
finish();
/* only runs at process exit, not on exec: the advertised state must
* survive a traced exec for the next image to adopt it */
CLG_(publish_instr_state)(False);
}


Expand Down Expand Up @@ -2088,9 +2109,11 @@ void CLG_(post_clo_init)(void)
"sp-at-mem-access\n");
}

/* Always needed: the wrapper is also how a fork's child pid is picked up. */
VG_(needs_syscall_wrapper)(CLG_(pre_syscalltime),
CLG_(post_syscalltime));

if (CLG_(clo).collect_systime != systime_no) {
VG_(needs_syscall_wrapper)(CLG_(pre_syscalltime),
CLG_(post_syscalltime));
syscalltime = CLG_MALLOC("cl.main.pci.1",
VG_N_THREADS * sizeof syscalltime[0]);
for (UInt i = 0; i < VG_N_THREADS; ++i) {
Expand Down Expand Up @@ -2166,6 +2189,11 @@ void CLG_(post_clo_init)(void)
CLG_(run_thread)(1);

CLG_(instrument_state) = CLG_(clo).instrument_atstart;
if (CLG_(clo).instrument_atstart_inherit) {
/* the adopted state is already the advertised one, so nothing to
* publish here: the file this image found is the file it keeps */
CLG_(instrument_state) = CLG_(inherited_instr_state)();
}

if (VG_(clo_verbosity) > 0) {
VG_(message)(Vg_UserMsg,
Expand Down Expand Up @@ -2212,6 +2240,8 @@ void CLG_(pre_clo_init)(void)
VG_(track_post_deliver_signal)( & CLG_(post_signal) );
VG_(track_pre_thread_ll_exit) ( & CLG_(pre_thread_ll_exit) );

CLG_(init_subprocess)();

CLG_(set_clo_defaults)();

}
Expand Down
Loading
Loading