https://github.com/firmiana402 created 
https://github.com/llvm/llvm-project/pull/216463

Fixes #211473.

On i386 Linux, `gs` contains a segment selector rather than the thread-pointer 
address. LLDB's i386 native register context did not expose 
`LLDB_REGNUM_GENERIC_TP`, so `DynamicLoaderPOSIXDYLD::GetThreadLocalData` could 
not locate the current thread's DTV and module TLS block. As a result, valid 
`DW_OP_form_tls_address` and `DW_OP_GNU_push_tls_address` expressions failed to 
evaluate.

Mapping the existing `gs` register to the generic thread pointer would be 
incorrect because it would expose the selector as an address. The selector's 
GDT entry must instead be resolved by the Linux process tracing the inferior.

## Fix

Add a Linux-only synthetic `gs_base` register to 
`NativeRegisterContextLinux_x86` and map it to `LLDB_REGNUM_GENERIC_TP`. 
Reading the original `gs` register continues to return the selector. Reading 
`gs_base` reads that selector, derives the GDT entry number, and uses 
`PTRACE_GET_THREAD_AREA` to obtain the descriptor's `base_addr`.

This keeps the Linux-specific ptrace operation in `lldb-server`, which owns the 
native process, while reusing the existing generic-thread-pointer and 
register-read paths. It avoids adding an i386 special case to the client-side 
`Thread` or `RegisterContext` abstractions and does not require a new 
gdb-remote packet.

The synthetic register is inserted virtually before the optional AVX and MPX 
registers so the available user registers remain a contiguous prefix. The 
shared i386 register table, its native `eRegisterKindLLDB` values, and existing 
register packet offsets remain unchanged. The native context translates public 
register-info indices around the synthetic entry, including accesses to the 
internal debug registers.

Because the synthetic register is not part of the unchanged static register-set 
arrays, it has no native register-set name. 
`GDBRemoteCommunicationServerLLGS::Handle_qRegisterInfo` now assigns registers 
without a static set to `general`, matching the existing target XML fallback. 
This keeps the `qRegisterInfo` fallback valid when target XML is unavailable; 
it only completes the register description and does not participate in 
calculating the TLS address.

The implementation supports the standard Linux i386 GDT-based TLS model. Null 
GDT selectors produce a zero thread pointer, LDT-backed selectors are reported 
as unsupported, and `PTRACE_GET_THREAD_AREA` failures are propagated.

## Testing

Add an API test that builds an i386 Linux inferior with a TLS variable and 
verifies that LLDB evaluates it through the existing TLS-expression path. The 
test also checks that a complete register read reports no unavailable registers 
and that `qRegisterInfo` advertises `gs_base` with `generic:tp` and 
`set:general`.


>From 5c4186701dd83e68e36ac1ebfc565e2f1edc6ef1 Mon Sep 17 00:00:00 2001
From: firmiana402 <[email protected]>
Date: Sat, 15 Aug 2026 15:14:12 +0800
Subject: [PATCH] [lldb] Support i386 Linux TLS thread pointers

---
 .../Linux/NativeRegisterContextLinux_x86.cpp  | 117 +++++++++++++++++-
 .../Linux/NativeRegisterContextLinux_x86.h    |  11 ++
 .../GDBRemoteCommunicationServerLLGS.cpp      |   2 +
 lldb/test/API/linux/i386/tls-address/Makefile |   3 +
 .../i386/tls-address/TestI386TLSAddress.py    |  46 +++++++
 lldb/test/API/linux/i386/tls-address/main.c   |   6 +
 6 files changed, 180 insertions(+), 5 deletions(-)
 create mode 100644 lldb/test/API/linux/i386/tls-address/Makefile
 create mode 100644 lldb/test/API/linux/i386/tls-address/TestI386TLSAddress.py
 create mode 100644 lldb/test/API/linux/i386/tls-address/main.c

diff --git 
a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.cpp 
b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.cpp
index a7af62e89c1dc..6d26ababe3cc1 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.cpp
@@ -9,14 +9,18 @@
 #if defined(__i386__) || defined(__x86_64__)
 
 #include "NativeRegisterContextLinux_x86.h"
+#include "Plugins/Process/Linux/NativeProcessLinux.h"
 #include "Plugins/Process/Linux/NativeThreadLinux.h"
 #include "Plugins/Process/Utility/RegisterContextLinux_i386.h"
 #include "Plugins/Process/Utility/RegisterContextLinux_x86_64.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Host/linux/Ptrace.h"
 #include "lldb/Utility/DataBufferHeap.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/RegisterValue.h"
 #include "lldb/Utility/Status.h"
+#include <algorithm>
+#include <asm/ldt.h>
 #include <cpuid.h>
 #include <linux/elf.h>
 #include <optional>
@@ -41,6 +45,10 @@ static inline int get_cpuid_count(unsigned int __leaf,
 using namespace lldb_private;
 using namespace lldb_private::process_linux;
 
+// Linux exposes the i386 thread pointer as a synthetic register between the
+// always-available FPU registers and the optional extended register sets.
+constexpr uint32_t k_i386_thread_pointer_index = k_first_avx_i386;
+
 // x86 32-bit general purpose registers.
 static const uint32_t g_gpr_regnums_i386[] = {
     lldb_eax_i386,      lldb_ebx_i386,    lldb_ecx_i386, lldb_edx_i386,
@@ -304,8 +312,29 @@ 
NativeRegisterContextLinux_x86::NativeRegisterContextLinux_x86(
       m_reg_info(), m_gpr_x86_64() {
   // Set up data about ranges of valid registers.
   switch (target_arch.GetMachine()) {
-  case llvm::Triple::x86:
-    m_reg_info.num_registers = k_num_registers_i386;
+  case llvm::Triple::x86: {
+    const RegisterInfoInterface &register_info = GetRegisterInfoInterface();
+    // Place the synthetic value after all existing user data so existing
+    // g-packet offsets remain unchanged.
+    uint32_t byte_offset = 0;
+    for (uint32_t i = 0; i < register_info.GetUserRegisterCount(); ++i) {
+      const RegisterInfo &info = register_info.GetRegisterInfo()[i];
+      byte_offset = std::max(byte_offset, info.byte_offset + info.byte_size);
+    }
+    m_i386_thread_pointer_info = {"gs_base",
+                                  nullptr,
+                                  sizeof(uint32_t),
+                                  byte_offset,
+                                  lldb::eEncodingUint,
+                                  lldb::eFormatHex,
+                                  {LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM,
+                                   LLDB_REGNUM_GENERIC_TP, LLDB_INVALID_REGNUM,
+                                   LLDB_INVALID_REGNUM},
+                                  nullptr,
+                                  nullptr,
+                                  nullptr};
+
+    m_reg_info.num_registers = k_num_registers_i386 + 1;
     m_reg_info.num_gpr_registers = k_num_gpr_registers_i386;
     m_reg_info.num_fpr_registers = k_num_fpr_registers_i386;
     m_reg_info.num_avx_registers = k_num_avx_registers_i386;
@@ -329,6 +358,7 @@ 
NativeRegisterContextLinux_x86::NativeRegisterContextLinux_x86(
     m_reg_info.last_dr = lldb_dr7_i386;
     m_reg_info.gpr_flags = lldb_eflags_i386;
     break;
+  }
   case llvm::Triple::x86_64:
     m_reg_info.num_registers = x86_64_with_base::k_num_registers;
     m_reg_info.num_gpr_registers = x86_64_with_base::k_num_gpr_registers;
@@ -372,6 +402,27 @@ 
NativeRegisterContextLinux_x86::NativeRegisterContextLinux_x86(
   m_fctrl_offset_in_userarea = reg_info_fctrl->byte_offset;
 }
 
+uint32_t NativeRegisterContextLinux_x86::GetRegisterCount() const {
+  uint32_t count = NativeRegisterContextRegisterInfo::GetRegisterCount();
+  if (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() ==
+      llvm::Triple::x86)
+    ++count;
+  return count;
+}
+
+const RegisterInfo *
+NativeRegisterContextLinux_x86::GetRegisterInfoAtIndex(uint32_t reg) const {
+  if (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() ==
+      llvm::Triple::x86) {
+    if (reg == k_i386_thread_pointer_index)
+      return &m_i386_thread_pointer_info;
+    // Translate the public index back to the unchanged underlying table.
+    if (reg > k_i386_thread_pointer_index)
+      --reg;
+  }
+  return NativeRegisterContextRegisterInfo::GetRegisterInfoAtIndex(reg);
+}
+
 // CONSIDER after local and llgs debugging are merged, register set support can
 // be moved into a base x86-64 class with IsRegisterSetAvailable made virtual.
 uint32_t NativeRegisterContextLinux_x86::GetRegisterSetCount() const {
@@ -391,6 +442,9 @@ uint32_t 
NativeRegisterContextLinux_x86::GetUserRegisterCount() const {
     if (set)
       count += set->num_registers;
   }
+  if (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() ==
+      llvm::Triple::x86)
+    ++count;
   return count;
 }
 
@@ -422,6 +476,9 @@ NativeRegisterContextLinux_x86::ReadRegister(const 
RegisterInfo *reg_info,
     return error;
   }
 
+  if (IsThreadPointer(*reg_info))
+    return ReadThreadPointer(reg_value);
+
   const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
   if (reg == LLDB_INVALID_REGNUM) {
     // This is likely an internal register for lldb use only and should not be
@@ -447,7 +504,7 @@ NativeRegisterContextLinux_x86::ReadRegister(const 
RegisterInfo *reg_info,
       full_reg = reg_info->invalidate_regs[0];
     }
 
-    error = ReadRegisterRaw(full_reg, reg_value);
+    error = ReadRegisterRaw(GetRegisterInfoIndex(full_reg), reg_value);
 
     if (error.Success()) {
       // If our read was not aligned (for ah,bh,ch,dh), shift our returned
@@ -585,6 +642,9 @@ Status NativeRegisterContextLinux_x86::WriteRegister(
     const RegisterInfo *reg_info, const RegisterValue &reg_value) {
   assert(reg_info && "reg_info is null");
 
+  if (IsThreadPointer(*reg_info))
+    return Status::FromErrorString("the i386 thread pointer is read-only");
+
   const uint32_t reg_index = reg_info->kinds[lldb::eRegisterKindLLDB];
   if (reg_index == LLDB_INVALID_REGNUM)
     return Status::FromErrorStringWithFormat(
@@ -594,7 +654,7 @@ Status NativeRegisterContextLinux_x86::WriteRegister(
   UpdateXSTATEforWrite(reg_index);
 
   if (IsGPR(reg_index) || IsDR(reg_index))
-    return WriteRegisterRaw(reg_index, reg_value);
+    return WriteRegisterRaw(GetRegisterInfoIndex(reg_index), reg_value);
 
   if (IsFPR(reg_index) || IsAVX(reg_index) || IsMPX(reg_index)) {
     if (reg_info->encoding == lldb::eEncodingVector) {
@@ -1002,6 +1062,53 @@ bool NativeRegisterContextLinux_x86::IsMPX(uint32_t 
reg_index) const {
           reg_index <= m_reg_info.last_mpxc);
 }
 
+bool NativeRegisterContextLinux_x86::IsThreadPointer(
+    const RegisterInfo &reg_info) const {
+  return GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() ==
+             llvm::Triple::x86 &&
+         reg_info.kinds[lldb::eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_TP;
+}
+
+Status
+NativeRegisterContextLinux_x86::ReadThreadPointer(RegisterValue &reg_value) {
+  RegisterValue selector;
+  Status error = ReadRegisterRaw(lldb_gs_i386, selector);
+  if (error.Fail())
+    return error;
+
+  const uint32_t gs = selector.GetAsUInt32();
+  // Selectors 0 through 3 refer to the null GDT descriptor with different
+  // requested privilege levels.
+  if (gs < 4) {
+    reg_value.SetUInt32(0);
+    return Status();
+  }
+  if (gs & 4)
+    return Status::FromErrorString(
+        "cannot read the i386 thread pointer from an LDT selector");
+
+  const uintptr_t entry_number = gs >> 3;
+  user_desc descriptor = {};
+  error = NativeProcessLinux::PtraceWrapper(
+      PTRACE_GET_THREAD_AREA, m_thread.GetID(),
+      reinterpret_cast<void *>(entry_number), &descriptor, sizeof(descriptor));
+  if (error.Fail())
+    return error;
+
+  reg_value.SetUInt32(descriptor.base_addr);
+  return Status();
+}
+
+uint32_t
+NativeRegisterContextLinux_x86::GetRegisterInfoIndex(uint32_t lldb_reg) const {
+  // Raw register helpers take register-info indices rather than LLDB numbers.
+  if (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() ==
+          llvm::Triple::x86 &&
+      lldb_reg >= k_i386_thread_pointer_index)
+    return lldb_reg + 1;
+  return lldb_reg;
+}
+
 bool NativeRegisterContextLinux_x86::CopyXSTATEtoMPX(uint32_t reg) {
   if (!IsMPX(reg))
     return false;
@@ -1077,7 +1184,7 @@ const RegisterInfo 
*NativeRegisterContextLinux_x86::GetDR(int num) const {
   assert(num >= 0 && num <= 7);
   switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) {
   case llvm::Triple::x86:
-    return GetRegisterInfoAtIndex(lldb_dr0_i386 + num);
+    return GetRegisterInfoAtIndex(GetRegisterInfoIndex(lldb_dr0_i386 + num));
   case llvm::Triple::x86_64:
     return GetRegisterInfoAtIndex(x86_64_with_base::lldb_dr0 + num);
   default:
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.h 
b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.h
index 905275652af59..8fb08f616926c 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.h
@@ -31,6 +31,10 @@ class NativeRegisterContextLinux_x86
   NativeRegisterContextLinux_x86(const ArchSpec &target_arch,
                                     NativeThreadProtocol &native_thread);
 
+  uint32_t GetRegisterCount() const override;
+
+  const RegisterInfo *GetRegisterInfoAtIndex(uint32_t reg) const override;
+
   uint32_t GetRegisterSetCount() const override;
 
   const RegisterSet *GetRegisterSet(uint32_t set_index) const override;
@@ -106,6 +110,7 @@ class NativeRegisterContextLinux_x86
   YMM m_ymm_set;
   MPX m_mpx_set;
   RegInfo m_reg_info;
+  RegisterInfo m_i386_thread_pointer_info = {};
   uint64_t m_gpr_x86_64[x86_64_with_base::k_num_gpr_registers];
   uint32_t m_fctrl_offset_in_userarea;
 
@@ -132,6 +137,12 @@ class NativeRegisterContextLinux_x86
 
   bool IsMPX(uint32_t reg_index) const;
 
+  bool IsThreadPointer(const RegisterInfo &reg_info) const;
+
+  Status ReadThreadPointer(RegisterValue &reg_value);
+
+  uint32_t GetRegisterInfoIndex(uint32_t lldb_reg) const;
+
   void UpdateXSTATEforWrite(uint32_t reg_index);
 
   RegisterContextLinux_x86 &GetRegisterInfo() const {
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 2e824282079b4..ef7cc1bf4e413 100644
--- 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -2188,6 +2188,8 @@ GDBRemoteCommunicationServerLLGS::Handle_qRegisterInfo(
       reg_context.GetRegisterSetNameForRegisterAtIndex(reg_index);
   if (register_set_name)
     response << "set:" << register_set_name << ';';
+  else
+    response << "set:general;";
 
   if (reg_info->kinds[RegisterKind::eRegisterKindEHFrame] !=
       LLDB_INVALID_REGNUM)
diff --git a/lldb/test/API/linux/i386/tls-address/Makefile 
b/lldb/test/API/linux/i386/tls-address/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/linux/i386/tls-address/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/linux/i386/tls-address/TestI386TLSAddress.py 
b/lldb/test/API/linux/i386/tls-address/TestI386TLSAddress.py
new file mode 100644
index 0000000000000..47cb2b3243617
--- /dev/null
+++ b/lldb/test/API/linux/i386/tls-address/TestI386TLSAddress.py
@@ -0,0 +1,46 @@
+"""Test evaluating TLS locations in a 32-bit x86 Linux inferior."""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class I386TLSAddressTestCase(TestBase):
+    @requireLinux
+    @skipIf(archs=no_match(["x86_64", "i386", "i686"]))
+    @skipUnlessCompilerSupports("-m32")
+    def test(self):
+        self.build(dictionary={"CFLAGS_EXTRAS": "-m32"})
+
+        target, process, thread, breakpoint = 
lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.c")
+        )
+        self.assertEqual(target.GetAddressByteSize(), 4)
+        self.expect("target variable tls_value", substrs=["(int) tls_value = 
43"])
+        self.expect("register read --all", substrs=["gs_base ="])
+        self.expect(
+            "register read --all",
+            matching=False,
+            substrs=["registers were unavailable"],
+        )
+
+        register_sets = thread.GetFrameAtIndex(0).GetRegisters()
+        register_count = sum(
+            register_sets[i].GetNumChildren() for i in 
range(register_sets.GetSize())
+        )
+        # Check the fallback used by clients that cannot consume target XML.
+        gs_base_response = None
+        for reg_index in range(register_count):
+            result = lldb.SBCommandReturnObject()
+            self.dbg.GetCommandInterpreter().HandleCommand(
+                f"process plugin packet send qRegisterInfo{reg_index:x}", 
result
+            )
+            self.assertTrue(result.Succeeded(), result.GetError())
+            if "name:gs_base;" in result.GetOutput():
+                gs_base_response = result.GetOutput()
+                break
+
+        self.assertIsNotNone(gs_base_response)
+        self.assertIn("generic:tp;", gs_base_response)
+        self.assertIn("set:general;", gs_base_response)
diff --git a/lldb/test/API/linux/i386/tls-address/main.c 
b/lldb/test/API/linux/i386/tls-address/main.c
new file mode 100644
index 0000000000000..52f6001bb325e
--- /dev/null
+++ b/lldb/test/API/linux/i386/tls-address/main.c
@@ -0,0 +1,6 @@
+__thread int tls_value = 42;
+
+int main(void) {
+  ++tls_value;
+  return tls_value; // break here
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to