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
 ---------------


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

Reply via email to