[PATCH 17/31] dwarf_loader: Fix data race in tag__init() decl_file string cache

Arnaldo Carvalho de Melo <[email protected]> Wed, 29 Jul 2026 16:07:17 -0300
Newsgroups org.kernel.vger.dwarves,org.kernel.vger.bpf
Message-ID <[email protected]>
From: Arnaldo Carvalho de Melo <[email protected]>

The static variables last_decl_file and last_decl_file_ptr cache the
most recent dwarf_decl_file() result to avoid redundant strdup() calls
when consecutive DIEs share the same declaration file.

However, multiple worker threads call tag__init() concurrently, and
this cache is a pahole-internal optimization unrelated to libdw thread
safety. When _ELFUTILS_THREAD_SAFE makes libdw__lock a no-op, threads
race on these statics — one thread can see another's stale pointer,
skipping the strdup and assigning an incorrect (or freed) filename.

Make them __thread so each worker gets its own cache. The caching
still works well since DWARF CUs are processed per-thread and DIEs
within a CU tend to share the same source file:

  Before (__thread): j=1: 6.63s, j=4: 2.32s, j=32: 2.13s
  After  (__thread): same — no measurable overhead from TLS access

Fixes: a93160df5335ed2e ("dwarf_loader: Make dwarf_tag->decl_file a real string")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
 dwarf_loader.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/dwarf_loader.c b/dwarf_loader.c
index 759883cad3fce693..04dffae3504efa06 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -534,7 +534,14 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
 
 		int32_t decl_line;
 		const char *decl_file = dwarf_decl_file(die);
-		static const char *last_decl_file, *last_decl_file_ptr;
+		/*
+		 * Per-thread string dedup cache: avoids strdup() when
+		 * consecutive DIEs share the same decl_file pointer.
+		 * Must be __thread, not plain static, because multiple
+		 * worker threads call tag__init() concurrently and a
+		 * shared cache would race regardless of libdw locking.
+		 */
+		static __thread const char *last_decl_file, *last_decl_file_ptr;
 
 		if (decl_file != last_decl_file_ptr) {
 			last_decl_file = decl_file ? strdup(decl_file) : NULL;
-- 
2.55.0