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/clo.c b/callgrind/clo.c index 90bd6c4ff..911051a27 100644 --- a/callgrind/clo.c +++ b/callgrind/clo.c @@ -429,7 +429,12 @@ Bool CLG_(process_cmd_line_option)(const HChar* arg) else if VG_BOOL_CLO(arg, "--collect-atstart", CLG_(clo).collect_atstart) {} - else if VG_BOOL_CLO(arg, "--instr-atstart", CLG_(clo).instrument_atstart) {} + else if VG_XACT_CLO(arg, "--instr-atstart=no", + CLG_(clo).instrument_atstart, instr_atstart_no) {} + else if VG_XACT_CLO(arg, "--instr-atstart=yes", + CLG_(clo).instrument_atstart, instr_atstart_yes) {} + else if VG_XACT_CLO(arg, "--instr-atstart=inherit", + CLG_(clo).instrument_atstart, instr_atstart_inherit) {} else if VG_BOOL_CLO(arg, "--separate-threads", CLG_(clo).separate_threads) {} @@ -604,7 +609,9 @@ void CLG_(print_usage)(void) #endif "\n data collection options:\n" -" --instr-atstart=no|yes Do instrumentation at callgrind start [yes]\n" +" --instr-atstart=no|yes|inherit Do instrumentation at callgrind start [yes]\n" +" inherit Like 'no', but inherit the instrumentation state across\n" +" 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" @@ -693,7 +700,7 @@ void CLG_(set_clo_defaults)(void) CLG_(clo).skip_direct_recursion = False; /* Instrumentation */ - CLG_(clo).instrument_atstart = True; + CLG_(clo).instrument_atstart = instr_atstart_yes; 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..1b41a4711 100644 --- a/callgrind/global.h +++ b/callgrind/global.h @@ -78,6 +78,17 @@ typedef enum { systime_nsec } Collect_Systime; +/* Whether to instrument from the start. + instr_atstart_no : start with instrumentation off + instr_atstart_yes : start with instrumentation on + instr_atstart_inherit : like "no", but adopt the state advertised by this + PID's pre-exec image, and advertise ours likewise */ +typedef enum { + instr_atstart_no, + instr_atstart_yes, + instr_atstart_inherit +} Instr_Atstart; + typedef struct _CommandLineOptions CommandLineOptions; struct _CommandLineOptions { @@ -113,7 +124,7 @@ struct _CommandLineOptions { Bool collect_bus; /* Collect global bus events */ /* Instrument options */ - Bool instrument_atstart; /* Instrument at start? */ + Instr_Atstart instrument_atstart; /* Instrument at start? */ 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 +741,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 +838,20 @@ 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); +/* Forward an object added via CALLGRIND_ADD_OBJ_SKIP to child valgrind + * processes, as if it had been passed on the command line. */ +void CLG_(publish_obj_skip)(const HChar* obj_name); + /*------------------------------------------------------------*/ /*--- Exported global variables ---*/ /*------------------------------------------------------------*/ diff --git a/callgrind/main.c b/callgrind/main.c index b777f55c0..44b9e6d3c 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"); @@ -1714,6 +1717,7 @@ Bool CLG_(handle_client_request)(ThreadId tid, UWord *args, UWord *ret) case VG_USERREQ__ADD_OBJ_SKIP: { const HChar* path = (const HChar*)args[1]; CLG_(add_obj_to_skip)(path); + CLG_(publish_obj_skip)(path); *ret = 0; break; } @@ -1779,12 +1783,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 +1828,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 +2073,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 +2110,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) { @@ -2165,7 +2189,15 @@ void CLG_(post_clo_init)(void) CLG_(init_threads)(); CLG_(run_thread)(1); - CLG_(instrument_state) = CLG_(clo).instrument_atstart; + switch (CLG_(clo).instrument_atstart) { + case instr_atstart_yes: CLG_(instrument_state) = True; break; + case instr_atstart_no: CLG_(instrument_state) = False; break; + case instr_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)(); + break; + } if (VG_(clo_verbosity) > 0) { VG_(message)(Vg_UserMsg, @@ -2212,6 +2244,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..b88b0c819 --- /dev/null +++ b/callgrind/subprocess.c @@ -0,0 +1,174 @@ +/* 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 != instr_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)); +} + +/*------------------------------------------------------------*/ +/*--- --obj-skip handover for objects added at runtime ---*/ +/*------------------------------------------------------------*/ + +/* CALLGRIND_ADD_OBJ_SKIP() adds an object to skip after startup, so unlike an + * --obj-skip on the original command line it is not something a traced exec + * would replay to the child. Appending it to VG_(args_for_valgrind) folds it + * into the same argv the core already reconstructs for exec'd children, so + * the child picks it up as an ordinary --obj-skip option. + */ +void CLG_(publish_obj_skip)(const HChar* obj_name) +{ + HChar* arg = VG_(malloc)("cl.subprocess.objskip.1", + VG_(strlen)(obj_name) + sizeof("--obj-skip=")); + VG_(sprintf)(arg, "--obj-skip=%s", obj_name); + VG_(addToXA)(VG_(args_for_valgrind), &arg); +} + +/*------------------------------------------------------------*/ +/*--- 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 ---*/ +/*--------------------------------------------------------------------*/