[Lldb-commits] [lldb] [llvm] [lldb][ARM] Support reading the thread local register on Arm Linux (PR #182438)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Feb 19 20:59:04 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Igor Kudrin (igorkudrin)
<details>
<summary>Changes</summary>
This implements reading the TPIDRURO register, which serves as the thread local register on Arm Linux.
---
Full diff: https://github.com/llvm/llvm-project/pull/182438.diff
11 Files Affected:
- (modified) lldb/include/lldb/Host/linux/Ptrace.h (+8-1)
- (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp (+45-4)
- (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h (+9)
- (modified) lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp (+28-10)
- (modified) lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h (+10-2)
- (modified) lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h (+20)
- (modified) lldb/source/Plugins/Process/Windows/Common/arm/RegisterContextWindows_arm.cpp (+1)
- (added) lldb/test/API/linux/arm/tls_register/Makefile (+3)
- (added) lldb/test/API/linux/arm/tls_register/TestArmLinuxTLSRegister.py (+45)
- (added) lldb/test/API/linux/arm/tls_register/main.c (+7)
- (modified) llvm/docs/ReleaseNotes.md (+4)
``````````diff
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
+
+
+ at skipUnlessArch("arm")
+ at 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
---------------
``````````
</details>
https://github.com/llvm/llvm-project/pull/182438
More information about the lldb-commits
mailing list