llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: firmiana (firmiana402) <details> <summary>Changes</summary> 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`. --- Full diff: https://github.com/llvm/llvm-project/pull/216463.diff 6 Files Affected: - (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.cpp (+112-5) - (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86.h (+11) - (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp (+2) - (added) lldb/test/API/linux/i386/tls-address/Makefile (+3) - (added) lldb/test/API/linux/i386/tls-address/TestI386TLSAddress.py (+46) - (added) lldb/test/API/linux/i386/tls-address/main.c (+6) ``````````diff 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 ®ister_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 ®_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 ®_info) const { + return GetRegisterInfoInterface().GetTargetArchitecture().GetMachine() == + llvm::Triple::x86 && + reg_info.kinds[lldb::eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_TP; +} + +Status +NativeRegisterContextLinux_x86::ReadThreadPointer(RegisterValue ®_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 ®_info) const; + + Status ReadThreadPointer(RegisterValue ®_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 +} `````````` </details> https://github.com/llvm/llvm-project/pull/216463 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
