https://github.com/igorkudrin updated https://github.com/llvm/llvm-project/pull/181315
>From cb8be96868c4a6b0b8011b6a94c189fc25d738be Mon Sep 17 00:00:00 2001 From: Igor Kudrin <[email protected]> Date: Wed, 11 Feb 2026 20:59:49 -0800 Subject: [PATCH] [lldb][ARM] Support thread local variables on ARM Linux This implements reading the thread pointer register (`TPIDRURO`) on ARM Linux and improves `DynamicLoaderPOSIXDYLD::GetThreadLocalData()` to support the TLS memory layout where the thread pointer register points directly to the DTV pointer. --- lldb/include/lldb/Host/linux/Ptrace.h | 8 +++- .../POSIX-DYLD/DYLDRendezvous.cpp | 5 ++- .../DynamicLoader/POSIX-DYLD/DYLDRendezvous.h | 3 +- .../POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp | 38 ++++++++++++++--- .../Linux/NativeRegisterContextLinux_arm.cpp | 41 +++++++++++++++++-- .../Linux/NativeRegisterContextLinux_arm.h | 9 ++++ .../Utility/RegisterContextPOSIX_arm.cpp | 7 ++++ .../Utility/RegisterContextPOSIX_arm.h | 2 + .../Process/Utility/RegisterInfoPOSIX_arm.cpp | 30 ++++++++++---- .../Process/Utility/RegisterInfoPOSIX_arm.h | 6 ++- .../Process/Utility/RegisterInfos_arm.h | 16 ++++++++ .../API/lang/c/tls_globals/TestTlsGlobals.py | 2 +- 12 files changed, 145 insertions(+), 22 deletions(-) diff --git a/lldb/include/lldb/Host/linux/Ptrace.h b/lldb/include/lldb/Host/linux/Ptrace.h index aabd3fd4fc557..233ef962782c7 100644 --- a/lldb/include/lldb/Host/linux/Ptrace.h +++ b/lldb/include/lldb/Host/linux/Ptrace.h @@ -38,9 +38,15 @@ typedef int __ptrace_request; #ifndef PTRACE_SETREGSET #define PTRACE_SETREGSET 0x4205 #endif + #ifndef PTRACE_GET_THREAD_AREA +#ifdef __arm__ +#define PTRACE_GET_THREAD_AREA 22 +#else #define PTRACE_GET_THREAD_AREA 25 -#endif +#endif // __arm__ +#endif // PTRACE_GET_THREAD_AREA + #ifndef PTRACE_ARCH_PRCTL #define PTRACE_ARCH_PRCTL 30 #endif diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp index 1a9c4593b1b4f..f9c29b95185dc 100644 --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp @@ -714,7 +714,8 @@ bool DYLDRendezvous::FindMetadata(const char *name, PThreadField field, return false; Address address = list[0].symbol->GetAddress(); - address.SetOffset(address.GetOffset() + field * sizeof(uint32_t)); + 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 +738,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..d13b8250cc314 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,7 @@ class DYLDRendezvous { /// supplied by the runtime linker. bool TakeSnapshot(SOEntryList &entry_list); - enum PThreadField { eSize, eNElem, eOffset }; + enum PThreadField { eSize, eNElem, eOffset, eStructSize }; 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..270339dd74111 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 "Version I" memory layout, in which the thread pointer + // points directly to the `dtv` field. However, for different 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/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp index c1bc6a3f036bf..70b957b0285fe 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp @@ -25,9 +25,10 @@ #if defined(__arm64__) || defined(__aarch64__) #include "NativeRegisterContextLinux_arm64dbreg.h" +#endif + #include "lldb/Host/linux/Ptrace.h" #include <asm/ptrace.h> -#endif #define REG_CONTEXT_SIZE (GetGPRSize() + sizeof(m_fpr)) @@ -68,8 +69,9 @@ NativeRegisterContextLinux::DetermineArchitecture(lldb::tid_t tid) { NativeRegisterContextLinux_arm::NativeRegisterContextLinux_arm( const ArchSpec &target_arch, NativeThreadProtocol &native_thread) - : NativeRegisterContextRegisterInfo(native_thread, - new RegisterInfoPOSIX_arm(target_arch)), + : NativeRegisterContextRegisterInfo( + native_thread, + new RegisterInfoPOSIX_arm(target_arch, /*has_tls_reg=*/true)), NativeRegisterContextLinux(native_thread) { assert(target_arch.GetMachine() == llvm::Triple::arm); @@ -77,6 +79,7 @@ NativeRegisterContextLinux_arm::NativeRegisterContextLinux_arm( ::memset(&m_gpr_arm, 0, sizeof(m_gpr_arm)); ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs)); ::memset(&m_hbp_regs, 0, sizeof(m_hbp_regs)); + m_tpidr = 0; // 16 is just a maximum value, query hardware for actual watchpoint count m_max_hwp_supported = 16; @@ -120,6 +123,11 @@ NativeRegisterContextLinux_arm::ReadRegister(const RegisterInfo *reg_info, error = ReadFPR(); if (error.Fail()) return error; + } else if (IsTLS(reg)) { + error = ReadTLS(); + if (error.Success()) + reg_value.SetUInt32(m_tpidr); + return error; } else { uint32_t full_reg = reg; bool is_subreg = reg_info->invalidate_regs && @@ -199,6 +207,10 @@ NativeRegisterContextLinux_arm::WriteRegister(const RegisterInfo *reg_info, return WriteFPR(); } + if (IsTLS(reg_index)) + return Status::FromErrorString( + "writing to a thread pointer register is not implemented"); + return Status::FromErrorString( "failed - register wasn't recognized to be a GPR or an FPR, " "write strategy unknown"); @@ -283,6 +295,13 @@ bool NativeRegisterContextLinux_arm::IsFPR(unsigned reg) const { return false; } +bool NativeRegisterContextLinux_arm::IsTLS(unsigned reg) const { + if (GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg) == + RegisterInfoPOSIX_arm::TLSRegSet) + return true; + return false; +} + llvm::Error NativeRegisterContextLinux_arm::ReadHardwareDebugInfo() { if (!m_refresh_hwdebug_info) return llvm::Error::success(); @@ -480,4 +499,20 @@ Status NativeRegisterContextLinux_arm::WriteFPR() { #endif // __arm__ } +Status NativeRegisterContextLinux_arm::ReadTLS() { +#ifdef __arm__ + return NativeProcessLinux::PtraceWrapper(PTRACE_GET_THREAD_AREA, + m_thread.GetID(), nullptr, + GetTLSBuffer(), GetTLSSize()); +#else // __aarch64__ + Status error; + + struct iovec ioVec; + ioVec.iov_base = GetTLSBuffer(); + ioVec.iov_len = GetTLSSize(); + + return ReadRegisterSet(&ioVec, GetTLSSize(), NT_ARM_TLS); +#endif // __arm__ +} + #endif // defined(__arm__) || defined(__arm64__) || defined(__aarch64__) diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h index cf36859b16ad4..2c292ad19b966 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h @@ -58,15 +58,22 @@ class NativeRegisterContextLinux_arm : public NativeRegisterContextLinux, Status WriteFPR() override; + Status ReadTLS(); + void *GetGPRBuffer() override { return &m_gpr_arm; } void *GetFPRBuffer() override { return &m_fpr; } size_t GetFPRSize() override { return sizeof(m_fpr); } + void *GetTLSBuffer() { return &m_tpidr; } + + size_t GetTLSSize() { return sizeof(m_tpidr); } + private: uint32_t m_gpr_arm[k_num_gpr_registers_arm]; RegisterInfoPOSIX_arm::FPU m_fpr; + uint32_t m_tpidr; bool m_refresh_hwdebug_info; @@ -74,6 +81,8 @@ class NativeRegisterContextLinux_arm : public NativeRegisterContextLinux, bool IsFPR(unsigned reg) const; + bool IsTLS(unsigned reg) const; + llvm::Error ReadHardwareDebugInfo() override; llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override; diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp index 684176bccdf0c..2d8f5a30fcd56 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp @@ -39,6 +39,13 @@ bool RegisterContextPOSIX_arm::IsFPR(unsigned reg) { return false; } +bool RegisterContextPOSIX_arm::IsTLS(unsigned reg) { + if (m_register_info_up->GetRegisterSetFromRegisterIndex(reg) == + RegisterInfoPOSIX_arm::TLSRegSet) + return true; + return false; +} + RegisterContextPOSIX_arm::RegisterContextPOSIX_arm( lldb_private::Thread &thread, std::unique_ptr<RegisterInfoPOSIX_arm> register_info) diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h index 099c37d46f496..ecd927e64cf70 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h +++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h @@ -51,6 +51,8 @@ class RegisterContextPOSIX_arm : public lldb_private::RegisterContext { bool IsFPR(unsigned reg); + bool IsTLS(unsigned reg); + size_t GetFPUSize() { return sizeof(RegisterInfoPOSIX_arm::FPU); } virtual bool ReadGPR() = 0; diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp index d47647422ae21..fbe85a3a92544 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp @@ -75,7 +75,9 @@ GetRegisterInfoCount(const lldb_private::ArchSpec &target_arch) { enum { k_num_gpr_registers = gpr_cpsr - gpr_r0 + 1, k_num_fpr_registers = fpu_q15 - fpu_s0 + 1, - k_num_register_sets = 2 + k_num_tls_registers = 1, + k_num_register_sets_without_tls = 2, + k_num_register_sets_with_tls = 3 }; // arm general purpose registers. @@ -142,18 +144,29 @@ static_assert(((sizeof g_fpu_regnums_arm / sizeof g_fpu_regnums_arm[0]) - 1) == k_num_fpr_registers, "g_fpu_regnums_arm has wrong number of register infos"); +// arm thread local storage registers. +static const uint32_t g_tls_regnums_arm[] = { + tls_tpidruro, + LLDB_INVALID_REGNUM // register sets need to end with this flag +}; +static_assert(((sizeof g_tls_regnums_arm / sizeof g_tls_regnums_arm[0]) - 1) == + k_num_tls_registers, + "g_tls_regnums_arm has wrong number of register infos"); + // Register sets for arm. -static const RegisterSet g_reg_sets_arm[k_num_register_sets] = { +static const RegisterSet g_reg_sets_arm[k_num_register_sets_with_tls] = { {"General Purpose Registers", "gpr", k_num_gpr_registers, g_gpr_regnums_arm}, - {"Floating Point Registers", "fpu", k_num_fpr_registers, - g_fpu_regnums_arm}}; + {"Floating Point Registers", "fpu", k_num_fpr_registers, g_fpu_regnums_arm}, + {"Thread Local Storage Registers", "tls", k_num_tls_registers, + g_tls_regnums_arm}}; RegisterInfoPOSIX_arm::RegisterInfoPOSIX_arm( - const lldb_private::ArchSpec &target_arch) + const lldb_private::ArchSpec &target_arch, bool has_tls_reg) : lldb_private::RegisterInfoAndSetInterface(target_arch), m_register_info_p(GetRegisterInfoPtr(target_arch)), - m_register_info_count(GetRegisterInfoCount(target_arch)) {} + m_register_info_count(GetRegisterInfoCount(target_arch)), + m_has_tls_reg(has_tls_reg) {} size_t RegisterInfoPOSIX_arm::GetGPRSize() const { return sizeof(struct RegisterInfoPOSIX_arm::GPR); @@ -169,7 +182,8 @@ RegisterInfoPOSIX_arm::GetRegisterInfo() const { } size_t RegisterInfoPOSIX_arm::GetRegisterSetCount() const { - return k_num_register_sets; + return m_has_tls_reg ? k_num_register_sets_with_tls + : k_num_register_sets_without_tls; } size_t RegisterInfoPOSIX_arm::GetRegisterSetFromRegisterIndex( @@ -178,6 +192,8 @@ size_t RegisterInfoPOSIX_arm::GetRegisterSetFromRegisterIndex( return GPRegSet; if (reg_index <= fpu_q15) return FPRegSet; + if (reg_index == tls_tpidruro && m_has_tls_reg) + return TLSRegSet; return LLDB_INVALID_REGNUM; } diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h index db155d757ca8c..327e9f746cab0 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h @@ -15,7 +15,7 @@ class RegisterInfoPOSIX_arm : public lldb_private::RegisterInfoAndSetInterface { public: - enum { GPRegSet = 0, FPRegSet}; + enum { GPRegSet = 0, FPRegSet, TLSRegSet }; struct GPR { uint32_t r[16]; // R0-R15 @@ -47,7 +47,8 @@ class RegisterInfoPOSIX_arm : public lldb_private::RegisterInfoAndSetInterface { uint32_t wcr[16]; }; - RegisterInfoPOSIX_arm(const lldb_private::ArchSpec &target_arch); + RegisterInfoPOSIX_arm(const lldb_private::ArchSpec &target_arch, + bool has_tls_reg = false); size_t GetGPRSize() const override; @@ -67,6 +68,7 @@ class RegisterInfoPOSIX_arm : public lldb_private::RegisterInfoAndSetInterface { private: const lldb_private::RegisterInfo *m_register_info_p; uint32_t m_register_info_count; + bool m_has_tls_reg; }; #endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERINFOPOSIX_ARM_H diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h index f535ca40c4030..afaada679cb79 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h @@ -146,6 +146,8 @@ enum { fpu_q14, fpu_q15, + tls_tpidruro, + exc_exception, exc_fsr, exc_far, @@ -682,6 +684,20 @@ static RegisterInfo g_register_infos_arm[] = { FPU_QREG(q14, 56), FPU_QREG(q15, 60), + { + "tpidruro", + nullptr, + 4, + 0, + eEncodingUint, + eFormatHex, + {LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_REGNUM_GENERIC_TP, + LLDB_INVALID_REGNUM, tls_tpidruro}, + nullptr, + nullptr, + nullptr, + }, + { "exception", nullptr, 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 differently 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): _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
