Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
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


  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