[PATCH 16/31] dwarf_loader: Skip libdw__lock when elfutils is built thread-safe

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

pahole serializes dwarf_getlocation(), dwarf_decl_file(), and
__dwarf_getlocations() calls behind libdw__lock because libdw's
internal caches (tsearch trees) are not thread-safe by default.

This mutex was added in commits 1caed1c443d4 ("Add a lock around
dwarf_decl_file() and dwarf_decl_line() calls") and 65b7fd68ccbb
("Use libdw__lock for dwarf_getlocation(s)") to work around
concurrency bugs in libdw.

Since then, elfutils has fixed the underlying issues:
  - 0.194: eu_tsearch with proper locking (ec21fbb47)
  - 0.194: rwlock replaced with __atomic builtins (680eb3021)
  - 0.195: additional last_abbrev_offset protection (8fa2cd1d3)

When elfutils is compiled with --enable-thread-safety it defines
_ELFUTILS_THREAD_SAFE in <elfutils/version.h>.  Use this to skip
the pahole-side mutex entirely, eliminating the serialization
overhead.

Profiling shows pthread_rwlock_tryrdlock + unlock from libdw's
internal locking accounts for ~15.6% of serial encoding time.
That overhead also disappears when elfutils is built thread-safe
(rwlock replaced with lock-free atomics in 0.194).

Before (serial, elfutils without --enable-thread-safety):
  15.6% in pthread_rwlock_{tryrdlock,unlock} (libdw internal)
  Plus serialization from pahole's libdw__lock mutex

After (serial, elfutils with --enable-thread-safety):
  Both overheads eliminated

Cc: Alan Maguire <[email protected]>
Cc: Eduard Zingerman <[email protected]>
Cc: Aaron Merey <[email protected]>
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
 dwarf_loader.c | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/dwarf_loader.c b/dwarf_loader.c
index ce0bead0e76e1876..759883cad3fce693 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -57,7 +57,25 @@
 #define EM_RISCV	243
 #endif
 
+/*
+ * libdw internal caches (tsearch trees for locations, decl file/line, etc.)
+ * are not thread-safe unless elfutils was compiled with --enable-thread-safety.
+ * When that option is active, elfutils >= 0.194 uses eu_tsearch with proper
+ * locking and >= 0.194 also replaces the abbrev rwlock with __atomic builtins,
+ * eliminating the overhead that rwlock_tryrdlock imposed even in serial mode.
+ *
+ * _ELFUTILS_THREAD_SAFE is defined in <elfutils/version.h> when the library
+ * was built thread-safe.  When present we skip our own mutex; when absent we
+ * serialize the racy calls ourselves.
+ */
+#ifdef _ELFUTILS_THREAD_SAFE
+static inline void libdw__lock_lock(void)   { }
+static inline void libdw__lock_unlock(void) { }
+#else
 static pthread_mutex_t libdw__lock = PTHREAD_MUTEX_INITIALIZER;
+static inline void libdw__lock_lock(void)   { pthread_mutex_lock(&libdw__lock); }
+static inline void libdw__lock_unlock(void) { pthread_mutex_unlock(&libdw__lock); }
+#endif
 
 static uint32_t hashtags__bits = 12;
 static uint32_t max_hashtags__bits = 21;
@@ -453,11 +471,7 @@ static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen)
 	int ret = 1;
 
 	if (dwarf_attr(die, DW_AT_location, &attr) != NULL) {
-		/* use libdw__lock as dwarf_getlocation(s) has concurrency
-		 * issues when libdw is not compiled with experimental
-		 * --enable-thread-safety
-		 */
-		pthread_mutex_lock(&libdw__lock);
+		libdw__lock_lock();
 		if (dwarf_getlocation(&attr, expr, exprlen) == 0) {
 			/* DW_OP_addrx needs additional lookup for real addr. */
 			if (*exprlen != 0 && expr[0]->atom == DW_OP_addrx) {
@@ -471,7 +485,7 @@ static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen)
 			}
 			ret = 0;
 		}
-		pthread_mutex_unlock(&libdw__lock);
+		libdw__lock_unlock();
 	}
 
 	return ret;
@@ -516,7 +530,7 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
 	tag->attributes = NULL;
 
 	if (cu->extra_dbg_info) {
-		pthread_mutex_lock(&libdw__lock);
+		libdw__lock_lock();
 
 		int32_t decl_line;
 		const char *decl_file = dwarf_decl_file(die);
@@ -531,7 +545,7 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
 		dwarf_decl_line(die, &decl_line);
 		dtag->decl_line = decl_line;
 
-		pthread_mutex_unlock(&libdw__lock);
+		libdw__lock_unlock();
 	}
 
 	INIT_LIST_HEAD(&tag->node);
@@ -1470,7 +1484,7 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
 	ptrdiff_t offset = 0;
 	int loc_num = -1;
 
-	pthread_mutex_lock(&libdw__lock);
+	libdw__lock_lock();
 	while ((offset = __dwarf_getlocations(attr, offset, &base, &start, &end, &expr, &exprlen)) > 0) {
 		bool had_stack_value;
 
@@ -1515,7 +1529,7 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
 			break;
 		}
 	}
-	pthread_mutex_unlock(&libdw__lock);
+	libdw__lock_unlock();
 
 	parameter__finish_piece_decode(parm, die, conf, cu);
 }
-- 
2.55.0