diff --git a/callgrind/Makefile.am b/callgrind/Makefile.am index 4aa85f927..1e5bcb97d 100644 --- a/callgrind/Makefile.am +++ b/callgrind/Makefile.am @@ -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 diff --git a/callgrind/callstack.c b/callgrind/callstack.c index c365bee61..d6cedccba 100644 --- a/callgrind/callstack.c +++ b/callgrind/callstack.c @@ -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 @@ -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 @@ -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]; @@ -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]); @@ -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); } diff --git a/callgrind/clo.c b/callgrind/clo.c index 90bd6c4ff..f4059cca8 100644 --- a/callgrind/clo.c +++ b/callgrind/clo.c @@ -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) {} @@ -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= Toggle collection on enter/leave function\n" " --collect-jumps=no|yes Collect jumps? [no]\n" @@ -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; diff --git a/callgrind/dump.c b/callgrind/dump.c index d077bcd3b..b1e7eab53 100644 --- a/callgrind/dump.c +++ b/callgrind/dump.c @@ -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 ---*/ /*------------------------------------------------------------*/ @@ -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); @@ -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(); } diff --git a/callgrind/global.h b/callgrind/global.h index 6ec236352..328d027f0 100644 --- a/callgrind/global.h +++ b/callgrind/global.h @@ -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) ? */ @@ -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 */ @@ -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 ---*/ /*------------------------------------------------------------*/ diff --git a/callgrind/main.c b/callgrind/main.c index b777f55c0..bda942f7f 100644 --- a/callgrind/main.c +++ b/callgrind/main.c @@ -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"); @@ -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. @@ -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; @@ -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); } @@ -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) { @@ -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, @@ -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)(); } diff --git a/callgrind/subprocess.c b/callgrind/subprocess.c new file mode 100644 index 000000000..a59a06d7a --- /dev/null +++ b/callgrind/subprocess.c @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------*/ +/*--- Callgrind ---*/ +/*--- ct_subprocess.c ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Callgrind, a Valgrind tool for call tracing. + + Copyright (C) 2002-2017, Josef Weidendorfer (Josef.Weidendorfer@gmx.de) + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + + The GNU General Public License is contained in the file COPYING. +*/ + +/* Everything about processes other than this one: recording the children it + * spawns, and what a spawned process inherits from its spawner. + */ + +#include "global.h" + +/*------------------------------------------------------------*/ +/*--- Instrumentation state handover (--instr-atstart=inherit)---*/ +/*------------------------------------------------------------*/ + +/* A traced exec replaces the image (and resets valgrind) but keeps the PID, so + * runtime state can be handed over through a file whose name is derived from + * the PID alone: each process advertises "instrumentation is on" by keeping + * /callgrind-instr- in existence, and a fresh valgrind started + * with --instr-atstart=inherit adopts the state advertised for its own PID. A + * fork child inherits the in-memory state with the address space and just + * republishes it under its new PID. + * + * The file is empty: its existence is the whole message. + */ + +static void instr_state_path(HChar* buf, Int size) +{ + VG_(snprintf)(buf, size, "%s/callgrind-instr-%d", + VG_(tmpdir)(), VG_(getpid)()); +} + +void CLG_(publish_instr_state)(Bool on) +{ + HChar path[256]; + SysRes res; + + if (!CLG_(clo).instrument_atstart_inherit) return; + + instr_state_path(path, sizeof path); + if (!on) { + VG_(unlink)(path); + return; + } + res = VG_(open)(path, VKI_O_CREAT|VKI_O_WRONLY, + VKI_S_IRUSR|VKI_S_IWUSR); + if (sr_isError(res)) { + VG_(message)(Vg_UserMsg, + "warning: cannot create instrumentation state file %s\n", + path); + return; + } + VG_(close)(sr_Res(res)); +} + +Bool CLG_(inherited_instr_state)(void) +{ + HChar path[256]; + struct vg_stat st; + + instr_state_path(path, sizeof path); + return !sr_isError(VG_(stat)(path, &st)); +} + +/*------------------------------------------------------------*/ +/*--- Children spawned by this process ---*/ +/*------------------------------------------------------------*/ + +/* Children spawned since the last dump, listed in the header of the part they + * were spawned during, which is what attributes a child to the exact part of + * its parent that spawned it. */ +static Int* spawned = 0; +static Int n_spawned = 0; +static Int spawned_capacity = 0; + +static void record_spawned_child(Int child_pid) +{ + if (n_spawned == spawned_capacity) { + spawned_capacity = spawned_capacity ? spawned_capacity * 2 : 8; + spawned = VG_(realloc)("cl.subprocess.spawn.1", spawned, + spawned_capacity * sizeof(Int)); + } + spawned[n_spawned++] = child_pid; +} + +/* "desc:" lines so other consumers of the format (kcachegrind, + * callgrind_annotate) ignore them. */ +void CLG_(print_spawned_children)(VgFile* fp) +{ + Int i; + + for (i = 0; i < n_spawned; i++) + VG_(fprintf)(fp, "desc: Spawned pid: %d\n", spawned[i]); +} + +void CLG_(forget_spawned_children)(void) +{ + n_spawned = 0; +} + +/*------------------------------------------------------------*/ +/*--- Fork ---*/ +/*------------------------------------------------------------*/ + +/* Whether the syscall currently in flight is a fork, as announced by the core + * through the atfork pre hook. The hooks do not carry the new child's pid, but + * the syscall it is announcing returns it in the parent, so the pid is picked + * up when that syscall returns. */ +static Bool forking = False; + +static void clg_atfork_pre(ThreadId tid) +{ + forking = True; +} + +/* Costs accumulated before the fork belong to - and are dumped by - the + * parent; zero everything so this process only reports its own work. Threads + * other than the forking one do not exist in the child; zeroing their copied + * state means they are skipped at dump time (zero delta). The part counter + * restarts at 1, matching an exec'd child. + */ +static void clg_atfork_child(ThreadId tid) +{ + forking = False; + CLG_(reset_dump_counter)(); + /* the inherited edges are the parent's; this process only reports the + * children it spawns itself */ + CLG_(forget_spawned_children)(); + + /* the state file of the parent's PID belongs to the parent; advertise the + * inherited in-memory state under our own PID */ + CLG_(publish_instr_state)(CLG_(instrument_state)); + + CLG_(zero_all_cost)(False); + + /* The fork itself is a syscall in flight: its start time was stamped in the + * parent, but the thread CPU clock restarts in the child, so the delta + * computed at the syscall exit would underflow. */ + CLG_(restamp_syscall_time)(tid); +} + +void CLG_(syscall_return)(SysRes res) +{ + if (!forking) return; + forking = False; + + /* A failed fork spawned nothing. A zero result is the child returning from + * the fork, which cleared the flag in its atfork hook already. */ + if (sr_isError(res) || (Int)sr_Res(res) <= 0) return; + + record_spawned_child((Int)sr_Res(res)); +} + +void CLG_(init_subprocess)(void) +{ + VG_(atfork)(clg_atfork_pre, NULL, clg_atfork_child); +} + +/*--------------------------------------------------------------------*/ +/*--- end ---*/ +/*--------------------------------------------------------------------*/ diff --git a/coregrind/m_threadstate.c b/coregrind/m_threadstate.c index b596a7457..216ab41bd 100644 --- a/coregrind/m_threadstate.c +++ b/coregrind/m_threadstate.c @@ -117,7 +117,6 @@ ThreadId VG_(get_running_tid)(void) return VG_(running_tid); } -// This function is for tools to call. const HChar* VG_(get_thread_name)(ThreadId tid) { if (!VG_(is_valid_tid)(tid)) return NULL;