From da9f1578aefccb48e4b0e5c524eeea8ef65115e8 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Wed, 22 Jul 2026 16:47:02 +0200 Subject: [PATCH 1/2] feat(callgrind): forward instrumentation state across traced exec A process spawned via exec under --trace-children=yes gets a fresh valgrind whose instrumentation state resets to --instr-atstart, so a benchmark reached through an exec chain (e.g. `cargo run`) was measured from the wrong point. A plain fork inherits the state with the address space, but an exec does not. Add a VG_(needs_child_exec_args) tool need: the core asks the tool for extra valgrind arguments while building the child's argv at exec time and appends them after VG_(args_for_valgrind), where later options override earlier ones. Callgrind uses it to forward --instr-atstart= so the exec'd image is instrumented from its first instruction. Wired into the exec argv construction on Linux (generic), Darwin, and Solaris. Closes COD-2349 Co-Authored-By: Claude --- callgrind/main.c | 25 +++++++++++++++++++++++++ coregrind/m_syswrap/syswrap-darwin.c | 15 +++++++++++++++ coregrind/m_syswrap/syswrap-generic.c | 17 ++++++++++++++++- coregrind/m_syswrap/syswrap-solaris.c | 16 ++++++++++++++++ coregrind/m_tooliface.c | 9 +++++++++ coregrind/pub_core_tooliface.h | 4 ++++ include/pub_tool_tooliface.h | 14 ++++++++++++++ 7 files changed, 99 insertions(+), 1 deletion(-) diff --git a/callgrind/main.c b/callgrind/main.c index 810ec9d7f..f4024182c 100644 --- a/callgrind/main.c +++ b/callgrind/main.c @@ -2061,6 +2061,30 @@ 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; +} + /*--------------------------------------------------------------------*/ /*--- Setup ---*/ /*--------------------------------------------------------------------*/ @@ -2209,6 +2233,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 ); 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-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 37f312fe8..578c5fb8c 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++) diff --git a/coregrind/m_syswrap/syswrap-solaris.c b/coregrind/m_syswrap/syswrap-solaris.c index 2665633f4..c2dd8fb12 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++) 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_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_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 From 002990c5718ccad38228103f98f754ac8942d733 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Wed, 22 Jul 2026 16:47:38 +0200 Subject: [PATCH 2/2] feat(callgrind): record spawned subprocesses per dump part When a benchmark spawns a subprocess, the resulting profile had no way to attribute that subprocess back to the exact benchmark (dump part) that spawned it, so the spawn tree could not be rebuilt. Track fork edges and emit them in the dump: on fork the parent records the new child pid against the part currently being measured, and each part's header lists the children spawned during it as "desc: Spawned pid:" lines (a desc field so kcachegrind and callgrind_annotate ignore it). A fork child restarts its part counter at 1 and drops the inherited edges so it only reports children it spawns itself, and zeroes cost so work before the fork stays attributed to the parent. To carry the child pid to the fork hook, VG_(atfork)'s parent callback now takes the pid of the just-created child; the pre/child callbacks are unchanged. All fork/clone sites (Linux, FreeBSD, Solaris, generic) pass it through. The child hook also re-stamps the in-flight fork syscall's start time from the child's clocks, since the thread CPU clock restarts in the child and the exit delta would otherwise underflow. Closes COD-2349 Co-Authored-By: Claude --- callgrind/dump.c | 64 +++++++++++++++++++++++++++ callgrind/global.h | 3 ++ callgrind/main.c | 38 ++++++++++++++++ coregrind/m_libcproc.c | 12 ++--- coregrind/m_syswrap/syswrap-freebsd.c | 4 +- coregrind/m_syswrap/syswrap-generic.c | 2 +- coregrind/m_syswrap/syswrap-linux.c | 2 +- coregrind/m_syswrap/syswrap-solaris.c | 2 +- coregrind/m_threadstate.c | 1 - coregrind/pub_core_libcproc.h | 2 +- include/pub_tool_libcproc.h | 6 ++- 11 files changed, 122 insertions(+), 14 deletions(-) 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 f4024182c..b1bc5d050 100644 --- a/callgrind/main.c +++ b/callgrind/main.c @@ -2085,6 +2085,42 @@ static const HChar** clg_get_child_exec_args(void) 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 ---*/ /*--------------------------------------------------------------------*/ @@ -2241,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-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 578c5fb8c..d7476bba3 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -3934,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 c2dd8fb12..fa752f9cf 100644 --- a/coregrind/m_syswrap/syswrap-solaris.c +++ b/coregrind/m_syswrap/syswrap-solaris.c @@ -6652,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/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/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);