From bb4fb76203e1de73c316814dd48b5158d055cb65 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 21 Jul 2026 11:18:58 +0200 Subject: [PATCH] fix(callgrind): represent os threads under --separate-threads=yes Make per-thread dumps behave like OS threads. The valgrind core recycles ThreadId slots and callgrind keyed all per-thread state on that slot, so a new OS thread reusing a slot silently inherited the dead thread's identity and costs. Mid-run client-request dumps also only flushed the calling thread, deferring every other thread's history to the termination dump. - Key thread identity (BBCC lookup, "thread:" header, dump suffix) on a monotonic serial instead of the recycled ThreadId slot. - Retire exiting threads via a pre_thread_ll_exit hook: unwind, snapshot their name, move them to a retired list so their costs stay attributed across the rest of the run. Gated on separate_threads. - Flush every thread, live and retired, at each dump so per-thread deltas land under the part being dumped, in ascending serial order. Threads with a zero delta are skipped; if that skips a whole part (metadata dumps with instrumentation off, termination), force one empty section so the part and its trigger metadata survive. Key the combined-dump header on file state, not out_counter, so it is written even when leading parts are skipped. - Zero every thread on CALLGRIND_ZERO_STATS under separate_threads. - Emit the core's thread name as a "desc: Thread name:" line, picked up by the backend in COD-3197. Refs COD-3196 Co-Authored-By: Claude --- callgrind/bbcc.c | 14 +-- callgrind/dump.c | 132 +++++++++++++++++++--- callgrind/global.h | 30 ++++- callgrind/main.c | 10 +- callgrind/threads.c | 194 +++++++++++++++++++++++++++++++++ coregrind/m_threadstate.c | 13 +++ include/pub_tool_threadstate.h | 11 ++ 7 files changed, 382 insertions(+), 22 deletions(-) diff --git a/callgrind/bbcc.c b/callgrind/bbcc.c index 9b08923d3..9e9e2ed52 100644 --- a/callgrind/bbcc.c +++ b/callgrind/bbcc.c @@ -165,7 +165,7 @@ BBCC* lookup_bbcc(BB* bb, Context* cxt) /* if we don't dump threads separate, tid doesn't have to match */ return bbcc; } - if (bbcc->tid == CLG_(current_tid)) return bbcc; + if (bbcc->tid == CLG_(current_thread_tid)()) return bbcc; } CLG_(stat).bbcc_lru_misses++; @@ -178,8 +178,8 @@ BBCC* lookup_bbcc(BB* bb, Context* cxt) bbcc = bbcc->next; } - CLG_DEBUG(2," lookup_bbcc(BB %#lx, Cxt %u, fn '%s'): %p (tid %u)\n", - bb_addr(bb), cxt->base_number, cxt->fn[0]->name, + CLG_DEBUG(2," lookup_bbcc(BB %#lx, Cxt %u, fn '%s'): %p (tid %d)\n", + bb_addr(bb), cxt->base_number, cxt->fn[0]->name, bbcc, bbcc ? bbcc->tid : 0); CLG_DEBUGIF(2) @@ -274,7 +274,7 @@ BBCC* new_bbcc(BB* bb) sizeof(BBCC) + (bb->cjmp_count+1) * sizeof(JmpData)); bbcc->bb = bb; - bbcc->tid = CLG_(current_tid); + bbcc->tid = CLG_(current_thread_tid)(); bbcc->ret_counter = 0; bbcc->skipped = 0; @@ -381,8 +381,8 @@ static BBCC* clone_bbcc(BBCC* orig, Context* cxt, Int rec_index) if (rec_index == 0) { - /* hash insertion is only allowed if tid or cxt is different */ - CLG_ASSERT((orig->tid != CLG_(current_tid)) || + /* hash insertion is only allowed if thread or cxt is different */ + CLG_ASSERT((orig->tid != CLG_(current_thread_tid)()) || (orig->cxt != cxt)); bbcc->rec_index = 0; @@ -394,7 +394,7 @@ static BBCC* clone_bbcc(BBCC* orig, Context* cxt, Int rec_index) } else { if (CLG_(clo).separate_threads) - CLG_ASSERT(orig->tid == CLG_(current_tid)); + CLG_ASSERT(orig->tid == CLG_(current_thread_tid)()); CLG_ASSERT(orig->cxt == cxt); CLG_ASSERT(orig->rec_array); diff --git a/callgrind/dump.c b/callgrind/dump.c index 3a3164c4b..0aac17480 100644 --- a/callgrind/dump.c +++ b/callgrind/dump.c @@ -37,6 +37,12 @@ static Int out_counter = 0; static HChar* out_file = 0; static Bool dumps_initialized = False; +/* With --combine-dumps, whether the single output file already carries its + * format header. A part can be skipped entirely (no thread had costs), so the + * first actual write may happen at out_counter > 1 yet still needs the header; + * hence tracked as file state, not derived from out_counter. */ +static Bool combined_header_written = False; + /* Command */ static HChar *cmdbuf; @@ -1290,12 +1296,16 @@ void file_err(void) * Create a new dump file and write header. * * Naming: .[.][-] - * is skipped for final dump (trigger==0) - * is skipped for thread 1 with CLG_(clo).separate_threads=no + * is skipped for final dump (trigger==0) + * is the thread's kernel id, skipped with + * CLG_(clo).separate_threads=no + * + * ti is the thread being dumped (used for its tid and name); its costs + * must already be installed as the current state. * * Returns the file descriptor, and -1 on error (no write permission) */ -static VgFile *new_dumpfile(int tid, const HChar* trigger) +static VgFile *new_dumpfile(thread_info* ti, const HChar* trigger) { Bool appending = False; int i; @@ -1305,6 +1315,10 @@ static VgFile *new_dumpfile(int tid, const HChar* trigger) CLG_ASSERT(dumps_initialized); CLG_ASSERT(filename != 0); + /* A thread that only ran a handful of blocks may not have had its kernel + * id resolved yet; make sure the suffix and header below carry it. */ + CLG_(resolve_thread_tid)(ti); + if (!CLG_(clo).combine_dumps) { i = VG_(sprintf)(filename, "%s", out_file); @@ -1312,14 +1326,14 @@ static VgFile *new_dumpfile(int tid, const HChar* trigger) i += VG_(sprintf)(filename+i, ".%d", out_counter); if (CLG_(clo).separate_threads) - VG_(sprintf)(filename+i, "-%02d", tid); + VG_(sprintf)(filename+i, "-%02d", ti->tid); fp = VG_(fopen)(filename, VKI_O_WRONLY|VKI_O_TRUNC, 0); } else { VG_(sprintf)(filename, "%s", out_file); fp = VG_(fopen)(filename, VKI_O_WRONLY|VKI_O_APPEND, 0); - if (fp && out_counter>1) + if (fp && combined_header_written) appending = True; } @@ -1354,11 +1368,22 @@ static VgFile *new_dumpfile(int tid, const HChar* trigger) /* "cmd:" line */ VG_(fprintf)(fp, "cmd: %s", cmdbuf); + + combined_header_written = True; } VG_(fprintf)(fp, "\npart: %d\n", out_counter); if (CLG_(clo).separate_threads) { - VG_(fprintf)(fp, "thread: %d\n", tid); + const HChar* tname; + + VG_(fprintf)(fp, "thread: %d\n", ti->tid); + + /* Retired threads carry a name snapshot; live threads are read from + * the core. */ + tname = ti->name ? ti->name + : VG_(get_thread_name)(CLG_(current_tid)); + if (tname && tname[0]) + VG_(fprintf)(fp, "desc: Thread name: %s\n", tname); } /* "desc:" lines */ @@ -1456,7 +1481,6 @@ static VgFile *new_dumpfile(int tid, const HChar* trigger) sum = CLG_(get_eventset_cost)( CLG_(sets).full ); CLG_(zero_cost)(CLG_(sets).full, sum); if (CLG_(clo).separate_threads) { - thread_info* ti = CLG_(get_current_thread)(); CLG_(add_diff_cost)(CLG_(sets).full, sum, ti->lastdump_cost, ti->states.entry[0]->cost); } @@ -1514,21 +1538,55 @@ static void close_dumpfile(VgFile *fp) static const HChar* print_trigger; +/* Whether any thread section was written for the part being dumped, and + * whether to bypass the zero-delta skip (used to force an empty section so + * the part still appears in the output). */ +static Bool part_section_written; +static Bool force_empty_section; + static void print_bbccs_of_thread(thread_info* ti) { BBCC **p, **array; FnPos lastFnPos; AddrPos lastAPos; + VgFile *print_fp; CLG_DEBUG(1, "+ print_bbccs(tid %u)\n", CLG_(current_tid)); - VgFile *print_fp = new_dumpfile(CLG_(current_tid), print_trigger); + p = array = prepare_dump(); + + /* With per-thread dumps, skip a thread that did nothing since the last dump + * (no BBCCs and a zero exec-state delta) so parts don't grow empty sections. + * A thread with never-dumped cost still has a nonzero delta (its + * lastdump_cost baseline is zero), so this never drops real data. */ + if (CLG_(clo).separate_threads && !force_empty_section && array[0] == 0) { + /* Compared element-wise rather than via a scratch cost buffer: those are + * pool-allocated and must not be individually freed, and add_diff_cost + * would clobber lastdump_cost, which the real dump updates below. */ + EventSet* es = CLG_(sets).full; + ULong* last = ti->lastdump_cost; + ULong* cur = ti->states.entry[0]->cost; + Bool empty = True; + Int i; + for(i = 0; i < es->size; i++) + if (cur[i] != last[i]) { empty = False; break; } + if (empty) { + CLG_DEBUG(1, "- print_bbccs(tid %u): empty delta, skipped\n", + CLG_(current_tid)); + VG_(free)(array); + return; + } + } + + print_fp = new_dumpfile(ti, print_trigger); if (print_fp == NULL) { CLG_DEBUG(1, "- print_bbccs(tid %u): No output...\n", CLG_(current_tid)); + VG_(free)(array); return; } + part_section_written = True; - p = array = prepare_dump(); + p = array; init_fpos(&lastFnPos); init_apos(&lastAPos, 0, 0, 0); @@ -1596,6 +1654,27 @@ static void print_bbccs_of_thread(thread_info* ti) } +/* Dump a retired thread, whose state is not reachable via switch_thread. + * Install its containers and (already-unwound) stacks as the current state + * so the shared dumping machinery operates on it, then dump. */ +static void print_bbccs_of_retired_thread(thread_info* ti, Bool retired) +{ + if (!retired) { + print_bbccs_of_thread(ti); + return; + } + + CLG_(set_current_exec_stack)( &(ti->states) ); + CLG_(set_current_call_stack)( &(ti->calls) ); + CLG_(set_current_fn_stack) ( &(ti->fns) ); + CLG_(set_current_fn_array) ( &(ti->fn_active) ); + CLG_(set_current_bbcc_hash) ( &(ti->bbccs) ); + CLG_(set_current_jcc_hash) ( &(ti->jccs) ); + CLG_(current_state).cost = ti->states.entry[0]->cost; + + print_bbccs_of_thread(ti); +} + static void print_bbccs(const HChar* trigger, Bool only_current_thread) { init_dump_array(); @@ -1611,10 +1690,36 @@ static void print_bbccs(const HChar* trigger, Bool only_current_thread) print_bbccs_of_thread( CLG_(get_current_thread)() ); CLG_(switch_thread)(orig_tid); } - else if (only_current_thread) - print_bbccs_of_thread( CLG_(get_current_thread)() ); - else - CLG_(forall_threads)(print_bbccs_of_thread); + else { + /* Per-thread dumps flush every thread, live and retired, so each thread's + * delta lands under the part being dumped -- otherwise threads other than + * the caller only ever get written by the termination dump. The + * only_current_thread hint is therefore ignored in this mode. Retired + * threads are freed once flushed. */ + (void) only_current_thread; + part_section_written = False; + CLG_(forall_threads_incl_retired)(print_bbccs_of_retired_thread); + if (!part_section_written) { + /* Every thread was skipped as zero-delta, but the part header itself + * carries metadata (trigger, timerange) that consumers rely on: force + * one empty section so the part still appears. Use the triggering + * thread; at termination it may already be retired. */ + thread_info* ti = 0; + Bool retired = False; + if (CLG_(current_tid) != VG_INVALID_THREADID) + ti = CLG_(get_current_thread)(); + if (!ti) { + ti = CLG_(newest_retired_thread)(); + retired = (ti != 0); + } + if (ti) { + force_empty_section = True; + print_bbccs_of_retired_thread(ti, retired); + force_empty_section = False; + } + } + CLG_(drop_retired_threads)(); + } free_dump_array(); } @@ -1755,6 +1860,7 @@ void CLG_(init_dumps)(void) } } if (!sr_isError(res)) VG_(close)( (Int)sr_Res(res) ); + combined_header_written = False; if (!dumps_initialized) init_cmdbuf(); diff --git a/callgrind/global.h b/callgrind/global.h index 10d4c53b1..78d1f70ca 100644 --- a/callgrind/global.h +++ b/callgrind/global.h @@ -380,7 +380,9 @@ struct _BBCC { BB* bb; /* BB for this cost center */ Context* cxt; /* execution context of this BBCC */ - ThreadId tid; /* only for assertion check purpose */ + Int tid; /* owning thread's kernel id (LWP id); identifies + * the thread across recycled valgrind ThreadId + * slots. Only for lookup/assertion purposes. */ UInt rec_index; /* Recursion index in rec->bbcc for this bbcc */ BBCC** rec_array; /* Variable sized array of pointers to * recursion BBCCs. Shared. */ @@ -588,6 +590,17 @@ struct _exec_stack { */ struct _thread_info { + /* Kernel thread id (LWP id), snapshotted at creation. Stable identity + * independent of the recycled valgrind ThreadId slot: used for the + * "thread:" dump header and the "-NN" dump file suffix so a new OS thread + * reusing a slot is never conflated with a dead one. */ + Int tid; + /* Name the thread gave itself, snapshotted at retirement (NULL if none); + * for live threads the current name is read from the core at dump time. */ + HChar* name; + /* Link for the retired-thread list; unused while the thread is live. */ + struct _thread_info* retired_next; + /* state */ fn_stack fns; /* function stack */ call_stack calls; /* context call arc stack */ @@ -783,9 +796,24 @@ void CLG_(push_cxt)(fn_node* fn); void CLG_(init_threads)(void); thread_info** CLG_(get_threads)(void); thread_info* CLG_(get_current_thread)(void); +/* Kernel thread id (LWP id) of the current thread; identifies a thread across + * recycled valgrind ThreadId slots. */ +Int CLG_(current_thread_tid)(void); +/* Resolve (and cache) a thread's kernel id from the core, filling it in the + * first time it is available. Returns 0 for a NULL thread. */ +Int CLG_(resolve_thread_tid)(thread_info* t); void CLG_(switch_thread)(ThreadId tid); void CLG_(forall_threads)(void (*func)(thread_info*)); +/* Like forall_threads, but also visits retired (exited) threads, in + * ascending kernel-tid order across both groups. The retired argument is + * True for threads from the retired list. */ +void CLG_(forall_threads_incl_retired)(void (*func)(thread_info*, Bool retired)); +/* Drop all retired threads, freeing their state. */ +void CLG_(drop_retired_threads)(void); +/* Most recently retired thread, or NULL if none are pending. */ +thread_info* CLG_(newest_retired_thread)(void); void CLG_(run_thread)(ThreadId tid); +void CLG_(pre_thread_ll_exit)(ThreadId tid); void CLG_(init_exec_state)(exec_state* es); void CLG_(init_exec_stack)(exec_stack*); diff --git a/callgrind/main.c b/callgrind/main.c index b0f910e67..810ec9d7f 100644 --- a/callgrind/main.c +++ b/callgrind/main.c @@ -1686,7 +1686,14 @@ Bool CLG_(handle_client_request)(ThreadId tid, UWord *args, UWord *ret) break; case VG_USERREQ__ZERO_STATS: - CLG_(zero_all_cost)(True); + /* Zero every thread under per-thread dumps so part boundaries stay + * consistent across threads; otherwise only the caller is zeroed. */ + CLG_(zero_all_cost)(!CLG_(clo).separate_threads); + /* Live threads are reset above, but an already-exited thread sits on the + * retired list untouched, and all of its cost predates this zero. Drop it + * so a later dump can't carry that pre-zero cost past the boundary. */ + if (CLG_(clo).separate_threads) + CLG_(drop_retired_threads)(); *ret = 0; /* meaningless */ break; @@ -2207,6 +2214,7 @@ void CLG_(pre_clo_init)(void) VG_(track_start_client_code) ( & clg_start_client_code_callback ); VG_(track_pre_deliver_signal) ( & CLG_(pre_signal) ); VG_(track_post_deliver_signal)( & CLG_(post_signal) ); + VG_(track_pre_thread_ll_exit) ( & CLG_(pre_thread_ll_exit) ); CLG_(set_clo_defaults)(); diff --git a/callgrind/threads.c b/callgrind/threads.c index 4ef3db766..4c85eef8f 100644 --- a/callgrind/threads.c +++ b/callgrind/threads.c @@ -61,6 +61,10 @@ ThreadId CLG_(current_tid); static thread_info** thread; +/* Threads that have exited but whose costs have not yet been flushed to a + * dump. Singly-linked via the retired_next field, newest first. */ +static thread_info* retired_threads = 0; + thread_info** CLG_(get_threads)(void) { return thread; @@ -71,6 +75,23 @@ thread_info* CLG_(get_current_thread)(void) return thread[CLG_(current_tid)]; } +/* Fill in a thread's kernel id from the core once it is available, caching it. + * The core assigns an LWP id only once the thread actually runs client code, + * which can be after the thread_info was created, so we resolve on first use + * rather than at creation. VG_(get_running_tid)() is the ThreadId valid in + * every context this is called from. */ +Int CLG_(resolve_thread_tid)(thread_info* t) +{ + if (t && t->tid == 0) + t->tid = VG_(get_thread_lwpid)( VG_(get_running_tid)() ); + return t ? t->tid : 0; +} + +Int CLG_(current_thread_tid)(void) +{ + return CLG_(resolve_thread_tid)( thread[CLG_(current_tid)] ); +} + void CLG_(init_threads)(void) { UInt i; @@ -95,6 +116,172 @@ void CLG_(forall_threads)(void (*func)(thread_info*)) CLG_(switch_thread)(orig_tid); } +/* Like forall_threads, but also visits the retired ones, in ascending + * kernel-tid order across both groups. Retired threads are no + * longer schedulable, so we do not switch to them; their state has already + * been folded into their containers at exit. + */ +void CLG_(forall_threads_incl_retired)(void (*func)(thread_info*, Bool retired)) +{ + typedef struct { thread_info* ti; Int tid; } visit; /* tid 0 = retired */ + Int t, n, i, j, orig_tid = CLG_(current_tid); + thread_info* r; + visit *order, v; + + n = 0; + for(t=1;tretired_next) n++; + if (n == 0) return; + + order = CLG_MALLOC("cl.threads.fatir.1", n * sizeof *order); + n = 0; + for(t=1;tretired_next) { + order[n].ti = r; + order[n].tid = 0; + n++; + } + + /* Insertion sort by kernel tid. A thread that exited or ran client code has + * a resolved, process-unique tid; a live thread that never ran client code + * still has tid 0, so several entries can share the key. Break such ties by + * the ThreadId slot (0 for retired) to keep the order total and stable. */ + for(i = 1; i < n; i++) { + v = order[i]; + for(j = i; + j > 0 && (order[j-1].ti->tid > v.ti->tid || + (order[j-1].ti->tid == v.ti->tid && + order[j-1].tid > v.tid)); + j--) + order[j] = order[j-1]; + order[j] = v; + } + + for(i = 0; i < n; i++) { + if (order[i].tid) { + CLG_(switch_thread)(order[i].tid); + (*func)(order[i].ti, False); + } + else { + /* The callback installs the retired thread's containers/stacks as the + * current state, so first detach any live thread by switching away: + * that saves its (possibly unsaved) running state into its slot. A + * later switch to a live thread reloads cleanly, discarding the + * retired state left in the globals. */ + CLG_(switch_thread)(VG_INVALID_THREADID); + (*func)(order[i].ti, True); + } + } + + CLG_(switch_thread)(orig_tid); + VG_(free)(order); +} + +static void free_thread(thread_info* t) +{ + /* BBCC/JCC nodes are reachable from their BBs and are never freed (as at + * program termination). The cost buffers (lastdump_cost, sighandler_cost) + * are slices of the shared cost-array pool and must not be freed + * individually. So we reclaim only the hash bucket arrays and the active-fn + * array, which this thread_info uniquely owns via CLG_MALLOC. */ + if (t->bbccs.table) VG_(free)(t->bbccs.table); + if (t->jccs.table) VG_(free)(t->jccs.table); + if (t->fn_active.array) VG_(free)(t->fn_active.array); + if (t->name) VG_(free)(t->name); + VG_(free)(t); +} + +thread_info* CLG_(newest_retired_thread)(void) +{ + return retired_threads; +} + +void CLG_(drop_retired_threads)(void) +{ + thread_info *r = retired_threads, *next; + + while(r) { + next = r->retired_next; + free_thread(r); + r = next; + } + retired_threads = 0; +} + +/* Fold a thread's still-pending costs into its cost containers, mirroring the + * unwinding done at program termination. Operates on the current thread's + * state, so the caller must have switched to it first. Safe to call on an + * already-unwound/empty state (e.g. with instrumentation off): it only + * unwinds what is actually there. */ +static void unwind_current_thread(void) +{ + while(CLG_(current_state).sig != 0) + CLG_(post_signal)(CLG_(current_tid), CLG_(current_state).sig); + + while(CLG_(current_call_stack).sp > 0) + CLG_(pop_call_stack)(); + + CLG_(init_exec_state)( &CLG_(current_state) ); + CLG_(current_fn_stack).top = CLG_(current_fn_stack).bottom; +} + +void CLG_(pre_thread_ll_exit)(ThreadId tid) +{ + thread_info* t; + const HChar* name; + + CLG_ASSERT(tid < VG_N_THREADS); + + /* Costs of all threads are cumulated into thread 1 unless kept separate, so + * there is no per-thread identity to preserve in that mode: leave the slot + * as-is, exactly as before. */ + if (!CLG_(clo).separate_threads) return; + + /* Nothing to retire if this thread never executed instrumented code. */ + t = thread[tid]; + if (t == 0) return; + + CLG_DEBUG(0, ">> thread %u (kernel tid %d) exiting\n", tid, t->tid); + + /* Load the exiting thread's state so its pending costs settle into its own + * containers before we detach it from the live slot. This is also the last + * moment the exiting thread is the running one, so resolve its kernel tid + * now: once retired it can only be resolved against a different running + * thread, which would stamp it with the wrong id. */ + CLG_(switch_thread)(tid); + CLG_(resolve_thread_tid)(t); + unwind_current_thread(); + CLG_(copy_current_exec_stack)( &(t->states) ); + CLG_(copy_current_call_stack)( &(t->calls) ); + CLG_(copy_current_fn_stack) ( &(t->fns) ); + CLG_(copy_current_fn_array) ( &(t->fn_active) ); + CLG_(copy_current_bbcc_hash) ( &(t->bbccs) ); + CLG_(copy_current_jcc_hash) ( &(t->jccs) ); + + /* Snapshot the name while the core's ThreadState is still alive. */ + name = VG_(get_thread_name)(tid); + if (name && name[0]) { + SizeT len = VG_(strlen)(name) + 1; + t->name = CLG_MALLOC("cl.threads.name", len); + VG_(strlcpy)(t->name, name, len); + } + + /* Detach from the live slot and stash on the retired list, newest first. */ + thread[tid] = 0; + t->retired_next = retired_threads; + retired_threads = t; + + /* The slot is now free for a future OS thread. Force the next switch to + * reload cleanly rather than trusting the stale current_tid. */ + CLG_(current_tid) = VG_INVALID_THREADID; +} + static thread_info* new_thread(void) @@ -104,6 +291,13 @@ thread_info* new_thread(void) t = (thread_info*) CLG_MALLOC("cl.threads.nt.1", sizeof(thread_info)); + /* The kernel id is resolved lazily (see resolve_thread_tid): on the first + * switch to the initial thread the core may not have assigned its LWP id + * yet, so snapshotting here would freeze a 0. */ + t->tid = 0; + t->name = NULL; + t->retired_next = NULL; + /* init state */ CLG_(init_exec_stack)( &(t->states) ); CLG_(init_call_stack)( &(t->calls) ); diff --git a/coregrind/m_threadstate.c b/coregrind/m_threadstate.c index 9b303bd14..b596a7457 100644 --- a/coregrind/m_threadstate.c +++ b/coregrind/m_threadstate.c @@ -117,6 +117,19 @@ 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; + return VG_(threads)[tid].thread_name; +} + +Int VG_(get_thread_lwpid)(ThreadId tid) +{ + if (!VG_(is_valid_tid)(tid)) return 0; + return VG_(threads)[tid].os_state.lwpid; +} + Bool VG_(is_running_thread)(ThreadId tid) { ThreadState *tst = VG_(get_ThreadState)(tid); diff --git a/include/pub_tool_threadstate.h b/include/pub_tool_threadstate.h index 437e488d4..7232ee0c4 100644 --- a/include/pub_tool_threadstate.h +++ b/include/pub_tool_threadstate.h @@ -42,6 +42,17 @@ extern UInt VG_N_THREADS; /* Get the TID of the thread which currently has the CPU. */ extern ThreadId VG_(get_running_tid) ( void ); +/* Get the name a thread set for itself (e.g. via prctl(PR_SET_NAME) or + pthread_setname_np on the calling thread), or NULL if it has none. The + returned pointer is owned by the core and only valid while the thread + is alive. */ +extern const HChar* VG_(get_thread_name) ( ThreadId tid ); + +/* Get the kernel thread id (LWP id) of a thread, or 0 if tid is not a valid + live thread. Unlike ThreadId, this is the OS-level identity the rest of the + system sees. */ +extern Int VG_(get_thread_lwpid) ( ThreadId tid ); + #endif // __PUB_TOOL_THREADSTATE_H /*--------------------------------------------------------------------*/