From: Andrew Burgess <aburgess@redhat.com>
To: "Schimpe, Christina" <christina.schimpe@intel.com>,
"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
Cc: Eli Zaretskii <eliz@gnu.org>, Keith Seitz <keiths@redhat.com>
Subject: RE: [PATCHv4] gdb: include NT_I386_TLS note in generated core files
Date: Thu, 13 Nov 2025 19:35:08 +0000 [thread overview]
Message-ID: <87ldk9zpxv.fsf@redhat.com> (raw)
In-Reply-To: <PH0PR11MB763600D6F65E6AD41BC7E156F9CCA@PH0PR11MB7636.namprd11.prod.outlook.com>
"Schimpe, Christina" <christina.schimpe@intel.com> writes:
> Hi Andrew,
>
> I only have some optional comments and found a few nits, please see below.
>
>> -----Original Message-----
>> From: Andrew Burgess <aburgess@redhat.com>
>> Sent: Mittwoch, 5. November 2025 14:57
>> To: gdb-patches@sourceware.org
>> Cc: Andrew Burgess <aburgess@redhat.com>; Schimpe, Christina
>> <christina.schimpe@intel.com>; Eli Zaretskii <eliz@gnu.org>
>> Subject: [PATCHv4] gdb: include NT_I386_TLS note in generated core files
>>
>> In v4:
>>
>> - Updated based on Christina's feedback.
>>
>> - Removed excessive use of 'struct' throughout the patch.
>>
>> - Fixed whitespace issue in comment.
>>
>> - Renamed test to remove 'linux-', this is inline with all the other
>> Linux specific tests, which don't have 'linux' in their name.
>>
>> - Changed 'return -1' to 'return' in the test script.
>>
>> - Updated test to handle case where kernel produced core file
>> doesn't work, e.g. system is configured to not allow the kernel to
>> dump core. Test will now report 'unsupported'.
>>
>> - Updated gdbserver changes so fetch_tls_area_register and
>> store_tls_area_register now return 'void'. These functions have
>> been updated to give a warning if something goes wrong.
>>
>> - Rebased onto something more recent, reran the patch specific test
>> only as the changes above are all minor and shouldn't impact
>> anything outside of this change.
>>
>> In v3:
>>
>> - Fixed doc issue that Eli pointed out.
>>
>> - Rebased to current HEAD, no merged conflicts.
>>
>> In v2:
>>
>> - Rebased to current HEAD, resolved merge conflicts related to
>> recent i386 register changes.
>>
>> - Updated test to take account of recent clean_restart changes.
>>
>> - Retested.
>>
>> ---
>>
>> This commit extends GDB for x86/Linux to include the NT_I386_TLS note in
>> generated core files (i.e. created with `generate-core-file` or `gcore` command).
>> This note contains the 3 per-thread TLS related GDT (global descriptor table)
>> entries, and is present for i386 binaries, or those compiled on x86-64 with -
>> m32.
>>
>> The approach I have taken to achieve this, is to make the 3 GDT entries
>> available within 3 new registers. I added these registers to the
>> org.gnu.gdb.i386.linux target description feature, as this feature seemed
>> perfectly named. As the new registers are optional I don't see any harm in
>> extending this existing feature. I did consider adding a new feature with `tls` in
>> the name, but this seemed excessive given the existing feature.
>>
>> Which GDT entries are used for TLS varies between i386 and x86-64 running in
>> 32-bit mode. As such the registers are named with suffixes 0, 1, and 2, and it is
>> left to GDB or gdbserver, to find the correct GDT entries (based on the precise
>> target) and place the contents into these registers.
>>
>> With this done, adding the relevant regset is sufficient to get the tls contents
>> emitted as a core file note. Support for emitting the note into the generated
>> core file relies on some BFD changes which were made in an earlier commit:
>>
>> commit ea6ec00ff4520895735e4913cb90c933c7296f04
>> Date: Fri Jul 25 19:51:58 2025 +0100
>>
>> bfd: support for NT_386_TLS notes
>>
>> The three new registers are readable and writable. Writing to one of the new
>> registers will update the relevant kernel GDT entry.
>>
>> Each TLS GDT is represented by a 'struct user_desc' (see 'man 2
>> get_thread_area' for details), the first 4 bytes of each 'user_desc'
>> is the 'entry_number' field, this is the index of the GDT within the kernel, and
>> cannot be modified. Attempts to write to this region of the register will be
>> ignored, but will not give an error.
>>
>> I did consider not including this part of the user_desc within the register value,
>> but this becomes difficult when we consider remote targets, GDB would then
>> need to figure out what these indexes were so that the core file note could be
>> generated. Sure, we probably could figure the correct index values out, but I
>> figure, why bother, we can just pass them through in the register and know for
>> certain that we have the correct values.
>>
>> For testing, there's a new test that covers the basic functionality, including
>> read/write access to the new registers, and checking that the NT_386_TLS note
>> is added to the core file, and that the note contents can be read by GDB.
>>
>> I also manually tested opening a core file generated from an old GDB (so no
>> NT_386_TLS notes) using a GDB with this patch. This works fine, the new tls
>> registers are not created as the NT_GDB_TDESC note (the target description)
>> doesn't include the new registers.
>>
>> Out of interest I also patched an old version of GDB to avoid creating the
>> NT_GDB_TDESC, and created a core file. This core file contained neither the
>> NT_386_TLS nor NT_GDB_TDESC. When opening this core file with a patched
>> GDB, the new registers do show up, but their contents are given as
>> <unavailable>, which is exactly what we'd expect, GDB builds a target
>> description based on the architecture, the architecture says these registers
>> should exist, but they are missing from the core file, hence, <unavailable>.
>>
>> I also tested using a patched GDB with an old version of gdbserver, the new
>> registers don't show up as the old gdbserver doesn't send them in its target
>> description. And a core file created using the gcore command in such a setup
>> leaves no NT_386_TLS notes added, which is what we'd expect.
>>
>> And I also tested a new gdbserver running with an old version of GDB.
>> As the new tls registers are now mentioned in the target description, then
>> obviously, the old GDB does see the registers, and present them to the user,
>> however GDB doesn't know how to use these registers to create a NT_386_TLS,
>> so that note isn't added to any core files.
>> Also, while a new GDB places the tls registers into the 'system'
>> group, an old GDB doesn't do this, so the registers end up in the 'general'
>> group by default. This means they show up within 'info registers' output. This
>> isn't ideal, but there's not much that can be done about this.
>>
>> Overall, I feel the combinations of old and new tools has been tested, and the
>> behaviours are what we'd want or expect.
>>
>> I'm tagging this commit with PR gdb/15591, even though this patch isn't
>> directly related. That bug is for improving GDB's testing of TLS support in core
>> files. The test in this commit does do some very simple reading of a TLS
>> variable, but there's only two threads, and one TLS variable, so it's not
>> extensive. Additionally, the test in this commit is x86 only, so this should not be
>> considered a full resolution to that bug. But still, it's something.
>>
>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=15591
>>
>> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
>> ---
>> gdb/NEWS | 4 +
>> gdb/amd64-linux-nat.c | 26 +-
>> gdb/doc/gdb.texinfo | 11 +-
>> gdb/features/i386/32bit-linux.c | 7 +
>> gdb/features/i386/32bit-linux.xml | 5 +
>> gdb/i386-linux-nat.c | 21 ++
>> gdb/i386-linux-tdep.c | 174 +++++++++++-
>> gdb/i386-linux-tdep.h | 15 +
>> gdb/i386-tdep.h | 5 +
>> gdb/nat/amd64-linux.h | 29 ++
>> gdb/nat/i386-linux.h | 10 +
>> gdb/nat/x86-linux.c | 44 +++
>> gdb/nat/x86-linux.h | 15 +
>> gdb/testsuite/gdb.arch/i386-tls-regs.c | 74 +++++
>> gdb/testsuite/gdb.arch/i386-tls-regs.exp | 335 +++++++++++++++++++++++
>> gdb/x86-linux-nat.c | 67 +++++
>> gdb/x86-linux-nat.h | 15 +
>> gdbserver/linux-x86-low.cc | 105 +++++++
>> 18 files changed, 955 insertions(+), 7 deletions(-) create mode 100644
>> gdb/nat/amd64-linux.h create mode 100644 gdb/testsuite/gdb.arch/i386-tls-
>> regs.c
>> create mode 100644 gdb/testsuite/gdb.arch/i386-tls-regs.exp
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 097d8da350b..9c0ebd98b18 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -162,6 +162,10 @@ qExecAndArgs
>> In systems that don't support linker namespaces, or if the inferior hasn't
>> started yet, these always return the integer 0.
>>
>> +* The 'org.gnu.gdb.i386.linux' target description feature can now
>> + contain three additional registers which provide access to the TLS
>> + related GDT entries on i386 (and x86-64 when compiling with -m32).
>> +
>> * Add record full support for rv64gc architectures
>>
>> * Debugging Linux programs that use AArch64 Guarded Control Stacks is now
>> diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c index
>> 96acfda7d00..53a92f89957 100644
>> --- a/gdb/amd64-linux-nat.c
>> +++ b/gdb/amd64-linux-nat.c
>> @@ -38,6 +38,9 @@
>> #include "x86-linux-nat.h"
>> #include "nat/linux-ptrace.h"
>> #include "nat/amd64-linux-siginfo.h"
>> +#include "nat/i386-linux.h"
>> +
>> +#include <asm/ldt.h>
>>
>> /* This definition comes from prctl.h. Kernels older than 2.5.64
>> do not have it. */
>> @@ -89,7 +92,8 @@ static int amd64_linux_gregset32_reg_offset[] =
>> -1, /* PKEYS register PKRU */
>> -1, /* SSP register. */
>> -1, -1, /* fs/gs base registers. */
>> - ORIG_RAX * 8 /* "orig_eax" */
>> + ORIG_RAX * 8, /* "orig_eax" */
>> + -1, -1, -1, /* TLS GDT regs: i386_tls_gdt_0...2. */
>> };
>>
>>
>>
>>
>> @@ -236,7 +240,13 @@ amd64_linux_nat_target::fetch_registers (struct
>> regcache *regcache, int regnum)
>>
>> if (regnum == -1 || !amd64_native_gregset_supplies_p (gdbarch, regnum))
>> {
>> - elf_fpregset_t fpregs;
>> + if (regnum == -1 || i386_is_tls_regnum_p (regnum))
>> + {
>> + if (tdep->i386_linux_tls)
>> + i386_fetch_tls_regs (regcache, tid, regnum);
>> + if (regnum != -1)
>> + return;
>> + }
>>
>> if (have_ptrace_getregset == TRIBOOL_TRUE)
>> {
>> @@ -266,6 +276,8 @@ amd64_linux_nat_target::fetch_registers (struct
>> regcache *regcache, int regnum)
>> }
>> else
>> {
>> + elf_fpregset_t fpregs;
>> +
>> if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0)
>> perror_with_name (_("Couldn't get floating point status"));
>>
>> @@ -308,7 +320,13 @@ amd64_linux_nat_target::store_registers (struct
>> regcache *regcache, int regnum)
>>
>> if (regnum == -1 || !amd64_native_gregset_supplies_p (gdbarch, regnum))
>> {
>> - elf_fpregset_t fpregs;
>> + if (regnum == -1 || i386_is_tls_regnum_p (regnum))
>> + {
>> + if (tdep->i386_linux_tls)
>> + i386_store_tls_regs (regcache, tid, regnum);
>> + if (regnum != -1)
>> + return;
>> + }
>>
>> if (have_ptrace_getregset == TRIBOOL_TRUE)
>> {
>> @@ -337,6 +355,8 @@ amd64_linux_nat_target::store_registers (struct
>> regcache *regcache, int regnum)
>> }
>> else
>> {
>> + elf_fpregset_t fpregs;
>> +
>> if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0)
>> perror_with_name (_("Couldn't get floating point status"));
>>
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
>> 821f3ed4b05..b8b70af931a 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -50311,8 +50311,15 @@ i386 Features
>> @samp{ymm0h} through @samp{ymm15h} for amd64 @end itemize
>>
>> -The @samp{org.gnu.gdb.i386.linux} feature is optional. It should -describe a
>> single register, @samp{orig_eax}.
>> +The @samp{org.gnu.gdb.i386.linux} feature is optional. If the feature
>> +is present, then it should describe the 32 bit register, @samp{orig_eax}.
>> +
>> +Additionally, the @samp{org.gnu.gdb.i386.linux} feature can optionally
>> +contain three 128 bit registers called @samp{i386_tls_gdt_0},
>> +@samp{i386_tls_gdt_1}, and @samp{i386_tls_gdt_2}. Each of these
>> +registers contains one 16 byte @samp{struct user_desc} (see @kbd{man
>> +2 get_thread_area}) object which describes one of the three TLS related
>> +GDT entries.
>>
>> The @samp{org.gnu.gdb.i386.segments} feature is optional. It should
>> describe two system registers: @samp{fs_base} and @samp{gs_base}.
>> diff --git a/gdb/features/i386/32bit-linux.c b/gdb/features/i386/32bit-linux.c
>> index 3289f07d332..6fba8a6880b 100644
>> --- a/gdb/features/i386/32bit-linux.c
>> +++ b/gdb/features/i386/32bit-linux.c
>> @@ -9,7 +9,14 @@ create_feature_i386_32bit_linux (struct target_desc
>> *result, long regnum)
>> struct tdesc_feature *feature;
>>
>> feature = tdesc_create_feature (result, "org.gnu.gdb.i386.linux");
>> + tdesc_type *element_type;
>> + element_type = tdesc_named_type (feature, "uint32");
>> + tdesc_create_vector (feature, "i386_tls_gdt_reg", element_type, 4);
>> +
>> regnum = 41;
>> tdesc_create_reg (feature, "orig_eax", regnum++, 1, NULL, 32, "int");
>> + tdesc_create_reg (feature, "i386_tls_gdt_0", regnum++, 1, "system",
>> + 128, "i386_tls_gdt_reg"); tdesc_create_reg (feature,
>> + "i386_tls_gdt_1", regnum++, 1, "system", 128, "i386_tls_gdt_reg");
>> + tdesc_create_reg (feature, "i386_tls_gdt_2", regnum++, 1, "system",
>> + 128, "i386_tls_gdt_reg");
>> return regnum;
>> }
>> diff --git a/gdb/features/i386/32bit-linux.xml b/gdb/features/i386/32bit-
>> linux.xml
>> index 0bfc0bc5563..7de3198b7da 100644
>> --- a/gdb/features/i386/32bit-linux.xml
>> +++ b/gdb/features/i386/32bit-linux.xml
>> @@ -8,4 +8,9 @@
>> <!DOCTYPE feature SYSTEM "gdb-target.dtd"> <feature
>> name="org.gnu.gdb.i386.linux">
>> <reg name="orig_eax" bitsize="32" type="int" regnum="41"/>
>> +
>> + <vector id="i386_tls_gdt_reg" type="uint32" count="4" /> <reg
>> + name="i386_tls_gdt_0" bitsize="128" type="i386_tls_gdt_reg"
>> + group="system" /> <reg name="i386_tls_gdt_1" bitsize="128"
>> + type="i386_tls_gdt_reg" group="system" /> <reg name="i386_tls_gdt_2"
>> + bitsize="128" type="i386_tls_gdt_reg" group="system" />
>> </feature>
>> diff --git a/gdb/i386-linux-nat.c b/gdb/i386-linux-nat.c index
>> 4af06c448a8..f86005e30e8 100644
>> --- a/gdb/i386-linux-nat.c
>> +++ b/gdb/i386-linux-nat.c
>> @@ -447,6 +447,8 @@ store_fpxregs (const struct regcache *regcache, int
>> tid, int regno) void i386_linux_nat_target::fetch_registers (struct regcache
>> *regcache, int regno) {
>> + gdbarch *gdbarch = regcache->arch (); const i386_gdbarch_tdep *tdep
>> + = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
>> pid_t tid;
>>
>> /* Use the old method of peeking around in `struct user' if the @@ -470,6
>> +472,9 @@ i386_linux_nat_target::fetch_registers (struct regcache *regcache,
>> int regno)
>> zero. */
>> if (regno == -1)
>> {
>> + if (tdep->i386_linux_tls)
>> + i386_fetch_tls_regs (regcache, tid, regno);
>> +
>> fetch_regs (regcache, tid);
>>
>> /* The call above might reset `have_ptrace_getregs'. */ @@ -514,6
>> +519,12 @@ i386_linux_nat_target::fetch_registers (struct regcache
>> *regcache, int regno)
>> return;
>> }
>>
>> + if (tdep->i386_linux_tls && i386_is_tls_regnum_p (regno))
>> + {
>> + i386_fetch_tls_regs (regcache, tid, regno);
>> + return;
>> + }
>> +
>> internal_error (_("Got request for bad register number %d."), regno); }
>>
>> @@ -523,6 +534,8 @@ i386_linux_nat_target::fetch_registers (struct
>> regcache *regcache, int regno) void i386_linux_nat_target::store_registers
>> (struct regcache *regcache, int regno) {
>> + gdbarch *gdbarch = regcache->arch (); const i386_gdbarch_tdep *tdep
>> + = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
>> pid_t tid;
>>
>> /* Use the old method of poking around in `struct user' if the @@ -545,6
>> +558,8 @@ i386_linux_nat_target::store_registers (struct regcache *regcache,
>> int regno)
>> store_fpxregs can fail, and return zero. */
>> if (regno == -1)
>> {
>> + if (tdep->i386_linux_tls)
>> + i386_store_tls_regs (regcache, tid, regno);
>> store_regs (regcache, tid, regno);
>> if (store_xstateregs (regcache, tid, regno))
>> return;
>> @@ -578,6 +593,12 @@ i386_linux_nat_target::store_registers (struct
>> regcache *regcache, int regno)
>> return;
>> }
>>
>> + if (tdep->i386_linux_tls && i386_is_tls_regnum_p (regno))
>> + {
>> + i386_store_tls_regs (regcache, tid, regno);
>> + return;
>> + }
>> +
>> internal_error (_("Got request to store bad register number %d."), regno); }
>>
>>
>>
>> diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c index
>> 20adf169c81..1d6abe2f546 100644
>> --- a/gdb/i386-linux-tdep.c
>> +++ b/gdb/i386-linux-tdep.c
>> @@ -59,7 +59,7 @@ static int
>> i386_linux_register_reggroup_p (struct gdbarch *gdbarch, int regnum,
>> const struct reggroup *group)
>> {
>> - if (regnum == I386_LINUX_ORIG_EAX_REGNUM)
>> + if (regnum == I386_LINUX_ORIG_EAX_REGNUM || i386_is_tls_regnum_p
>> + (regnum))
>> return (group == system_reggroup
>> || group == save_reggroup
>> || group == restore_reggroup);
>> @@ -1047,6 +1047,7 @@ int i386_linux_gregset_reg_offset[] =
>> -1, /* SSP register. */
>> -1, -1, /* fs/gs base registers. */
>> 11 * 4, /* "orig_eax" */
>> + -1, -1, -1, /* TLS GDT regs: i386_tls_gdt_0...2. */
>> };
>>
>> /* Mapping between the general-purpose registers in `struct @@ -1163,15
>> +1164,150 @@ i386_linux_collect_xstateregset (const struct regset *regset,
>> i387_collect_xsave (regcache, regnum, xstateregs, 1); }
>>
>> +/* Within a tdep file we don't have access to system headers. This
>> + structure is a clone of 'struct user_desc' from 'asm/ldt.h' on x86
>> + GNU/Linux systems. See 'see man 2 get_thread_area' on a suitable x86
>> + machine for more details. */
>> +
>> +struct x86_user_desc
>> +{
>> + uint32_t entry_number;
>> + uint32_t base_addr;
>> + uint32_t limit;
>> +
>> + /* In the actual struct, these flags are a series of 1-bit separate
>> + flags. But we don't need that level of insight for the
>> + processing we do in GDB, so just make it a single field. */
>> + uint32_t flags;
>> +};
>> +
>> +/* Supply the 3 tls related registers from BUFFER (length LEN) into
>> + REGCACHE. The REGSET and REGNUM are ignored, all three registers are
>> + always supplied from BUFFER. */
>> +
>> +static void
>> +i386_linux_supply_tls_regset (const regset *regset,
>> + regcache *regcache, int regnum,
>> + const void *buffer, size_t len) {
>> + gdbarch *gdbarch = regcache->arch ();
>> + i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
>> +
>> + if (!tdep->i386_linux_tls)
>> + return;
>> +
>> + gdb_assert (len == sizeof (x86_user_desc) * 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + int tls_regno = I386_LINUX_TLS_GDT_0 + i;
>> +
>> + gdb_assert (regcache->register_size (tls_regno)
>> + == sizeof (x86_user_desc));
>
> You're calling sizeof 4 times here for x86_user_desc. Would it make sense to store it in a variable?
I'll get this fixed.
>
>> +
>> + regcache->raw_supply (tls_regno, buffer);
>> + buffer = static_cast<const x86_user_desc *> (buffer) + 1;
>> + }
>> +}
>> +
>> +/* Collect the 3 tls related registers from REGCACHE, placing the results
>> + in to BUFFER (length LEN). The REGSET and REGNUM are ignored, all three
>> + registers are always collected from REGCACHE. */
>> +
>> +static void
>> +i386_linux_collect_tls_regset (const regset *regset,
>> + const regcache *regcache,
>> + int regnum, void *buffer, size_t len) {
>> + gdbarch *gdbarch = regcache->arch ();
>> + i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
>> +
>> + if (!tdep->i386_linux_tls)
>> + return;
>> +
>> + gdb_assert (len == sizeof (x86_user_desc) * 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + x86_user_desc desc;
>> + int tls_regno = I386_LINUX_TLS_GDT_0 + i;
>> +
>> + gdb_assert (regcache->register_size (tls_regno) == sizeof
>> + (desc));
>> +
>> + regcache->raw_collect (tls_regno, &desc);
>> + memcpy (buffer, &desc, sizeof (desc));
>> + buffer = static_cast<x86_user_desc *> (buffer) + 1;
>> + }
>> +}
>> +
>> /* Register set definitions. */
>>
>> -static const struct regset i386_linux_xstateregset =
>> +static const regset i386_linux_xstateregset =
>> {
>> NULL,
>> i386_linux_supply_xstateregset,
>> i386_linux_collect_xstateregset
>> };
>>
>> +static const regset i386_linux_tls_regset =
>> + {
>> + NULL,
>> + i386_linux_supply_tls_regset,
>> + i386_linux_collect_tls_regset
>> + };
>> +
>> +/* Helper for i386_linux_iterate_over_regset_sections. Should we
>> + visit the NT_386_TLS note? If REGCACHE is NULL then we are reading
>> + the notes from the corefile, so we always visit the note. If
>> + REGCACHE is not NULL, in this case we are creating a corefile. In
>> + this case, we only visit the note if all the TLS registers are
>> + valid, and their base address and limit are not zero, this mirrors
>> + the kernel behaviour where the TLS note is elided when the TLS GDT
>> + entries have not been set.
>> +
>> + Only call for architectures where i386_gdbarch_tdep::i386_linux_tls
>> + is true. */
>> +
>> +static bool
>> +should_visit_i386_tls_note (const regcache *regcache) {
>> + if (regcache == nullptr)
>> + return true;
>> +
>> + /* Check the pre-condition. */
>> + gdbarch *gdbarch = regcache->arch (); i386_gdbarch_tdep *tdep =
>> + gdbarch_tdep<i386_gdbarch_tdep> (gdbarch); gdb_assert
>> + (tdep->i386_linux_tls);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + int tls_regno = I386_LINUX_TLS_GDT_0 + i;
>> +
>> + /* If we failed to read any of the registers then we'll not be
>> + able to emit valid note. */
>> + if (regcache->get_register_status (tls_regno) != REG_VALID)
>> + return false;
>> +
>> + /* As i386_gdbarch_tdep::i386_linux_tls is true, the registers
>> + must be the right size. The flag is only set true when this
>> + condition holds. */
>> + gdb_assert (regcache->register_size (tls_regno)
>> + == sizeof (x86_user_desc));
>> +
>> + /* Read the TLS GDT entry. If it is in use then we want to
>> + write the NT_386_TLS note. */
>> + x86_user_desc ud;
>> + regcache->raw_collect (tls_regno, &ud);
>> + if (ud.base_addr != 0 && ud.limit != 0)
>> + return true;
>> + }
>> +
>> + /* Made it through the loop without finding any in-use TLS related
>> + GDT entries. No point creating the NT_386_TLS note, the kernel
>> + doesn't. */
>> + return false;
>> +}
>> +
>> /* Iterate over core file register note sections. */
>>
>> static void
>> @@ -1193,6 +1329,9 @@ i386_linux_iterate_over_regset_sections (struct
>> gdbarch *gdbarch,
>> cb_data);
>> else
>> cb (".reg2", 108, 108, &i386_fpregset, NULL, cb_data);
>> +
>> + if (tdep->i386_linux_tls && should_visit_i386_tls_note (regcache))
>> + cb (".reg-i386-tls", 48, 48, &i386_linux_tls_regset, nullptr,
>> + cb_data);
>> }
>>
>> /* Linux kernel shows PC value after the 'int $0x80' instruction even if @@ -
>> 1272,6 +1411,37 @@ i386_linux_init_abi (struct gdbarch_info info, struct
>> gdbarch *gdbarch)
>> if (!valid_p)
>> return;
>>
>> + /* Helper function. Look for TLS_REG_NAME in I386_FEATURE (with the
>> + associated LOCAL_TDESC_DATA), and if the register is found assign it
>> + TLS_REGNO. Return true if the register is found, and it is the size
>> + of 'struct user_desc' (see man 2 get_thread_area), otherwise, return
>> + false. */
>> + static const auto valid_tls_reg
>> + = [] (const tdesc_feature *i386_feature,
>> + tdesc_arch_data *local_tdesc_data,
>> + const char *tls_reg_name, int tls_regno) -> bool
>> + {
>> + static constexpr int required_reg_size
>> + = sizeof (x86_user_desc) * HOST_CHAR_BIT;
>> + return (tdesc_numbered_register (i386_feature, local_tdesc_data,
>> + tls_regno, tls_reg_name)
>> + && (tdesc_register_bitsize (i386_feature, tls_reg_name)
>> + == required_reg_size));
>> + };
>> +
>> + /* Check all the expected tls related registers are found, and are the
>> + correct size. If they are then mark the tls feature as being active
>> + in TDEP. Otherwise, leave the feature as deactivated. */
>> + valid_p = (valid_tls_reg (feature, tdesc_data, "i386_tls_gdt_0",
>> + I386_LINUX_TLS_GDT_0)
>> + && valid_tls_reg (feature, tdesc_data, "i386_tls_gdt_1",
>> + I386_LINUX_TLS_GDT_1)
>> + && valid_tls_reg (feature, tdesc_data, "i386_tls_gdt_2",
>> + I386_LINUX_TLS_GDT_2));
>> +
>> + if (valid_p)
>> + tdep->i386_linux_tls = true;
>> +
>> /* Add the %orig_eax register used for syscall restarting. */
>> set_gdbarch_write_pc (gdbarch, i386_linux_write_pc);
>>
>> diff --git a/gdb/i386-linux-tdep.h b/gdb/i386-linux-tdep.h index
>> 8a1a2447776..e75e53aba95 100644
>> --- a/gdb/i386-linux-tdep.h
>> +++ b/gdb/i386-linux-tdep.h
>> @@ -39,6 +39,13 @@ enum i386_linux_regnum
>> is supposed to restart. */
>> I386_LINUX_ORIG_EAX_REGNUM = I386_NUM_REGS,
>>
>> + /* Register numbers for the three TLS GDT registers. These contain the
>> + 'struct user_desc' (see 'man 2 get_thread_area') values for the three
>> + TLS related Global Descriptor Table entries. */
>> + I386_LINUX_TLS_GDT_0, I386_LINUX_TLS_GDT_1,
>> I386_LINUX_TLS_GDT_2,
>> +
>> /* Total number of registers for GNU/Linux. */
>> I386_LINUX_NUM_REGS
>>
>> @@ -61,4 +68,12 @@ extern bool i386_linux_core_read_x86_xsave_layout
>> (struct gdbarch *gdbarch,
>>
>> extern int i386_linux_gregset_reg_offset[];
>>
>> +/* Return true if REGNUM is one of the 3 tls gdt registers. */
>> +
>> +static inline bool
>> +i386_is_tls_regnum_p (int regnum)
>> +{
>> + return regnum >= I386_LINUX_TLS_GDT_0 && regnum <=
>> +I386_LINUX_TLS_GDT_2; }
>> +
>> #endif /* GDB_I386_LINUX_TDEP_H */
>> diff --git a/gdb/i386-tdep.h b/gdb/i386-tdep.h index
>> cfe0d7383d6..ad355eb597a 100644
>> --- a/gdb/i386-tdep.h
>> +++ b/gdb/i386-tdep.h
>> @@ -240,6 +240,11 @@ struct i386_gdbarch_tdep : gdbarch_tdep_base
>> struct type *i386_zmm_type = nullptr;
>> struct type *i387_ext_type = nullptr;
>>
>> + /* If the registers containing the i386 Linux TLS related global
>> + descriptor table information are available. This is used to decide
>> + whether to add the NT_386_TLS note to the core file or not. */
>> + bool i386_linux_tls = false;
>> +
>> /* Process record/replay target. */
>> /* The map for registers because the AMD64's registers order
>> in GDB is not same as I386 instructions. */ diff --git a/gdb/nat/amd64-
>> linux.h b/gdb/nat/amd64-linux.h new file mode 100644 index
>> 00000000000..1d5bf573829
>> --- /dev/null
>> +++ b/gdb/nat/amd64-linux.h
>> @@ -0,0 +1,29 @@
>> +/* Native-dependent code for GNU/Linux amd64.
>> +
>> + Copyright (C) 2025 Free Software Foundation, Inc.
>> +
>> + This file is part of GDB.
>> +
>> + This program is free software; you can redistribute it and/or modify
>> + it under the terms of the GNU General Public License as published by
>> + the Free Software Foundation; either version 3 of the License, or
>> + (at your option) any later version.
>> +
>> + This program is distributed in the hope that it will be useful,
>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + GNU General Public License for more details.
>> +
>> + You should have received a copy of the GNU General Public License
>> + along with this program. If not, see
>> + <http://www.gnu.org/licenses/>. */
>> +
>> +#ifndef GDB_NAT_AMD64_LINUX_H
>> +#define GDB_NAT_AMD64_LINUX_H
>> +
>> +/* See nat/i386-linux.h for a full description of this constant. This is
>> + the version used when GDB is compiled for amd64, and is running an
>> + executable compiled with -m32. */
>> +
>> +static inline constexpr int i386_initial_tls_gdt = 12;
>> +
>> +#endif /* GDB_NAT_AMD64_LINUX_H */
>> diff --git a/gdb/nat/i386-linux.h b/gdb/nat/i386-linux.h index
>> d80e0f999bf..b99f0a02418 100644
>> --- a/gdb/nat/i386-linux.h
>> +++ b/gdb/nat/i386-linux.h
>> @@ -34,4 +34,14 @@
>> variable. */
>> extern tribool have_ptrace_getfpxregs;
>>
>> +/* This constant defines the first GDT (Global Descriptor Table) entry
>> + that the kernel allocates for holding TLS descriptors. There are three
>> + entries, starting at this index which can be accessed using the
>> + PTRACE_GET_THREAD_AREA and PTRACE_SET_THREAD_AREA ptrace calls.
>> This
>> + constant is only valid for true i386 kernels. For amd64 kernels
>> + running in 32-bit mode (i.e. executables compiled -m32) there is a
>> + different constant, see nat/amd64-linux.h. */
>> +
>> +static inline constexpr int i386_initial_tls_gdt = 6;
>> +
>> #endif /* GDB_NAT_I386_LINUX_H */
>> diff --git a/gdb/nat/x86-linux.c b/gdb/nat/x86-linux.c index
>> 55158268970..8906b7e6104 100644
>> --- a/gdb/nat/x86-linux.c
>> +++ b/gdb/nat/x86-linux.c
>> @@ -28,6 +28,12 @@
>> #include "nat/gdb_ptrace.h"
>> #include <sys/user.h>
>>
>> +#ifndef __x86_64__
>> +#include "nat/i386-linux.h"
>> +#else
>> +#include "nat/amd64-linux.h"
>> +#endif
>> +
>> /* Per-thread arch-specific data we want to keep. */
>>
>> struct arch_lwp_info
>> @@ -185,3 +191,41 @@ x86_check_ssp_support (const int tid)
>>
>> return true;
>> }
>> +
>> +/* See nat/x86-linux.h. */
>> +
>> +bool
>> +i386_ptrace_get_tls_data (int pid, gdb::array_view<user_desc> buffer) {
>> + gdb_assert (buffer.size () == 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + void *addr = (void *) (uintptr_t) (i386_initial_tls_gdt + i);
>> + void *data = buffer.slice (i, 1).data ();
>> +
>> + if (ptrace (PTRACE_GET_THREAD_AREA, pid, addr, data) < 0)
>> + return false;
>> + }
>> +
>> + return true;
>> +}
>> +
>> +/* See nat/x86-linux.h. */
>> +
>> +bool
>> +i386_ptrace_set_tls_data (int pid, gdb::array_view<user_desc> buffer) {
>> + gdb_assert (buffer.size () == 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + void *addr = (void *) (uintptr_t) (i386_initial_tls_gdt + i);
>> + void *data = buffer.slice (i, 1).data ();
>> +
>> + if (ptrace (PTRACE_SET_THREAD_AREA, pid, addr, data) < 0)
>> + return false;
>> + }
>> +
>> + return true;
>> +}
>> diff --git a/gdb/nat/x86-linux.h b/gdb/nat/x86-linux.h index
>> 1783aae05d0..c982a46bf2e 100644
>> --- a/gdb/nat/x86-linux.h
>> +++ b/gdb/nat/x86-linux.h
>> @@ -21,6 +21,7 @@
>> #define GDB_NAT_X86_LINUX_H
>>
>> #include "nat/linux-nat.h"
>> +#include <asm/ldt.h>
>>
>> /* Set whether our local mirror of LWP's debug registers has been
>> changed since the values were last written to the thread. Nonzero @@ -79,4
>> +80,18 @@ extern x86_linux_arch_size x86_linux_ptrace_get_arch_size (int
>> tid);
>>
>> extern bool x86_check_ssp_support (const int tid);
>>
>> +/* Get the three TLS related GDT (Global Descriptor Table) entries from
>> + the kernel for thread PID, placing the results into BUFFER, an array of
>> + length 3. */
>> +
>> +extern bool i386_ptrace_get_tls_data
>> + (int pid, gdb::array_view<user_desc> buffer);
>> +
>> +/* Store the three TLS related GDT (Global Descriptor Table) entries held
>> + in BUFFER back into the kernel for thread PID. BUFFER must have a
>> + length of three. */
>> +
>> +extern bool i386_ptrace_set_tls_data
>> + (int pid, gdb::array_view<user_desc> buffer);
>> +
>> #endif /* GDB_NAT_X86_LINUX_H */
>> diff --git a/gdb/testsuite/gdb.arch/i386-tls-regs.c
>> b/gdb/testsuite/gdb.arch/i386-tls-regs.c
>> new file mode 100644
>> index 00000000000..420d1566f20
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/i386-tls-regs.c
>> @@ -0,0 +1,74 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> + Copyright 2025 Free Software Foundation, Inc.
>> +
>> + This program is free software; you can redistribute it and/or modify
>> + it under the terms of the GNU General Public License as published by
>> + the Free Software Foundation; either version 3 of the License, or
>> + (at your option) any later version.
>> +
>> + This program is distributed in the hope that it will be useful,
>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + GNU General Public License for more details.
>> +
>> + You should have received a copy of the GNU General Public License
>> + along with this program. If not, see
>> + <http://www.gnu.org/licenses/>. */
>> +
>> +#include <pthread.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +
>> +static pthread_barrier_t barrier;
>> +
>> +static __thread int local_var = 0;
>> +
>> +volatile int wait_for_gdb_p = 1;
>> +
>> +volatile int should_dump_core_p = 1;
>> +
>> +void
>> +crash_func (void)
>> +{
>> + if (should_dump_core_p)
>> + abort ();
>> +}
>> +
>> +void
>> +spin_forever (void)
>> +{
>> + while (wait_for_gdb_p)
>> + sleep (1);
>> +}
>> +
>> +void *
>> +thread_func (void *arg)
>> +{
>> + local_var = *((int*) arg);
>> +
>> + pthread_barrier_wait (&barrier);
>> +
>> + spin_forever ();
>> +}
>> +
>> +int
>> +main (void)
>> +{
>> + pthread_t thr;
>> +
>> + if (pthread_barrier_init (&barrier, NULL, 2) != 0)
>> + abort ();
>> +
>> + local_var = 1;
>> + int i = 2;
>> + if (pthread_create (&thr, NULL, thread_func, &i) != 0)
>> + abort ();
>> +
>> + pthread_barrier_wait (&barrier);
>> +
>> + crash_func ();
>> +
>> + spin_forever ();
>> +
>> + return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/i386-tls-regs.exp
>> b/gdb/testsuite/gdb.arch/i386-tls-regs.exp
>> new file mode 100644
>> index 00000000000..1753f8762c6
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/i386-tls-regs.exp
>> @@ -0,0 +1,335 @@
>> +# Copyright 2025 Free Software Foundation, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or #
>> +(at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful, # but
>> +WITHOUT ANY WARRANTY; without even the implied warranty of #
>> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU
>> +General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License #
>> +along with this program. If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# Check that the TLS GDT registers are available for i386 executable.
>> +
>> +require {is_any_target "i?86-*-linux*" "x86_64-*-linux*"}
>> +
>> +standard_testfile
>> +
>> +set options {debug pthreads}
>> +if {![istarget "i386-*-*"]} {
>> + lappend options "additional_flags=-m32"
>> +}
>> +
>> +# Check that we can actually compile a 32-bit executable.
>> +#
>> +# It is possible that even with the above setting of OPTIONS, we might
>> +# still get a 64-bit executable, for example, running on x86-64 with #
>> +'--target_board=unix/-m64' will add the -m64 after the above -m32, #
>> +meaning we always end up with a 64-bit executable.
>> +#
>> +# If the following compiles then we are getting a 32-bit executable.
>> +if {![gdb_can_simple_compile is_lp64_target {
>> + int dummy[sizeof (int) == 4
>> + && sizeof (void *) == 4
>> + && sizeof (long) == 4 ? 1 : -1];} \
>> + object $options]} {
>> + unsupported "cannot compile 32-bit executable"
>> + return
>> +}
>> +
>> +if { [build_executable "failed to prepare" $testfile $srcfile $options] } {
>> + return
>> +}
>> +
>> +# Start an inferior and check we can read and modify the TLS registers.
>> +proc_with_prefix test_tls_reg_availability {} {
>> + clean_restart $::testfile
>> +
>> + if {![runto_main]} {
>> + return
>> + }
>> +
>> + set tls_reg_vals { "" "" "" }
>> + gdb_test_multiple "info registers system" "check for TLS regs" {
>> + -re "^info registers system\r\n" {
>> + exp_continue
>> + }
>> + -re "^i386_tls_gdt_(\\d) \\{($::hex), ($::hex), ($::hex), ($::hex)\\}\r\n" {
>> + set idx $expect_out(1,string)
>> + set val0 $expect_out(2,string)
>> + set val1 $expect_out(3,string)
>> + set val2 $expect_out(4,string)
>> + set val3 $expect_out(5,string)
>> + set val [list $val0 $val1 $val2 $val3]
>> + set tls_reg_vals [lreplace $tls_reg_vals $idx $idx $val]
>> + exp_continue
>> + }
>> + -re "^$::gdb_prompt $" {
>> + gdb_assert {[lsearch -exact $tls_reg_vals ""] == -1} $gdb_test_name
>> + }
>> + -re "^\[^\r\n\]+\r\n" {
>> + exp_continue
>> + }
>> + }
>> +
>> + if { [lindex $tls_reg_vals 0] != [lindex $tls_reg_vals 1] } {
>> + set new_vals [lindex $tls_reg_vals 0]
>> + set old_vals [lindex $tls_reg_vals 1]
>> +
>> + set val0 [lindex $old_vals 0]
>> + set val1 [lindex $new_vals 1]
>> + set val2 [lindex $new_vals 2]
>> + set val3 [lindex $new_vals 3]
>> +
>> + set new_val_str "{$val0, $val1, $val2, $val3}"
>> +
>> + gdb_test_no_output "set \$i386_tls_gdt_1 = $new_val_str"
>> + set re [string_to_regexp $new_val_str]
>> + gdb_test "print /x \$i386_tls_gdt_1" " = $re"
>> + }
>> +
>> + gdb_test_no_output "set should_dump_core_p=0"
>> + gdb_test_no_output "set wait_for_gdb_p=0"
>> +
>> + gdb_continue_to_end "" continue true }
>> +
>> +# Start GDB using global BINFILE, load COREFILE (which must match #
>> +BINFILE), and check that the core has two threads, that the TLS #
>> +registers are visible in both threads, and that the TLS register #
>> +values are different in each thread.
>> +proc load_core_and_test_tls_regs { corefile } {
>> + clean_restart $::testfile
>> +
>> + gdb_core_cmd $corefile "load corefile"
>> +
>> + gdb_test "info threads" \
>> + [multi_line \
>> + "\\*\\s+1\\s+Thread \[^\r\n\]+" \
>> + "\\s+2\\s+Thread \[^\r\n\]+"] \
>> + "check for two threads"
>> +
>> + # Record the TLS values in thread 1.
>> + set tls_reg_vals_thr_1 { "" "" "" }
>> + gdb_test_multiple "info registers system" "check for TLS regs thread 1" {
>> + -re "^info registers system\r\n" {
>> + exp_continue
>> + }
>> + -re "^i386_tls_gdt_(\\d) (\\{$::hex, $::hex, $::hex, $::hex\\})\r\n" {
>> + set idx $expect_out(1,string)
>> + set val $expect_out(2,string)
>> + set tls_reg_vals_thr_1 [lreplace $tls_reg_vals_thr_1 $idx $idx $val]
>> + exp_continue
>> + }
>> + -re "^$::gdb_prompt $" {
>> + gdb_assert {[lsearch -exact $tls_reg_vals_thr_1 ""] == -1}
>> $gdb_test_name
>> + }
>> + -re "^\[^\r\n\]+\r\n" {
>> + exp_continue
>> + }
>> + }
>> +
>> + # Check a TLS variable in thread 1.
>> + gdb_test "print local_var" " = 1" \
>> + "check TLS variable in thread 1"
>> +
>> + # Switch to thread 2 and confirm the values are different.
>> + gdb_test "thread 2"
>> +
>> + set tls_reg_vals_thr_2 { "" "" "" }
>> + gdb_test_multiple "info registers system" "check for TLS regs thread 2" {
>> + -re "^info registers system\r\n" {
>> + exp_continue
>> + }
>> + -re "^i386_tls_gdt_(\\d) (\\{$::hex, $::hex, $::hex, $::hex\\})\r\n" {
>> + set idx $expect_out(1,string)
>> + set val $expect_out(2,string)
>> + set tls_reg_vals_thr_2 \
>> + [lreplace $tls_reg_vals_thr_2 $idx $idx $val]
>> + exp_continue
>> + }
>> + -re "^$::gdb_prompt $" {
>> + gdb_assert {[lsearch -exact $tls_reg_vals_thr_2 ""] == -1} \
>> + $gdb_test_name
>> + }
>> + -re "^\[^\r\n\]+\r\n" {
>> + exp_continue
>> + }
>> + }
>> +
>> + # Check a TLS variable in thread 2.
>> + gdb_test "print local_var" " = 2" \
>> + "check TLS variable in thread 2"
>> +
>> + set all_same [expr [lindex $tls_reg_vals_thr_1 0] == [lindex
>> $tls_reg_vals_thr_2 0] \
>> + && [lindex $tls_reg_vals_thr_1 1] == [lindex
>> $tls_reg_vals_thr_2 1] \
>> + && [lindex $tls_reg_vals_thr_1 2] == [lindex
>> $tls_reg_vals_thr_2 2]]
>> + gdb_assert {!$all_same} \
>> + "tls regs are different between threads"
>> +}
>> +
>> +# Generate a core file using the gcore command. Load it into GDB and #
>> +check we can still read the TLS registers.
>> +proc_with_prefix test_gcore_tls {} {
>> +
>> + if {![gcore_cmd_available]} {
>> + unsupported "gcore command not available"
>> + return
>> + }
>> +
>> + clean_restart $::testfile
>> +
>> + if {![runto_main]} {
>> + return
>> + }
>> +
>> + gdb_test_no_output "set should_dump_core_p=0"
>> +
>> + gdb_breakpoint crash_func
>> + gdb_continue_to_breakpoint "stop at crash_func"
>> +
>> + set corefile [standard_output_file ${::testfile}.gcore]
>> + if {![gdb_gcore_cmd $corefile "dump core"]} {
>> + return
>> + }
>> +
>> + set readelf_program [gdb_find_readelf]
>> + set res [catch {exec $readelf_program -n $corefile} output]
>> + if { $res != 0 } {
>> + unresolved "unable to run readelf to check for TLS notes"
>> + return
>> + }
>> + set lines [split $output \n]
>> + set tls_count 0
>> + foreach line $lines {
>> + if {[regexp "^\\s+LINUX\\s+$::hex\\s+NT_386_TLS" $line]} {
>> + incr tls_count
>> + }
>> + }
>> + gdb_assert {$tls_count == 2} \
>> + "expected number of TLS notes"
>> +
>> + load_core_and_test_tls_regs $corefile }
>> +
>> +# Generate a core file using the gcore command, but before doing so, #
>> +clear all the TLS related GDT entries. When the TLS GDT entries # have
>> +a base address and limit of zero the kernel doesn't emit the #
>> +NT_386_TLS note, GDB copies this behaviour.
>> +proc_with_prefix test_gcore_no_tls {} {
>> +
>> + if {![gcore_cmd_available]} {
>> + unsupported "gcore command not available"
>> + return
>> + }
>> +
>> + clean_restart $::testfile
>> +
>> + if {![runto_main]} {
>> + return
>> + }
>> +
>> + gdb_test_no_output "set should_dump_core_p=0"
>> +
>> + gdb_breakpoint crash_func
>> + gdb_continue_to_breakpoint "stop at crash_func"
>> +
>> + # Clear the TLS registers in each thread.
>> + foreach_with_prefix thr { 1 2 } {
>> + gdb_test "thread $thr" ".*" \
>> + "switch thread to clear tls regs"
>> + gdb_test_no_output "set \$i386_tls_gdt_0 = { 0x0, 0x0, 0x0, 0x28 }"
>> + gdb_test_no_output "set \$i386_tls_gdt_1 = { 0x0, 0x0, 0x0, 0x28 }"
>> + gdb_test_no_output "set \$i386_tls_gdt_2 = { 0x0, 0x0, 0x0, 0x28 }"
>> + }
>> +
>> + set corefile [standard_output_file ${::testfile}.gcore]
>> + if {![gdb_gcore_cmd $corefile "dump core"]} {
>> + return
>> + }
>> +
>> + # Look in the core file for NT_386_TLS entries. There should be
>> + # none.
>> + set readelf_program [gdb_find_readelf]
>> + set res [catch {exec $readelf_program -n $corefile} output]
>> + if { $res != 0 } {
>> + unresolved "unable to run readelf to check for TLS notes"
>> + return
>> + }
>> + set lines [split $output \n]
>> + set tls_count 0
>> + foreach line $lines {
>> + if {[regexp "^\\s+LINUX\\s+$::hex\\s+NT_386_TLS" $line]} {
>> + incr tls_count
>> + }
>> + }
>> + gdb_assert {$tls_count == 0} \
>> + "expected number of TLS notes"
>> +
>> + # Restart GDB and load the core file. As there are no NT_386_TLS
>> + # entries the TLS registers should show as unavailable.
>> + clean_restart $::testfile
>> +
>> + gdb_core_cmd $corefile "load corefile"
>> +
>> + gdb_test "info threads" \
>> + [multi_line \
>> + "\\*\\s+1\\s+Thread \[^\r\n\]+" \
>> + "\\s+2\\s+Thread \[^\r\n\]+"] \
>> + "check for two threads"
>> +
>> + foreach_with_prefix thr { 1 2 } {
>> + set unavailable_tls_count 0
>> + set valid_tls_count 0
>> + gdb_test "thread $thr" ".*" \
>> + "switch thread to check TLS register status"
>> + gdb_test_multiple "info registers system" "check TLS reg values" {
>> + -re "^info registers system\r\n" {
>> + exp_continue
>> + }
>> + -re "^i386_tls_gdt_\\d \\{<unavailable>, <unavailable>,
>> <unavailable>, <unavailable>\\}\r\n" {
>> + incr unavailable_tls_count
>> + exp_continue
>> + }
>> + -re "^i386_tls_gdt_\\d \[^\r\n\]+\r\n" {
>> + incr valid_tls_count
>> + exp_continue
>> + }
>> +
>> + -re "^$::gdb_prompt $" {
>> + gdb_assert {$valid_tls_count == 0 && \
>> + $unavailable_tls_count == 3} \
>> + $gdb_test_name
>> + }
>> + -re "^\[^\r\n\]+\r\n" {
>> + exp_continue
>> + }
>> + }
>> +
>> + # Check a TLS variable in this thread.
>> + gdb_test "print local_var" " = $thr" \
>> + "check TLS variable in thread $thr"
>> + }
>> +}
>> +
>> +# Generate a native core file. Load it into GDB and check the TLS #
>> +registers can be read.
>> +proc_with_prefix test_native_core {} {
>> + set corefile [core_find $::binfile]
>> + if { $corefile eq "" } {
>> + unsupported "unable to generate core file"
>
> Given that this is usually a setup issue, I think UNTESTED would fit better here.
> I tried to make that consistent in our testsuite with this patch:
> https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=857ef95cd9eb87a1548f1d4999ce230a454f16ac
> Sorry for not being too specific in the first place.
I think UNTESTED is the wrong choice. Here's what the DejaGNU manual
has to say:
UNTESTED
A test was not run. This is a placeholder used when there is no
real test case yet.
So to me, this is for cases where we (GDB developers) either haven't
written a test yet, or don't know how to write a test. The bit about
"no real test case yet" certainly seems to exclude its use in this case.
For UNSUPPORTED the manual says:
UNSUPPORTED
There is no support for the tested case. This may mean that a
conditional feature of an operating system, or of a compiler, is
not implemented. DejaGnu also uses this message when a testing
environment (often a "bare board" target) lacks basic support for
compiling or running the test case. For example, a test for the
system subroutine _gethostname_ would never work on a target board
running only a boot monitor.
Which seems to be a slightly better match for this case, though I'd
agree it's not a perfect match. The conditional feature (kernel core
file generation) is implemented (we can assume), but is disabled on this
system. And I think this makes sense; on a system where kernel core
file creation has been disabled, running a test that relies on this
feature is not supported.
>
>> + return
>> + }
>> +
>> + load_core_and_test_tls_regs $corefile }
>> +
>> +# Run the tests.
>> +test_tls_reg_availability
>> +test_gcore_tls
>> +test_gcore_no_tls
>> +test_native_core
>> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
>> 660a906cdb6..58b7b6379ff 100644
>> --- a/gdb/x86-linux-nat.c
>> +++ b/gdb/x86-linux-nat.c
>> @@ -254,6 +254,73 @@ x86_linux_store_ssp (const regcache *regcache,
>> const int tid)
>> perror_with_name (_("Failed to write pl3_ssp register")); }
>>
>> +/* Copy from BUFFER into REGCACHE, supplying the tls gdt entry registers.
>> + Use REGNUM to decide exactly which registers are copied. */
>> +
>> +static void
>> +i386_supply_tls_regs (regcache *regcache, int regnum,
>> + gdb::array_view<user_desc> buffer) {
>
> Nit: I think there is an indent issue here. There is one whitespace too
> much in the line above (also for i386_collect_tls_regs below).
Thanks for spotting this. I'll get it fixed.
>
>> + gdb_assert (buffer.size () == 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + if (regnum == -1 || regnum == I386_LINUX_TLS_GDT_0 + i)
>> + regcache->raw_supply (I386_LINUX_TLS_GDT_0 + i,
>> + buffer.slice (i, 1).data ());
>> + }
>> +}
>> +
>> +/* Copy from REGCACHE into BUFFER, collecting the tls gdt entry
>> + registers. USE REGNUM to decide which registers are copied. */
>
> Nit: USE-> Use
Will fix.
>
>> +
>> +static void
>> +i386_collect_tls_regs (regcache *regcache, int regnum,
>> + gdb::array_view<user_desc> buffer)
>> +{
>> + gdb_assert (buffer.size () == 3);
>> +
>> + for (int i = 0; i < 3; ++i)
>> + {
>> + if (regnum == -1 || regnum == I386_LINUX_TLS_GDT_0 + i)
>> + regcache->raw_collect (I386_LINUX_TLS_GDT_0 + i,
>> + buffer.slice (i, 1).data ());
>> + }
>> +}
>> +
>> +/* See x86-linux-nat.h. */
>
> Nit: one whitespace too much before "*/"
Will fix.
>
>> +
>> +void
>> +i386_fetch_tls_regs (regcache *regcache, int tid, int regnum) {
>> + user_desc tls_ud[3];
>> + if (!i386_ptrace_get_tls_data (tid, tls_ud))
>> + perror_with_name (_("Couldn't get TLS area data"));
>> +
>> + i386_supply_tls_regs (regcache, regnum, tls_ud); }
>> +
>> +/* See x86-linux-nat.h. */
>> +
>> +void
>> +i386_store_tls_regs (regcache *regcache, int tid, int regnum) {
>> + /* Read current values in to TLS_UD. */
>> + user_desc tls_ud[3];
>> + if (!i386_ptrace_get_tls_data (tid, tls_ud))
>> + perror_with_name (_("Couldn't get TLS area data"));
>> +
>> + /* Write new values from regcache into TLS_UD. Overwriting the
>> + current values. */
>> + i386_collect_tls_regs (regcache, regnum, tls_ud);
>> +
>> + /* Write the new values back to the kernel. */
>> + if (!i386_ptrace_set_tls_data (tid, tls_ud))
>> + perror_with_name (_("Couldn't write TLS area data")); }
>> +
>> +
>>
>>
>> +
>> INIT_GDB_FILE (x86_linux_nat)
>> {
>> /* Initialize the debug register function vectors. */ diff --git a/gdb/x86-linux-
>> nat.h b/gdb/x86-linux-nat.h index c4556532226..16a3b35b547 100644
>> --- a/gdb/x86-linux-nat.h
>> +++ b/gdb/x86-linux-nat.h
>> @@ -25,6 +25,7 @@
>> #include "gdbsupport/x86-xstate.h"
>> #include "x86-nat.h"
>> #include "nat/x86-linux.h"
>> +#include "i386-linux-tdep.h"
>>
>> struct x86_linux_nat_target : public x86_nat_target<linux_nat_target> { @@ -
>> 103,4 +104,18 @@ extern void x86_linux_fetch_ssp (regcache *regcache,
>> const int tid);
>>
>> extern void x86_linux_store_ssp (const regcache *regcache, const int tid);
>>
>> +/* Fetch the tls related registers for thread TID from the kernel and place
>> + them into REGCACHE. If REGNUM is -1 then all 3 tls registers are
>> + fetched, otherwise only the register matching REGNUM is fetched. A tls
>> + register number is one for which i386_is_tls_regnum_p returns true.
>> +*/
>> +
>> +extern void i386_fetch_tls_regs (regcache *regcache, int tid, int
>> +regnum);
>> +
>> +/* Store the tls related registers for thread TID from REGCACHE back in to
>> + the kernel. If REGNUM is -1 then all 3 tls registers are stored,
>> + otherwise only the register matching REGNUM is stored. A tls register
>> + number is one for which i386_is_tls_regnum_p returns true. */
>> +
>> +extern void i386_store_tls_regs (regcache *regcache, int tid, int
>> +regnum);
>> +
>> #endif /* GDB_X86_LINUX_NAT_H */
>> diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc index
>> 2aa85ec3d9c..0fd32a22b42 100644
>> --- a/gdbserver/linux-x86-low.cc
>> +++ b/gdbserver/linux-x86-low.cc
>> @@ -53,6 +53,8 @@
>> #include "nat/x86-linux-dregs.h"
>> #include "nat/x86-linux-tdesc.h"
>>
>> +#include <asm/ldt.h>
>> +
>> #ifdef __x86_64__
>> static target_desc_up tdesc_amd64_linux_no_xml; #endif @@ -132,6
>> +134,10 @@ public:
>>
>> int get_ipa_tdesc_idx () override;
>>
>> + /* Override these to provide access to i386 TLS state. */ void
>> + fetch_registers (regcache *regcache, int regno) override; void
>> + store_registers (regcache *regcache, int regno) override;
>> +
>> protected:
>>
>> void low_arch_setup () override;
>> @@ -573,6 +579,105 @@ static struct regset_info x86_regsets[] =
>> NULL_REGSET
>> };
>>
>> +/* Fetch TLS area data from the kernel and copy it into REGCACHE. REGNO
>> + indicates which TLS area register is wanted, or -1 for all of them.
>> +
>> + If anything goes wrong then this function will return without updating
>> + REGCACHE. */
>> +
>> +static void
>> +fetch_tls_area_register (regcache *regcache, int regno) {
>> + int tid = current_thread->id.lwp ();
>> +
>> + /* Fetch all the TLS area data from the kernel. */ user_desc
>> + tls_ud[3]; if (!i386_ptrace_get_tls_data (tid, tls_ud))
>> + {
>> + warning (_("failed to read TLS GDT entries for register read"));
>> + return;
>> + }
>> +
>> + /* Now copy the values from TLS_UD back into the register cache. */
>> + int tls_regno[3] = {
>> + find_regno (regcache->tdesc, "i386_tls_gdt_0"),
>> + find_regno (regcache->tdesc, "i386_tls_gdt_1"),
>> + find_regno (regcache->tdesc, "i386_tls_gdt_2") };
>> +
>> + for (int i = 0; i < std::size (tls_regno); ++i)
>> + supply_register (regcache, tls_regno[i], &tls_ud[i]); }
>> +
>> +/* See class declaration above. */
>> +
>> +void
>> +x86_target::fetch_registers (regcache *regcache, int regno) {
>> + linux_process_target::fetch_registers (regcache, regno);
>> +
>> +#ifdef __x86_64__
>> + if (!is_64bit_tdesc (current_thread)) #endif
>> + {
>> + fetch_tls_area_register (regcache, regno);
>> + }
>> +}
>> +
>> +/* Copy TLS area data from REGCACHE back to the kernel. REGNO indicates
>> + which TLS area register should be copied, or -1 for all of them. If
>> + anything goes wrong then return immediately; some of the register may
>> + have been written back to the kernel in this case. */
>> +
>> +static void
>> +store_tls_area_registers (regcache *regcache, int regno) {
>> + int tid = current_thread->id.lwp ();
>> +
>> + /* Read current TLS area data from the kernel into TLS_UD. We then
>> + overwrite this with values from REGCACHE, and finally, copy the
>> + updated values back to the kernel. */ user_desc tls_ud[3]; if
>> + (!i386_ptrace_get_tls_data (tid, tls_ud))
>> + {
>> + warning (_("failed to read TLS GDT entries for register store"));
>> + return;
>> + }
>> +
>> + int tls_regno[] = {
>> + find_regno (regcache->tdesc, "i386_tls_gdt_0"),
>> + find_regno (regcache->tdesc, "i386_tls_gdt_1"),
>> + find_regno (regcache->tdesc, "i386_tls_gdt_2") };
>> +
>> + /* Now copy data from REGCACHE over the top of the values written
>> + into TLS_UD. */
>> + for (int i = 0; i < std::size (tls_regno); ++i)
>> + collect_register (regcache, tls_regno[i], &tls_ud[i]);
>> +
>> + /* And write the contents of TLS_UD back to the kernel. We ignore the
>> + return value from this call; if anything went wrong then there's
>> + nothing we can do about it. */
>> + if (!i386_ptrace_set_tls_data (tid, tls_ud))
>> + warning (_("failed to write updated TLS GDT entries for register
>> + store"));
>> +
>> +}
>> +
>> +/* See class declaration above. */
>> +
>> +void
>> +x86_target::store_registers (regcache *regcache, int regno) {
>> + linux_process_target::store_registers (regcache, regno);
>> +
>> +#ifdef __x86_64__
>> + if (!is_64bit_tdesc (current_thread)) #endif
>> + {
>> + store_tls_area_registers (regcache, regno);
>> + }
>> +}
>> +
>> bool
>> x86_target::low_supports_breakpoints () {
>>
>> base-commit: 1ddfd4f3eac858294793801fa52d63d36042b4b3
>> --
>> 2.47.1
>>
>
> With the comment from Keith and the nits fixed this lgtm.
>
> Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>
I'll get these fixed and push the patch sometime next week.
Thanks,
Andrew
>
> Christina
>
>
> Intel Deutschland GmbH
> Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht München HRB 186928
next prev parent reply other threads:[~2025-11-13 19:37 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-02 11:22 [PATCH] " Andrew Burgess
2025-09-23 17:31 ` Andrew Burgess
2025-09-23 17:33 ` [PATCHv2] " Andrew Burgess
2025-10-14 13:54 ` [PATCHv3] " Andrew Burgess
2025-10-14 14:12 ` Eli Zaretskii
2025-10-29 9:49 ` Schimpe, Christina
2025-11-05 13:57 ` [PATCHv4] " Andrew Burgess
2025-11-06 21:20 ` Keith Seitz
2025-11-12 9:44 ` Schimpe, Christina
2025-11-13 19:35 ` Andrew Burgess [this message]
2025-11-14 9:37 ` Schimpe, Christina
2025-11-20 17:19 ` Andrew Burgess
2025-09-23 19:41 ` [PATCH] " Eli Zaretskii
2025-09-23 20:19 ` Andrew Burgess
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87ldk9zpxv.fsf@redhat.com \
--to=aburgess@redhat.com \
--cc=christina.schimpe@intel.com \
--cc=eliz@gnu.org \
--cc=gdb-patches@sourceware.org \
--cc=keiths@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox