https://github.com/igorkudrin created https://github.com/llvm/llvm-project/pull/182438
This implements reading the TPIDRURO register, which serves as the thread local register on Arm Linux. >From bda519718344cf6f53899da6ff59a5923a87801a Mon Sep 17 00:00:00 2001 From: Igor Kudrin <[email protected]> Date: Thu, 19 Feb 2026 20:49:17 -0800 Subject: [PATCH] [lldb][ARM] Support reading the thread local register on Arm Linux This implements reading the TPIDRURO register, which serves as the thread local register on Arm Linux. --- lldb/include/lldb/Host/linux/Ptrace.h | 9 +++- .../Linux/NativeRegisterContextLinux_arm.cpp | 49 +++++++++++++++++-- .../Linux/NativeRegisterContextLinux_arm.h | 9 ++++ .../Process/Utility/RegisterInfoPOSIX_arm.cpp | 38 ++++++++++---- .../Process/Utility/RegisterInfoPOSIX_arm.h | 12 ++++- .../Process/Utility/RegisterInfos_arm.h | 20 ++++++++ .../Common/arm/RegisterContextWindows_arm.cpp | 1 + lldb/test/API/linux/arm/tls_register/Makefile | 3 ++ .../tls_register/TestArmLinuxTLSRegister.py | 45 +++++++++++++++++ lldb/test/API/linux/arm/tls_register/main.c | 7 +++ llvm/docs/ReleaseNotes.md | 4 ++ 11 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 lldb/test/API/linux/arm/tls_register/Makefile create mode 100644 lldb/test/API/linux/arm/tls_register/TestArmLinuxTLSRegister.py create mode 100644 lldb/test/API/linux/arm/tls_register/main.c diff --git a/lldb/include/lldb/Host/linux/Ptrace.h b/lldb/include/lldb/Host/linux/Ptrace.h index aabd3fd4fc557..0a45516a45c7f 100644 --- a/lldb/include/lldb/Host/linux/Ptrace.h +++ b/lldb/include/lldb/Host/linux/Ptrace.h @@ -38,9 +38,16 @@ typedef int __ptrace_request; #ifndef PTRACE_SETREGSET #define PTRACE_SETREGSET 0x4205 #endif + #ifndef PTRACE_GET_THREAD_AREA +#ifdef __arm__ +// Arm has a different value, see arch/arm/include/uapi/asm/ptrace.h. +#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/Process/Linux/NativeRegisterContextLinux_arm.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp index c1bc6a3f036bf..c83cea2bbf5bd 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp @@ -25,11 +25,12 @@ #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)) +#define REG_CONTEXT_SIZE (GetGPRSize() + sizeof(m_fpr) + sizeof(m_tls)) #ifndef PTRACE_GETVFPREGS #define PTRACE_GETVFPREGS 27 @@ -68,12 +69,14 @@ 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); ::memset(&m_fpr, 0, sizeof(m_fpr)); + ::memset(&m_tls, 0, sizeof(m_tls)); ::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)); @@ -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_tls.tpidruro); + 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"); @@ -217,10 +229,16 @@ Status NativeRegisterContextLinux_arm::ReadAllRegisterValues( if (error.Fail()) return error; + error = ReadTLS(); + if (error.Fail()) + return error; + uint8_t *dst = data_sp->GetBytes(); ::memcpy(dst, &m_gpr_arm, GetGPRSize()); dst += GetGPRSize(); ::memcpy(dst, &m_fpr, sizeof(m_fpr)); + dst += sizeof(m_fpr); + ::memcpy(dst, &m_tls, sizeof(m_tls)); return error; } @@ -266,6 +284,8 @@ Status NativeRegisterContextLinux_arm::WriteAllRegisterValues( if (error.Fail()) return error; + // Note: writing to a thread pointer register is not implemented. + return error; } @@ -283,6 +303,11 @@ bool NativeRegisterContextLinux_arm::IsFPR(unsigned reg) const { return false; } +bool NativeRegisterContextLinux_arm::IsTLS(unsigned reg) const { + return GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg) == + RegisterInfoPOSIX_arm::TLSRegSet; +} + llvm::Error NativeRegisterContextLinux_arm::ReadHardwareDebugInfo() { if (!m_refresh_hwdebug_info) return llvm::Error::success(); @@ -480,4 +505,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..3722f667b62c5 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_tls; } + + size_t GetTLSSize() { return sizeof(m_tls); } + private: uint32_t m_gpr_arm[k_num_gpr_registers_arm]; RegisterInfoPOSIX_arm::FPU m_fpr; + RegisterInfoPOSIX_arm::TLS m_tls; 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/RegisterInfoPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp index d47647422ae21..021ea56325e77 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp @@ -24,13 +24,15 @@ using namespace lldb_private; #define FPSCR_OFFSET \ (LLVM_EXTENSION offsetof(RegisterInfoPOSIX_arm::FPU, fpscr) + \ sizeof(RegisterInfoPOSIX_arm::GPR)) +#define TLS_OFFSET \ + (sizeof(RegisterInfoPOSIX_arm::GPR) + sizeof(RegisterInfoPOSIX_arm::FPU)) #define EXC_OFFSET(idx) \ - ((idx)*4 + sizeof(RegisterInfoPOSIX_arm::GPR) + \ - sizeof(RegisterInfoPOSIX_arm::FPU)) + ((idx) * 4 + sizeof(RegisterInfoPOSIX_arm::GPR) + \ + sizeof(RegisterInfoPOSIX_arm::FPU) + sizeof(RegisterInfoPOSIX_arm::TLS)) #define DBG_OFFSET(reg) \ ((LLVM_EXTENSION offsetof(RegisterInfoPOSIX_arm::DBG, reg) + \ sizeof(RegisterInfoPOSIX_arm::GPR) + sizeof(RegisterInfoPOSIX_arm::FPU) + \ - sizeof(RegisterInfoPOSIX_arm::EXC))) + sizeof(RegisterInfoPOSIX_arm::TLS) + sizeof(RegisterInfoPOSIX_arm::EXC))) #define DEFINE_DBG(reg, i) \ #reg, NULL, sizeof(((RegisterInfoPOSIX_arm::DBG *) NULL)->reg[i]), \ @@ -75,7 +77,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 +146,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 +184,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 +194,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..0695b0afd858e 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 @@ -40,6 +40,10 @@ class RegisterInfoPOSIX_arm : public lldb_private::RegisterInfoAndSetInterface { uint32_t far; /* Virtual Fault Address */ }; + struct TLS { + uint32_t tpidruro; + }; + struct DBG { uint32_t bvr[16]; uint32_t bcr[16]; @@ -47,7 +51,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 +72,9 @@ class RegisterInfoPOSIX_arm : public lldb_private::RegisterInfoAndSetInterface { private: const lldb_private::RegisterInfo *m_register_info_p; uint32_t m_register_info_count; + // Only provide information about the TLS register to users of this class that + // can handle it. Currently, only `NativeRegisterContextLinux_arm` reads it. + 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..6bcfe949bd478 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h @@ -32,6 +32,10 @@ using namespace lldb_private; #error FPSCR_OFFSET must be defined before including this header file #endif +#ifndef TLS_OFFSET +#error TLS_OFFSET must be defined before including this header file +#endif + #ifndef EXC_OFFSET #error EXC_OFFSET_NAME must be defined before including this header file #endif @@ -146,6 +150,8 @@ enum { fpu_q14, fpu_q15, + tls_tpidruro, + exc_exception, exc_fsr, exc_far, @@ -682,6 +688,20 @@ static RegisterInfo g_register_infos_arm[] = { FPU_QREG(q14, 56), FPU_QREG(q15, 60), + { + "tpidruro", + nullptr, + 4, + TLS_OFFSET, + 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/source/Plugins/Process/Windows/Common/arm/RegisterContextWindows_arm.cpp b/lldb/source/Plugins/Process/Windows/Common/arm/RegisterContextWindows_arm.cpp index bf970bd521c73..6f0efc257695d 100644 --- a/lldb/source/Plugins/Process/Windows/Common/arm/RegisterContextWindows_arm.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/arm/RegisterContextWindows_arm.cpp @@ -25,6 +25,7 @@ using namespace lldb_private; #define GPR_OFFSET(idx) 0 #define FPU_OFFSET(idx) 0 #define FPSCR_OFFSET 0 +#define TLS_OFFSET 0 #define EXC_OFFSET(reg) 0 #define DBG_OFFSET_NAME(reg) 0 diff --git a/lldb/test/API/linux/arm/tls_register/Makefile b/lldb/test/API/linux/arm/tls_register/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/linux/arm/tls_register/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/linux/arm/tls_register/TestArmLinuxTLSRegister.py b/lldb/test/API/linux/arm/tls_register/TestArmLinuxTLSRegister.py new file mode 100644 index 0000000000000..5f5699702eaa5 --- /dev/null +++ b/lldb/test/API/linux/arm/tls_register/TestArmLinuxTLSRegister.py @@ -0,0 +1,45 @@ +""" +Test lldb's ability to read the Arm TLS register. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +@skipUnlessArch("arm") +@skipUnlessPlatform(["linux"]) +class ArmLinuxTLSRegister(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_tls_reg_read(self): + self.build() + (_, _, thread, _) = lldbutil.run_to_source_breakpoint( + self, "// Set breakpoint here.", lldb.SBFileSpec("main.c") + ) + frame = thread.GetFrameAtIndex(0) + + tpidruro_var = frame.FindVariable("tpidruro") + self.assertTrue(tpidruro_var.IsValid()) + + regs = frame.GetRegisters() + tls_regs = regs.GetFirstValueByName("Thread Local Storage Registers") + self.assertTrue(tls_regs.IsValid(), "No TLS registers found.") + tpidruro_reg = tls_regs.GetChildMemberWithName("tpidruro") + self.assertTrue(tpidruro_reg.IsValid(), "tpidruro register not found.") + + val = tpidruro_var.GetValueAsUnsigned() + self.assertEqual(tpidruro_reg.GetValueAsUnsigned(), val) + self.expect("reg read tp", substrs=[hex(val)]) + + def test_tls_reg_write(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "// Set breakpoint here.", lldb.SBFileSpec("main.c") + ) + self.expect( + "register write tpidruro 0x1234", + error=True, + substrs=["Failed to write register 'tpidruro'"], + ) diff --git a/lldb/test/API/linux/arm/tls_register/main.c b/lldb/test/API/linux/arm/tls_register/main.c new file mode 100644 index 0000000000000..ccbc3cbc45377 --- /dev/null +++ b/lldb/test/API/linux/arm/tls_register/main.c @@ -0,0 +1,7 @@ +#include <stdint.h> + +int main(int argc, char *argv[]) { + uint32_t tpidruro = 0; + __asm__ volatile("mrc p15, 0, %0, c13, c0, 3" : "=r"(tpidruro)); + return 0; // Set breakpoint here. +} diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md index 2e121923e0bb9..184a1c0dc9407 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -213,6 +213,10 @@ Changes to LLDB * The crashed thread is now automatically selected on start. * Threads are listed in incrmental order by pid then by tid. +### Linux + +* On Arm Linux, the tpidruro register can now be read. Writing to this register is not supported. + Changes to BOLT --------------- _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
