https://github.com/mchoo7 created 
https://github.com/llvm/llvm-project/pull/204710

Commit 67e571d (#179306) added lldbHost and lldbUtility to 
`LLDB_DRIVER_LINK_LIBS` A side-effect is that HostInfoBase.cpp, which contains 
the file-static `g_fields` pointer, is now compiled into both the lldb binary 
and liblldb.so, giving each its own independent `g_fields`.

On ELF platforms this creates an interposition hazard. When 
`LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS` is set, AddLLDB.cmake switches all 
LLDB libraries to `CXX_VISIBILITY_PRESET=default` so that the version script 
can re-export private symbols needed by dynamically loaded plugins. The Python 
plugin calls `HostInfo::GetShlibDir()` directly, so 
extract-dynamic-script-interpreter-exports.py adds `HostInfoBase::GetShlibDir` 
to liblldb.so's exports (global: in the version script). 
`HostInfoBase::Initialize()` is not called by the plugin and stays local:.

At runtime the dynamic linker resolves liblldb.so's PLT entry for 
`GetShlibDir()` to lldb's copy (ELF interposition: executable symbols win), 
which holds a never-initialized `g_fields == nullptr`. `Initialize()` was 
already called, but it ran on liblldb.so's own copy via the 
version-script-local binding. Dereferencing `nullptr + 0x108` (offset of 
`m_lldb_so_dir_once`) crashes in `pthread_once`.

To fix this, pass `--exclude-libs,ALL` to the lldb executable on ELF platforms. 
This keeps every archive-contributed symbol (lldbHost, lldbUtility, LLVM libs) 
out of lldb's `.dynsym`, eliminating the interposition. liblldb.so's PLT for 
`GetShlibDir()` then finds only liblldb.so's own exported definition, so 
`Initialize()` and `GetShlibDir()` consistently operate on the same `g_fields`.

The issue does not affect Darwin because Mach-O uses a two-level namespace: 
symbol references in liblldb.dylib are bound to a specific library at link 
time, so lldb's copy is never a candidate for resolution regardless of 
visibility.

Closes: #204643
Assisted-by: Claude

>From 2740701f2a529f1294e68d35f7bc472c1dc543e9 Mon Sep 17 00:00:00 2001
From: Minsoo Choo <[email protected]>
Date: Thu, 18 Jun 2026 20:34:01 -0400
Subject: [PATCH] [lldb][driver] Fix ELF interposition of HostInfoBase symbols
 causing segfault

Commit 67e571d (#179306) added lldbHost and lldbUtility to
LLDB_DRIVER_LINK_LIBS A side-effect is that HostInfoBase.cpp -- which contains
the file-static g_fields pointer -- is now compiled into both the lldb
binary and liblldb.so, giving each its own independent g_fields.

On ELF platforms this creates an interposition hazard. When
LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS is set, AddLLDB.cmake switches
all LLDB libraries to CXX_VISIBILITY_PRESET=default so that the
version script can re-export private symbols needed by dynamically
loaded plugins. The Python plugin calls HostInfo::GetShlibDir()
directly, so extract-dynamic-script-interpreter-exports.py adds
HostInfoBase::GetShlibDir to liblldb.so's exports (global: in the
version script). HostInfoBase::Initialize() is not called by the
plugin and stays local:.

At runtime the dynamic linker resolves liblldb.so's PLT entry for
GetShlibDir() to lldb's copy (ELF interposition: executable symbols
win), which holds a never-initialized g_fields == nullptr. Initialize()
was already called, but it ran on liblldb.so's own copy via the
version-script-local binding. Dereferencing nullptr + 0x108
(offsetof m_lldb_so_dir_once) crashes in pthread_once.

To fix this, pass --exclude-libs,ALL to the lldb executable on ELF platforms.
This keeps every archive-contributed symbol (lldbHost, lldbUtility,
LLVM libs) out of lldb's .dynsym, eliminating the interposition.
liblldb.so's PLT for GetShlibDir() then finds only liblldb.so's own
exported definition, so Initialize() and GetShlibDir() consistently
operate on the same g_fields.

The issue does not affect Darwin because Mach-O uses a two-level
namespace: symbol references in liblldb.dylib are bound to a specific
library at link time, so lldb's copy is never a candidate for
resolution regardless of visibility.

Closes: #204643
Assisted-by: Claude
Signed-off-by: Minsoo Choo <[email protected]>
---
 lldb/tools/driver/CMakeLists.txt | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lldb/tools/driver/CMakeLists.txt b/lldb/tools/driver/CMakeLists.txt
index 0534ddc9a58c3..38670a14a78b0 100644
--- a/lldb/tools/driver/CMakeLists.txt
+++ b/lldb/tools/driver/CMakeLists.txt
@@ -52,6 +52,17 @@ add_dependencies(lldb
   ${tablegen_deps}
 )
 
+# lldbHost and lldbUtility are also statically linked into liblldb.so, so any
+# state they carry exists in two independent copies. On ELF platforms this
+# creates an interposition hazard: when liblldb.so exports a symbol from those
+# archives (e.g. for dynamic plugin loading), the dynamic linker can resolve
+# it to lldb's copy instead of liblldb.so's, breaking shared-state assumptions.
+# --exclude-libs,ALL keeps archive symbols out of lldb's .dynsym, eliminating
+# the hazard without affecting the executable's own use of those symbols.
+if(UNIX AND NOT APPLE)
+  target_link_options(lldb PRIVATE "LINKER:--exclude-libs,ALL")
+endif()
+
 if(LLDB_BUILD_FRAMEWORK)
   # In the build-tree, we know the exact path to the framework directory.
   # The installed framework can be in different locations.

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to