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
