diff --git a/callgrind/dump.c b/callgrind/dump.c index 0aac17480..bf076f85a 100644 --- a/callgrind/dump.c +++ b/callgrind/dump.c @@ -59,6 +59,58 @@ Int CLG_(get_dump_counter)(void) return out_counter; } +/* Restart part numbering in a fork child (see clg_atfork_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; +} + +/* Children spawned by this process, each tagged with the dump part that was + * being measured when the fork happened. Emitted as "desc: Spawned pid:" + * lines in that part's header so the spawn tree can be rebuilt (see + * clg_atfork_parent). */ +typedef struct { Int part; Int pid; } SpawnedChild; +static SpawnedChild* spawned_children = 0; +static Int n_spawned = 0; +static Int spawned_capacity = 0; + +void CLG_(record_spawned_child)(Int part, Int child_pid) +{ + if (n_spawned == spawned_capacity) { + spawned_capacity = spawned_capacity ? spawned_capacity * 2 : 8; + spawned_children = VG_(realloc)("cl.dump.spawn.1", spawned_children, + spawned_capacity * sizeof(SpawnedChild)); + } + spawned_children[n_spawned].part = part; + spawned_children[n_spawned].pid = child_pid; + n_spawned++; +} + +/* A fork child inherits the parent's list but none of its spawns are its + * own; drop them so it only reports children it spawns itself. */ +void CLG_(forget_spawned_children)(void) +{ + n_spawned = 0; +} + +/* Drop the records already emitted for the given part. A spawn is tagged with + * the part active when the fork happened, so once that part is written no + * later part can reference it; keeping them would grow the list and rescan + * cost without bound in a fork-heavy run. */ +static void discard_spawned_children_of_part(Int part) +{ + Int kept = 0; + for (Int s = 0; s < n_spawned; s++) { + if (spawned_children[s].part != part) + spawned_children[kept++] = spawned_children[s]; + } + n_spawned = kept; +} + /*------------------------------------------------------------*/ /*--- Output file related stuff ---*/ /*------------------------------------------------------------*/ @@ -1373,6 +1425,16 @@ static VgFile *new_dumpfile(thread_info* ti, const HChar* trigger) } VG_(fprintf)(fp, "\npart: %d\n", out_counter); + + /* Children this process spawned while this part was being measured, so + * the spawn tree can be rebuilt: a child pid found here is attributed to + * this (pid, part). Per-part, not in the once-per-file header, so a child + * spawned during a later part is still recorded. "desc:" lines so other + * consumers of the format (kcachegrind, callgrind_annotate) ignore them. */ + for (int s = 0; s < n_spawned; s++) { + if (spawned_children[s].part == out_counter) + VG_(fprintf)(fp, "desc: Spawned pid: %d\n", spawned_children[s].pid); + } if (CLG_(clo).separate_threads) { const HChar* tname; @@ -1721,6 +1783,8 @@ static void print_bbccs(const HChar* trigger, Bool only_current_thread) CLG_(drop_retired_threads)(); } + discard_spawned_children_of_part(out_counter); + free_dump_array(); } diff --git a/callgrind/global.h b/callgrind/global.h index 78d1f70ca..b28016037 100644 --- a/callgrind/global.h +++ b/callgrind/global.h @@ -724,6 +724,9 @@ void CLG_(set_instrument_state)(const HChar*,Bool); void CLG_(dump_profile)(const HChar* trigger,Bool only_current_thread); void CLG_(zero_all_cost)(Bool only_current_thread); Int CLG_(get_dump_counter)(void); +void CLG_(reset_dump_counter)(void); +void CLG_(record_spawned_child)(Int part, Int child_pid); +void CLG_(forget_spawned_children)(void); void CLG_(fini)(Int exitcode); /* from bb.c */ diff --git a/callgrind/main.c b/callgrind/main.c index 810ec9d7f..b1bc5d050 100644 --- a/callgrind/main.c +++ b/callgrind/main.c @@ -2061,6 +2061,66 @@ void CLG_(fini)(Int exitcode) } +/*--------------------------------------------------------------------*/ +/*--- Subprocess tracking ---*/ +/*--------------------------------------------------------------------*/ + +/* Extra valgrind args passed to the child's valgrind when a traced process + * execs (--trace-children=yes), see VG_(needs_child_exec_args): the current + * instrumentation state, so a process spawned while instrumentation is + * enabled is instrumented from the start. A plain fork inherits the state + * with the address space; this forwards it across the exec, which otherwise + * resets it to --instr-atstart. + */ +static HChar clg_child_instr_arg[32]; +static const HChar* clg_child_exec_args[2]; + +static const HChar** clg_get_child_exec_args(void) +{ + Int n = 0; + VG_(sprintf)(clg_child_instr_arg, "--instr-atstart=%s", + CLG_(instrument_state) ? "yes" : "no"); + clg_child_exec_args[n++] = clg_child_instr_arg; + clg_child_exec_args[n] = NULL; + return clg_child_exec_args; +} + +/* Fork parent hook: this process just spawned child_pid. Record the edge + * against the part being measured right now (out_counter+1 is the part the + * next dump will get), so the dump attributes the child to that part. The + * spawn tree is rebuilt from these edges: each process names the children + * it created, and in which of its own parts. + */ +static void clg_atfork_parent(ThreadId tid, Int child_pid) +{ + CLG_(record_spawned_child)(CLG_(get_dump_counter)() + 1, child_pid); +} + +/* Fork child hook: 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) +{ + CLG_(reset_dump_counter)(); + CLG_(forget_spawned_children)(); + + CLG_(zero_all_cost)(False); + if (CLG_(clo).separate_threads) + CLG_(drop_retired_threads)(); + + /* 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. Re-stamp + * from the child's clocks. */ + if (CLG_(clo).collect_systime != systime_no) + collect_time(&syscalltime[tid], + CLG_(clo).collect_systime == systime_nsec + ? &syscallcputime[tid] : NULL); +} + /*--------------------------------------------------------------------*/ /*--- Setup ---*/ /*--------------------------------------------------------------------*/ @@ -2209,6 +2269,7 @@ void CLG_(pre_clo_init)(void) CLG_(print_debug_usage)); VG_(needs_client_requests)(CLG_(handle_client_request)); + VG_(needs_child_exec_args)(clg_get_child_exec_args); VG_(needs_print_stats) (clg_print_stats); VG_(track_start_client_code) ( & clg_start_client_code_callback ); @@ -2216,6 +2277,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) ); + VG_(atfork)(NULL, clg_atfork_parent, clg_atfork_child); + CLG_(set_clo_defaults)(); } diff --git a/coregrind/m_libcproc.c b/coregrind/m_libcproc.c index 7c4d6acc9..9e3ed36c5 100644 --- a/coregrind/m_libcproc.c +++ b/coregrind/m_libcproc.c @@ -1144,9 +1144,9 @@ UInt VG_(get_user_milliseconds)(void) ------------------------------------------------------------------ */ struct atfork { - vg_atfork_t pre; - vg_atfork_t parent; - vg_atfork_t child; + vg_atfork_t pre; + vg_atfork_parent_t parent; + vg_atfork_t child; }; #define VG_MAX_ATFORK 10 @@ -1154,7 +1154,7 @@ struct atfork { static struct atfork atforks[VG_MAX_ATFORK]; static Int n_atfork = 0; -void VG_(atfork)(vg_atfork_t pre, vg_atfork_t parent, vg_atfork_t child) +void VG_(atfork)(vg_atfork_t pre, vg_atfork_parent_t parent, vg_atfork_t child) { Int i; @@ -1185,13 +1185,13 @@ void VG_(do_atfork_pre)(ThreadId tid) (*atforks[i].pre)(tid); } -void VG_(do_atfork_parent)(ThreadId tid) +void VG_(do_atfork_parent)(ThreadId tid, Int child_pid) { Int i; for (i = 0; i < n_atfork; i++) if (atforks[i].parent != NULL) - (*atforks[i].parent)(tid); + (*atforks[i].parent)(tid, child_pid); } void VG_(do_atfork_child)(ThreadId tid) diff --git a/coregrind/m_syswrap/syswrap-darwin.c b/coregrind/m_syswrap/syswrap-darwin.c index de8c37fc3..de34d251b 100644 --- a/coregrind/m_syswrap/syswrap-darwin.c +++ b/coregrind/m_syswrap/syswrap-darwin.c @@ -3612,6 +3612,17 @@ PRE(posix_spawn) if (!trace_this_child) { argv = (HChar**)ARG4; } else { + // Extra valgrind args the tool wants to forward to the child's + // valgrind (e.g. current instrumentation state); appended after + // VG_(args_for_valgrind) so they override the original options. + const HChar** tool_args = NULL; + Int n_tool_args = 0; + if (VG_(needs).child_exec_args) { + tool_args = VG_(tdict).tool_child_exec_args(); + if (tool_args) + while (tool_args[n_tool_args]) + n_tool_args++; + } vg_assert( VG_(args_for_valgrind) ); vg_assert( VG_(args_for_valgrind_noexecpass) >= 0 ); vg_assert( VG_(args_for_valgrind_noexecpass) @@ -3622,6 +3633,8 @@ PRE(posix_spawn) // V's args tot_args += VG_(sizeXA)( VG_(args_for_valgrind) ); tot_args -= VG_(args_for_valgrind_noexecpass); + // tool-provided args for the child's valgrind + tot_args += n_tool_args; // name of client exe tot_args++; // args for client exe, skipping [0] @@ -3641,6 +3654,8 @@ PRE(posix_spawn) continue; argv[j++] = * (HChar**) VG_(indexXA)( VG_(args_for_valgrind), i ); } + for (i = 0; i < n_tool_args; i++) + argv[j++] = (HChar*)tool_args[i]; argv[j++] = (HChar*)ARG2; if (arg2copy && arg2copy[0]) for (i = 1; arg2copy[i]; i++) diff --git a/coregrind/m_syswrap/syswrap-freebsd.c b/coregrind/m_syswrap/syswrap-freebsd.c index 083263321..f6b1e059a 100644 --- a/coregrind/m_syswrap/syswrap-freebsd.c +++ b/coregrind/m_syswrap/syswrap-freebsd.c @@ -421,7 +421,7 @@ SysRes ML_(do_fork) ( ThreadId tid ) } else { /* parent */ - VG_(do_atfork_parent)(tid); + VG_(do_atfork_parent)(tid, (Int)sr_Res(res)); if (VG_(clo_trace_syscalls)) { VG_(printf)(" clone(fork): process %d created child %lu\n", @@ -5770,7 +5770,7 @@ PRE(sys_pdfork) /* restore signal mask */ VG_(sigprocmask)(VKI_SIG_SETMASK, &pdfork_saved_mask, NULL); } else { - VG_(do_atfork_parent)(tid); + VG_(do_atfork_parent)(tid, child_pid); PRINT(" fork: process %d created child %d\n", VG_(getpid)(), child_pid); diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 37f312fe8..d7476bba3 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -3547,9 +3547,20 @@ void handle_pre_sys_execve(ThreadId tid, SyscallStatus *status, Addr pathname, if (!trace_this_child) { argv = (HChar**)(Addr)arg_2; } else { + // Extra valgrind args the tool wants to forward to the child's + // valgrind (e.g. current instrumentation state); appended after + // VG_(args_for_valgrind) so they override the original options. + const HChar** tool_args = NULL; + Int n_tool_args = 0; + if (VG_(needs).child_exec_args) { + tool_args = VG_(tdict).tool_child_exec_args(); + if (tool_args) + while (tool_args[n_tool_args]) + n_tool_args++; + } vg_assert( VG_(args_for_valgrind) ); vg_assert( VG_(args_for_valgrind_noexecpass) >= 0 ); - vg_assert( VG_(args_for_valgrind_noexecpass) + vg_assert( VG_(args_for_valgrind_noexecpass) <= VG_(sizeXA)( VG_(args_for_valgrind) ) ); /* how many args in total will there be? */ // launcher basename @@ -3557,6 +3568,8 @@ void handle_pre_sys_execve(ThreadId tid, SyscallStatus *status, Addr pathname, // V's args tot_args += VG_(sizeXA)( VG_(args_for_valgrind) ); tot_args -= VG_(args_for_valgrind_noexecpass); + // tool-provided args for the child's valgrind + tot_args += n_tool_args; // name of client exe tot_args++; // args for client exe, skipping [0] @@ -3576,6 +3589,8 @@ void handle_pre_sys_execve(ThreadId tid, SyscallStatus *status, Addr pathname, continue; argv[j++] = * (HChar**) VG_(indexXA)( VG_(args_for_valgrind), i ); } + for (i = 0; i < n_tool_args; i++) + argv[j++] = (HChar*)tool_args[i]; argv[j++] = (HChar*)(Addr)pathname; if (arg2copy && arg2copy[0]) for (i = 1; arg2copy[i]; i++) @@ -3919,7 +3934,7 @@ PRE(sys_fork) /* restore signal mask */ VG_(sigprocmask)(VKI_SIG_SETMASK, &fork_saved_mask, NULL); } else { - VG_(do_atfork_parent)(tid); + VG_(do_atfork_parent)(tid, child_pid); PRINT(" fork: process %d created child %d\n", VG_(getpid)(), child_pid); diff --git a/coregrind/m_syswrap/syswrap-linux.c b/coregrind/m_syswrap/syswrap-linux.c index 3606bb9ad..b9b5b90fc 100644 --- a/coregrind/m_syswrap/syswrap-linux.c +++ b/coregrind/m_syswrap/syswrap-linux.c @@ -803,7 +803,7 @@ static SysRes ML_(do_fork_clone) ( ThreadId tid, UInt flags, else if (!sr_isError(res) && sr_Res(res) > 0) { /* parent */ - VG_(do_atfork_parent)(tid); + VG_(do_atfork_parent)(tid, (Int)sr_Res(res)); if (VG_(clo_trace_syscalls)) VG_(printf)(" clone(fork): process %d created child %" FMT_REGWORD "u\n", diff --git a/coregrind/m_syswrap/syswrap-solaris.c b/coregrind/m_syswrap/syswrap-solaris.c index 2665633f4..fa752f9cf 100644 --- a/coregrind/m_syswrap/syswrap-solaris.c +++ b/coregrind/m_syswrap/syswrap-solaris.c @@ -3800,6 +3800,18 @@ PRE(sys_execve) else { Int tot_args; + /* Extra valgrind args the tool wants to forward to the child's + valgrind (e.g. current instrumentation state); appended after + VG_(args_for_valgrind) so they override the original options. */ + const HChar** tool_args = NULL; + Int n_tool_args = 0; + if (VG_(needs).child_exec_args) { + tool_args = VG_(tdict).tool_child_exec_args(); + if (tool_args) + while (tool_args[n_tool_args]) + n_tool_args++; + } + vg_assert(VG_(args_for_valgrind)); vg_assert(VG_(args_for_valgrind_noexecpass) >= 0); vg_assert(VG_(args_for_valgrind_noexecpass) @@ -3811,6 +3823,8 @@ PRE(sys_execve) /* V's args */ tot_args += VG_(sizeXA)(VG_(args_for_valgrind)); tot_args -= VG_(args_for_valgrind_noexecpass); + /* tool-provided args for the child's valgrind */ + tot_args += n_tool_args; /* name of client exe */ tot_args++; /* args for client exe, skipping [0] */ @@ -3828,6 +3842,8 @@ PRE(sys_execve) continue; argv[j++] = *(HChar**)VG_(indexXA)(VG_(args_for_valgrind), i); } + for (i = 0; i < n_tool_args; i++) + argv[j++] = CONST_CAST(HChar *, tool_args[i]); argv[j++] = CONST_CAST(HChar *, fname); if (arg2copy[0] != NULL) for (i = 1; arg2copy[i]; i++) @@ -6636,7 +6652,7 @@ PRE(sys_forksys) # endif /* SOLARIS_PT_SUNDWTRACE_THRP */ } else { - VG_(do_atfork_parent)(tid); + VG_(do_atfork_parent)(tid, (Int)RES); /* Print information about the fork. */ PRINT(" fork: process %d created child %d\n", VG_(getpid)(), 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; diff --git a/coregrind/m_tooliface.c b/coregrind/m_tooliface.c index 1362cd142..59f1dd1b7 100644 --- a/coregrind/m_tooliface.c +++ b/coregrind/m_tooliface.c @@ -90,6 +90,7 @@ VgNeeds VG_(needs) = { .superblock_discards = False, .command_line_options = False, .client_requests = False, + .child_exec_args = False, .syscall_wrapper = False, .sanity_checks = False, .print_stats = False, @@ -301,6 +302,14 @@ void VG_(needs_client_requests)( VG_(tdict).tool_handle_client_request = wrap_tool_handle_client_request; } +void VG_(needs_child_exec_args)( + const HChar** (*get_child_exec_args)(void) +) +{ + VG_(needs).child_exec_args = True; + VG_(tdict).tool_child_exec_args = get_child_exec_args; +} + void VG_(needs_syscall_wrapper)( void(*pre) (ThreadId, UInt, UWord*, UInt), void(*post)(ThreadId, UInt, UWord*, UInt, SysRes res) diff --git a/coregrind/pub_core_libcproc.h b/coregrind/pub_core_libcproc.h index 9391898af..dcf11f316 100644 --- a/coregrind/pub_core_libcproc.h +++ b/coregrind/pub_core_libcproc.h @@ -82,7 +82,7 @@ extern Int VG_(ptrace)( Int request, Int pid, void *addr, void *data ); // atfork extern void VG_(do_atfork_pre) ( ThreadId tid ); -extern void VG_(do_atfork_parent) ( ThreadId tid ); +extern void VG_(do_atfork_parent) ( ThreadId tid, Int child_pid ); extern void VG_(do_atfork_child) ( ThreadId tid ); #if defined(VGO_freebsd) diff --git a/coregrind/pub_core_tooliface.h b/coregrind/pub_core_tooliface.h index 069e60360..800fa8a95 100644 --- a/coregrind/pub_core_tooliface.h +++ b/coregrind/pub_core_tooliface.h @@ -85,6 +85,7 @@ typedef Bool superblock_discards; Bool command_line_options; Bool client_requests; + Bool child_exec_args; Bool syscall_wrapper; Bool sanity_checks; Bool print_stats; @@ -142,6 +143,9 @@ typedef struct { // VG_(needs).client_requests Bool (*tool_handle_client_request)(ThreadId, UWord*, UWord*); + // VG_(needs).child_exec_args + const HChar** (*tool_child_exec_args)(void); + // VG_(needs).syscall_wrapper void (*tool_pre_syscall) (ThreadId, UInt, UWord*, UInt); void (*tool_post_syscall)(ThreadId, UInt, UWord*, UInt, SysRes); diff --git a/include/pub_tool_libcproc.h b/include/pub_tool_libcproc.h index c53285ec2..6e6b39c4d 100644 --- a/include/pub_tool_libcproc.h +++ b/include/pub_tool_libcproc.h @@ -122,7 +122,11 @@ extern UInt VG_(get_user_milliseconds)(void); ------------------------------------------------------------------ */ typedef void (*vg_atfork_t)(ThreadId); -extern void VG_(atfork)(vg_atfork_t pre, vg_atfork_t parent, vg_atfork_t child); +/* The parent hook additionally receives the pid of the just-created child, + which the other hooks have no use for. */ +typedef void (*vg_atfork_parent_t)(ThreadId, Int child_pid); +extern void VG_(atfork)(vg_atfork_t pre, vg_atfork_parent_t parent, + vg_atfork_t child); #if defined(VGO_freebsd) extern Int VG_(getosreldate)(void); diff --git a/include/pub_tool_tooliface.h b/include/pub_tool_tooliface.h index 01cefa6f0..32e95ba52 100644 --- a/include/pub_tool_tooliface.h +++ b/include/pub_tool_tooliface.h @@ -407,6 +407,20 @@ extern void VG_(needs_command_line_options) ( void (*print_debug_usage)(void) ); +/* Tool wants to pass extra valgrind command line arguments to the + valgrind of a traced child process at exec time? */ +extern void VG_(needs_child_exec_args) ( + // Called while building the argv of the valgrind that will trace a + // child process about to be exec'd (--trace-children=yes). Returns + // NULL, or a NULL-terminated array of extra arguments appended after + // VG_(args_for_valgrind). Since later arguments override earlier + // ones, this lets a tool forward runtime state (e.g. whether + // instrumentation is currently enabled) to child processes. The + // array and the strings must stay valid until the exec has happened; + // static storage is fine, the core does not free them. + const HChar** (*get_child_exec_args)(void) +); + /* Tool defines its own client requests? */ extern void VG_(needs_client_requests) ( // If using client requests, the number of the first request should be equal