[llvm] a4d7866 - [lldb][ARM] Support thread local variables on ARM Linux (#181315)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 2 12:50:50 PST 2026
Author: Igor Kudrin
Date: 2026-03-02T12:50:43-08:00
New Revision: a4d786630c4757ce91aef65fc2744fbde650632d
URL: https://github.com/llvm/llvm-project/commit/a4d786630c4757ce91aef65fc2744fbde650632d
DIFF: https://github.com/llvm/llvm-project/commit/a4d786630c4757ce91aef65fc2744fbde650632d.diff
LOG: [lldb][ARM] Support thread local variables on ARM Linux (#181315)
Currently, `DynamicLoaderPOSIXDYLD::GetThreadLocalData()` only supports
the TLS memory layout where the thread pointer register points to the
start of the `pthread` structure, and the address of the DTV pointer can
be calculated by adding the offset of the `dtv` field to `tp`. On ARM
(and AArch64), the thread pointer points directly to `dtv`. The patch
improves the detection of the actual memory layout in the method and
adjusts the calculations for the new case, thus adding support for
thread-local variables on ARM Linux.
Added:
Modified:
lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py
llvm/docs/ReleaseNotes.md
Removed:
################################################################################
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
index 1a9c4593b1b4f..98c753c2c2490 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
@@ -714,7 +714,10 @@ bool DYLDRendezvous::FindMetadata(const char *name, PThreadField field,
return false;
Address address = list[0].symbol->GetAddress();
- address.SetOffset(address.GetOffset() + field * sizeof(uint32_t));
+ // eSize, eNElem, and eOffset correspond to the fields of the DESC structure.
+ // eStructSize instructs to read a value generated by DB_STRUCT.
+ int field_num = (field == eStructSize) ? 0 : field;
+ address.SetOffset(address.GetOffset() + field_num * sizeof(uint32_t));
// Read from target memory as this allows us to try process memory and
// fallback to reading from read only sections from the object files. Here we
@@ -737,6 +740,8 @@ const DYLDRendezvous::ThreadInfo &DYLDRendezvous::GetThreadInfo() {
if (!m_thread_info.valid) {
bool ok = true;
+ ok &= FindMetadata("_thread_db_sizeof_pthread", eStructSize,
+ m_thread_info.pthread_size);
ok &= FindMetadata("_thread_db_pthread_dtvp", eOffset,
m_thread_info.dtv_offset);
ok &=
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
index b8bdf78fbdfad..41eab64dcfc3b 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
@@ -133,6 +133,7 @@ class DYLDRendezvous {
// the per-thread state.
struct ThreadInfo {
bool valid; // whether we read valid metadata
+ uint32_t pthread_size; // size of struct pthread
uint32_t dtv_offset; // offset of DTV pointer within pthread
uint32_t dtv_slot_size; // size of one DTV slot
uint32_t modid_offset; // offset of module ID within link_map
@@ -345,7 +346,14 @@ class DYLDRendezvous {
/// supplied by the runtime linker.
bool TakeSnapshot(SOEntryList &entry_list);
- enum PThreadField { eSize, eNElem, eOffset };
+ /// For the definitions of the metadata entries, see
+ /// <glibc>/nptl_db/(db_info.c, structs.def, thread_dbP.h).
+ enum PThreadField {
+ eSize, // Size of an element of a field as defined by DESC, bits
+ eNElem, // Number of elements in the field
+ eOffset, // Offset of the field
+ eStructSize // Size of a type as defined by DB_STRUCT, bytes
+ };
bool FindMetadata(const char *name, PThreadField field, uint32_t &value);
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
index 1d814f93484d8..3541d2f998c45 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
@@ -843,10 +843,11 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
LLDB_LOGF(log,
"GetThreadLocalData info: link_map=0x%" PRIx64
", thread info metadata: "
- "modid_offset=0x%" PRIx32 ", dtv_offset=0x%" PRIx32
- ", tls_offset=0x%" PRIx32 ", dtv_slot_size=%" PRIx32 "\n",
- link_map, metadata.modid_offset, metadata.dtv_offset,
- metadata.tls_offset, metadata.dtv_slot_size);
+ "modid_offset=0x%" PRIx32 ", pthread_size=0x%" PRIx32
+ ", dtv_offset=0x%" PRIx32 ", tls_offset=0x%" PRIx32
+ ", dtv_slot_size=%" PRIx32 "\n",
+ link_map, metadata.modid_offset, metadata.pthread_size,
+ metadata.dtv_offset, metadata.tls_offset, metadata.dtv_slot_size);
// Get the thread pointer.
addr_t tp = thread->GetThreadPointer();
@@ -865,8 +866,33 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
}
// Lookup the DTV structure for this thread.
- addr_t dtv_ptr = tp + metadata.dtv_offset;
- addr_t dtv = ReadPointer(dtv_ptr);
+ addr_t dtv_ptr = LLDB_INVALID_ADDRESS;
+ if (metadata.dtv_offset < metadata.pthread_size) {
+ // The DTV pointer field lies within `pthread`. This indicates that `libc`
+ // placed `tcbhead_t header`, which contains the `dtv` field, inside
+ // `pthread`, so, for this architecture, `TLS_TCB_AT_TP` is set to `1` and
+ // `TLS_DTV_AT_TP` is `0`. This corresponds to the "Variant II" memory
+ // layout described in Ulrich Drepper's ELF TLS document
+ // (https://akkadia.org/drepper/tls.pdf). The thread pointer points to the
+ // start of `pthread`, and the address of the `dtv` field can be calculated
+ // by adding its offset.
+ dtv_ptr = tp + metadata.dtv_offset;
+ } else if (metadata.dtv_offset == metadata.pthread_size) {
+ // The DTV pointer field is located right after `pthread`. This means that,
+ // for this architecture, `TLS_DTV_AT_TP` is set to `1` in `libc`, which may
+ // correspond to the "Variant I" memory layout, in which the thread pointer
+ // points directly to the `dtv` field. However, for
diff erent architectures,
+ // the position of the `dtv` field relative to the thread pointer may vary,
+ // so the following calculations must be adjusted for each platform.
+ //
+ // On AArch64 and ARM, `tp` is known to point directly to `dtv`.
+ const llvm::Triple &triple = module_sp->GetArchitecture().GetTriple();
+ if (triple.isAArch64() || triple.isARM()) {
+ dtv_ptr = tp;
+ }
+ }
+ addr_t dtv = (dtv_ptr != LLDB_INVALID_ADDRESS) ? ReadPointer(dtv_ptr)
+ : LLDB_INVALID_ADDRESS;
if (dtv == LLDB_INVALID_ADDRESS) {
LLDB_LOGF(log, "GetThreadLocalData error: fail to read dtv");
return LLDB_INVALID_ADDRESS;
diff --git a/lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py b/lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py
index ed696bca54ab4..28d5212cbc7dc 100644
--- a/lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py
+++ b/lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py
@@ -38,7 +38,7 @@ def setUp(self):
# TLS works
diff erently on Windows, this would need to be implemented
# separately.
@skipIfWindows
- @skipIf(oslist=["linux"], archs=["arm$", "aarch64"])
+ @skipIf(oslist=["linux"], archs=["aarch64"])
@skipIf(oslist=no_match([lldbplatformutil.getDarwinOSTriples(), "linux"]))
@expectedFailureIf(lldbplatformutil.xcode15LinkerBug())
def test(self):
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 2e0c5c5cb9370..91b150c9fe982 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -234,6 +234,7 @@ Changes to LLDB
### Linux
* On Arm Linux, the tpidruro register can now be read. Writing to this register is not supported.
+* Thread local variables are now supported on Arm Linux if the program being debugged is using glibc.
Changes to BOLT
---------------
More information about the llvm-commits
mailing list