[Lldb-commits] [lldb] [llvm] [lldb][ARM] Support thread local variables on ARM Linux (PR #181315)
Igor Kudrin via lldb-commits
lldb-commits at lists.llvm.org
Thu Feb 19 21:01:57 PST 2026
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 <ikudrin at accesssoftek.com>
Date: Wed, 11 Feb 2026 20:59:49 -0800
Subject: [PATCH 1/9] [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):
>From 16150f1ee19d0e5950da063fa56a6679b4ddff4f Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 17 Feb 2026 17:36:17 -0800
Subject: [PATCH 2/9] fixup! Add comments
---
lldb/include/lldb/Host/linux/Ptrace.h | 1 +
.../Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp | 2 ++
.../Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h | 9 ++++++++-
.../Plugins/Process/Utility/RegisterInfoPOSIX_arm.h | 2 ++
4 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Host/linux/Ptrace.h b/lldb/include/lldb/Host/linux/Ptrace.h
index 233ef962782c7..0a45516a45c7f 100644
--- a/lldb/include/lldb/Host/linux/Ptrace.h
+++ b/lldb/include/lldb/Host/linux/Ptrace.h
@@ -41,6 +41,7 @@ typedef int __ptrace_request;
#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
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
index f9c29b95185dc..98c753c2c2490 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
@@ -714,6 +714,8 @@ bool DYLDRendezvous::FindMetadata(const char *name, PThreadField field,
return false;
Address address = list[0].symbol->GetAddress();
+ // 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));
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
index d13b8250cc314..41eab64dcfc3b 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
@@ -346,7 +346,14 @@ class DYLDRendezvous {
/// supplied by the runtime linker.
bool TakeSnapshot(SOEntryList &entry_list);
- enum PThreadField { eSize, eNElem, eOffset, eStructSize };
+ /// 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/Process/Utility/RegisterInfoPOSIX_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
index 327e9f746cab0..c6384f64bfff6 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
@@ -68,6 +68,8 @@ 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;
};
>From c4b7acbd6c33846eeee63e1df24c81cad0476b11 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 17 Feb 2026 17:38:34 -0800
Subject: [PATCH 3/9] fixup! Remove changes in RegisterContextPOSIX_arm
---
.../Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp | 7 -------
.../Plugins/Process/Utility/RegisterContextPOSIX_arm.h | 2 --
2 files changed, 9 deletions(-)
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp
index 2d8f5a30fcd56..684176bccdf0c 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.cpp
@@ -39,13 +39,6 @@ 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 ecd927e64cf70..099c37d46f496 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm.h
@@ -51,8 +51,6 @@ 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;
>From b259f0249fbf5c07531ba5263ba87bf542f1b32e Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 17 Feb 2026 17:39:11 -0800
Subject: [PATCH 4/9] fixup! Simplify NativeRegisterContextLinux_arm::IsTLS()
---
.../Process/Linux/NativeRegisterContextLinux_arm.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
index 70b957b0285fe..21ba06c902d33 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
@@ -296,10 +296,8 @@ bool NativeRegisterContextLinux_arm::IsFPR(unsigned reg) const {
}
bool NativeRegisterContextLinux_arm::IsTLS(unsigned reg) const {
- if (GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg) ==
- RegisterInfoPOSIX_arm::TLSRegSet)
- return true;
- return false;
+ return GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg) ==
+ RegisterInfoPOSIX_arm::TLSRegSet;
}
llvm::Error NativeRegisterContextLinux_arm::ReadHardwareDebugInfo() {
>From 0a115d99c12c0fbbf3187e367f4acd8555d6cc50 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 17 Feb 2026 17:40:13 -0800
Subject: [PATCH 5/9] fixup! "tpidruro" -> "tpidr"
---
.../Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp | 4 ++--
lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
index fbe85a3a92544..49a3629019003 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
@@ -146,7 +146,7 @@ static_assert(((sizeof g_fpu_regnums_arm / sizeof g_fpu_regnums_arm[0]) - 1) ==
// arm thread local storage registers.
static const uint32_t g_tls_regnums_arm[] = {
- tls_tpidruro,
+ tls_tpidr,
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) ==
@@ -192,7 +192,7 @@ size_t RegisterInfoPOSIX_arm::GetRegisterSetFromRegisterIndex(
return GPRegSet;
if (reg_index <= fpu_q15)
return FPRegSet;
- if (reg_index == tls_tpidruro && m_has_tls_reg)
+ if (reg_index == tls_tpidr && m_has_tls_reg)
return TLSRegSet;
return LLDB_INVALID_REGNUM;
}
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
index afaada679cb79..99efe9b6449a0 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
@@ -146,7 +146,7 @@ enum {
fpu_q14,
fpu_q15,
- tls_tpidruro,
+ tls_tpidr,
exc_exception,
exc_fsr,
@@ -685,14 +685,14 @@ static RegisterInfo g_register_infos_arm[] = {
FPU_QREG(q15, 60),
{
- "tpidruro",
+ "tpidr",
nullptr,
4,
0,
eEncodingUint,
eFormatHex,
{LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_REGNUM_GENERIC_TP,
- LLDB_INVALID_REGNUM, tls_tpidruro},
+ LLDB_INVALID_REGNUM, tls_tpidr},
nullptr,
nullptr,
nullptr,
>From 1706e80502e78993ebacaae962e029bcc0d70a2b Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 17 Feb 2026 19:38:51 -0800
Subject: [PATCH 6/9] fixup! fix comment: "Version I" -> "Variant I"
---
.../Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
index 270339dd74111..3541d2f998c45 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
@@ -880,7 +880,7 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
} 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
+ // correspond to the "Variant 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.
>From fa0f11993c520c47588c29dd5893c8eaffca92f0 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Wed, 18 Feb 2026 22:45:11 -0800
Subject: [PATCH 7/9] fixup! Revert "fixup! "tpidruro" -> "tpidr""
This reverts commit 0a115d99c12c0fbbf3187e367f4acd8555d6cc50.
---
.../Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp | 4 ++--
lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
index 49a3629019003..fbe85a3a92544 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
@@ -146,7 +146,7 @@ static_assert(((sizeof g_fpu_regnums_arm / sizeof g_fpu_regnums_arm[0]) - 1) ==
// arm thread local storage registers.
static const uint32_t g_tls_regnums_arm[] = {
- tls_tpidr,
+ 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) ==
@@ -192,7 +192,7 @@ size_t RegisterInfoPOSIX_arm::GetRegisterSetFromRegisterIndex(
return GPRegSet;
if (reg_index <= fpu_q15)
return FPRegSet;
- if (reg_index == tls_tpidr && m_has_tls_reg)
+ if (reg_index == tls_tpidruro && m_has_tls_reg)
return TLSRegSet;
return LLDB_INVALID_REGNUM;
}
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
index 99efe9b6449a0..afaada679cb79 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
@@ -146,7 +146,7 @@ enum {
fpu_q14,
fpu_q15,
- tls_tpidr,
+ tls_tpidruro,
exc_exception,
exc_fsr,
@@ -685,14 +685,14 @@ static RegisterInfo g_register_infos_arm[] = {
FPU_QREG(q15, 60),
{
- "tpidr",
+ "tpidruro",
nullptr,
4,
0,
eEncodingUint,
eFormatHex,
{LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_REGNUM_GENERIC_TP,
- LLDB_INVALID_REGNUM, tls_tpidr},
+ LLDB_INVALID_REGNUM, tls_tpidruro},
nullptr,
nullptr,
nullptr,
>From 8555dc49fd117b87588093696f67d432fc23242a Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Wed, 18 Feb 2026 23:33:41 -0800
Subject: [PATCH 8/9] fixup! Add a distinct offset for tpidruro
---
.../Linux/NativeRegisterContextLinux_arm.cpp | 14 +++++++++++---
.../Process/Linux/NativeRegisterContextLinux_arm.h | 6 +++---
.../Process/Utility/RegisterInfoPOSIX_arm.cpp | 8 +++++---
.../Process/Utility/RegisterInfoPOSIX_arm.h | 4 ++++
.../Plugins/Process/Utility/RegisterInfos_arm.h | 6 +++++-
.../Common/arm/RegisterContextWindows_arm.cpp | 1 +
6 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
index 21ba06c902d33..c83cea2bbf5bd 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp
@@ -30,7 +30,7 @@
#include "lldb/Host/linux/Ptrace.h"
#include <asm/ptrace.h>
-#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
@@ -76,10 +76,10 @@ NativeRegisterContextLinux_arm::NativeRegisterContextLinux_arm(
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));
- m_tpidr = 0;
// 16 is just a maximum value, query hardware for actual watchpoint count
m_max_hwp_supported = 16;
@@ -126,7 +126,7 @@ NativeRegisterContextLinux_arm::ReadRegister(const RegisterInfo *reg_info,
} else if (IsTLS(reg)) {
error = ReadTLS();
if (error.Success())
- reg_value.SetUInt32(m_tpidr);
+ reg_value.SetUInt32(m_tls.tpidruro);
return error;
} else {
uint32_t full_reg = reg;
@@ -229,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;
}
@@ -278,6 +284,8 @@ Status NativeRegisterContextLinux_arm::WriteAllRegisterValues(
if (error.Fail())
return error;
+ // Note: writing to a thread pointer register is not implemented.
+
return error;
}
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h
index 2c292ad19b966..3722f667b62c5 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h
@@ -66,14 +66,14 @@ class NativeRegisterContextLinux_arm : public NativeRegisterContextLinux,
size_t GetFPRSize() override { return sizeof(m_fpr); }
- void *GetTLSBuffer() { return &m_tpidr; }
+ void *GetTLSBuffer() { return &m_tls; }
- size_t GetTLSSize() { return sizeof(m_tpidr); }
+ size_t GetTLSSize() { return sizeof(m_tls); }
private:
uint32_t m_gpr_arm[k_num_gpr_registers_arm];
RegisterInfoPOSIX_arm::FPU m_fpr;
- uint32_t m_tpidr;
+ RegisterInfoPOSIX_arm::TLS m_tls;
bool m_refresh_hwdebug_info;
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.cpp
index fbe85a3a92544..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]), \
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
index c6384f64bfff6..0695b0afd858e 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm.h
@@ -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];
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h b/lldb/source/Plugins/Process/Utility/RegisterInfos_arm.h
index afaada679cb79..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
@@ -688,7 +692,7 @@ static RegisterInfo g_register_infos_arm[] = {
"tpidruro",
nullptr,
4,
- 0,
+ TLS_OFFSET,
eEncodingUint,
eFormatHex,
{LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_REGNUM_GENERIC_TP,
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
>From 74ca6254bc708ebdd20eb42e62c68a44578d31df Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Thu, 19 Feb 2026 21:01:13 -0800
Subject: [PATCH 9/9] fixup! add a release note
---
llvm/docs/ReleaseNotes.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 257d962ba8941..8858beaa8eecd 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -195,6 +195,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
+
+* Thread local variables are now supported on Arm Linux if the program being debugged is using glibc.
+
Changes to BOLT
---------------
More information about the lldb-commits
mailing list