Skip to content

Commit a4d7866

Browse files
authored
[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.
1 parent 03773c3 commit a4d7866

5 files changed

Lines changed: 49 additions & 9 deletions

File tree

‎lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,10 @@ bool DYLDRendezvous::FindMetadata(const char *name, PThreadField field,
714714
return false;
715715

716716
Address address = list[0].symbol->GetAddress();
717-
address.SetOffset(address.GetOffset() + field * sizeof(uint32_t));
717+
// eSize, eNElem, and eOffset correspond to the fields of the DESC structure.
718+
// eStructSize instructs to read a value generated by DB_STRUCT.
719+
int field_num = (field == eStructSize) ? 0 : field;
720+
address.SetOffset(address.GetOffset() + field_num * sizeof(uint32_t));
718721

719722
// Read from target memory as this allows us to try process memory and
720723
// fallback to reading from read only sections from the object files. Here we
@@ -737,6 +740,8 @@ const DYLDRendezvous::ThreadInfo &DYLDRendezvous::GetThreadInfo() {
737740
if (!m_thread_info.valid) {
738741
bool ok = true;
739742

743+
ok &= FindMetadata("_thread_db_sizeof_pthread", eStructSize,
744+
m_thread_info.pthread_size);
740745
ok &= FindMetadata("_thread_db_pthread_dtvp", eOffset,
741746
m_thread_info.dtv_offset);
742747
ok &=

‎lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class DYLDRendezvous {
133133
// the per-thread state.
134134
struct ThreadInfo {
135135
bool valid; // whether we read valid metadata
136+
uint32_t pthread_size; // size of struct pthread
136137
uint32_t dtv_offset; // offset of DTV pointer within pthread
137138
uint32_t dtv_slot_size; // size of one DTV slot
138139
uint32_t modid_offset; // offset of module ID within link_map
@@ -345,7 +346,14 @@ class DYLDRendezvous {
345346
/// supplied by the runtime linker.
346347
bool TakeSnapshot(SOEntryList &entry_list);
347348

348-
enum PThreadField { eSize, eNElem, eOffset };
349+
/// For the definitions of the metadata entries, see
350+
/// <glibc>/nptl_db/(db_info.c, structs.def, thread_dbP.h).
351+
enum PThreadField {
352+
eSize, // Size of an element of a field as defined by DESC, bits
353+
eNElem, // Number of elements in the field
354+
eOffset, // Offset of the field
355+
eStructSize // Size of a type as defined by DB_STRUCT, bytes
356+
};
349357

350358
bool FindMetadata(const char *name, PThreadField field, uint32_t &value);
351359

‎lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -843,10 +843,11 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
843843
LLDB_LOGF(log,
844844
"GetThreadLocalData info: link_map=0x%" PRIx64
845845
", thread info metadata: "
846-
"modid_offset=0x%" PRIx32 ", dtv_offset=0x%" PRIx32
847-
", tls_offset=0x%" PRIx32 ", dtv_slot_size=%" PRIx32 "\n",
848-
link_map, metadata.modid_offset, metadata.dtv_offset,
849-
metadata.tls_offset, metadata.dtv_slot_size);
846+
"modid_offset=0x%" PRIx32 ", pthread_size=0x%" PRIx32
847+
", dtv_offset=0x%" PRIx32 ", tls_offset=0x%" PRIx32
848+
", dtv_slot_size=%" PRIx32 "\n",
849+
link_map, metadata.modid_offset, metadata.pthread_size,
850+
metadata.dtv_offset, metadata.tls_offset, metadata.dtv_slot_size);
850851

851852
// Get the thread pointer.
852853
addr_t tp = thread->GetThreadPointer();
@@ -865,8 +866,33 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
865866
}
866867

867868
// Lookup the DTV structure for this thread.
868-
addr_t dtv_ptr = tp + metadata.dtv_offset;
869-
addr_t dtv = ReadPointer(dtv_ptr);
869+
addr_t dtv_ptr = LLDB_INVALID_ADDRESS;
870+
if (metadata.dtv_offset < metadata.pthread_size) {
871+
// The DTV pointer field lies within `pthread`. This indicates that `libc`
872+
// placed `tcbhead_t header`, which contains the `dtv` field, inside
873+
// `pthread`, so, for this architecture, `TLS_TCB_AT_TP` is set to `1` and
874+
// `TLS_DTV_AT_TP` is `0`. This corresponds to the "Variant II" memory
875+
// layout described in Ulrich Drepper's ELF TLS document
876+
// (https://akkadia.org/drepper/tls.pdf). The thread pointer points to the
877+
// start of `pthread`, and the address of the `dtv` field can be calculated
878+
// by adding its offset.
879+
dtv_ptr = tp + metadata.dtv_offset;
880+
} else if (metadata.dtv_offset == metadata.pthread_size) {
881+
// The DTV pointer field is located right after `pthread`. This means that,
882+
// for this architecture, `TLS_DTV_AT_TP` is set to `1` in `libc`, which may
883+
// correspond to the "Variant I" memory layout, in which the thread pointer
884+
// points directly to the `dtv` field. However, for different architectures,
885+
// the position of the `dtv` field relative to the thread pointer may vary,
886+
// so the following calculations must be adjusted for each platform.
887+
//
888+
// On AArch64 and ARM, `tp` is known to point directly to `dtv`.
889+
const llvm::Triple &triple = module_sp->GetArchitecture().GetTriple();
890+
if (triple.isAArch64() || triple.isARM()) {
891+
dtv_ptr = tp;
892+
}
893+
}
894+
addr_t dtv = (dtv_ptr != LLDB_INVALID_ADDRESS) ? ReadPointer(dtv_ptr)
895+
: LLDB_INVALID_ADDRESS;
870896
if (dtv == LLDB_INVALID_ADDRESS) {
871897
LLDB_LOGF(log, "GetThreadLocalData error: fail to read dtv");
872898
return LLDB_INVALID_ADDRESS;

‎lldb/test/API/lang/c/tls_globals/TestTlsGlobals.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def setUp(self):
3838
# TLS works differently on Windows, this would need to be implemented
3939
# separately.
4040
@skipIfWindows
41-
@skipIf(oslist=["linux"], archs=["arm$", "aarch64"])
41+
@skipIf(oslist=["linux"], archs=["aarch64"])
4242
@skipIf(oslist=no_match([lldbplatformutil.getDarwinOSTriples(), "linux"]))
4343
@expectedFailureIf(lldbplatformutil.xcode15LinkerBug())
4444
def test(self):

‎llvm/docs/ReleaseNotes.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ Changes to LLDB
234234
### Linux
235235

236236
* On Arm Linux, the tpidruro register can now be read. Writing to this register is not supported.
237+
* Thread local variables are now supported on Arm Linux if the program being debugged is using glibc.
237238

238239
Changes to BOLT
239240
---------------

0 commit comments

Comments
 (0)