Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: "Schimpe, Christina" <christina.schimpe@intel.com>
To: Andrew Burgess <aburgess@redhat.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: Wed, 12 Nov 2025 09:44:59 +0000	[thread overview]
Message-ID: <PH0PR11MB763600D6F65E6AD41BC7E156F9CCA@PH0PR11MB7636.namprd11.prod.outlook.com> (raw)
In-Reply-To: <aca276e8d328ca8a794a40e8e9e641ecd4e6ba98.1762350702.git.aburgess@redhat.com>

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?

> +
> +      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.

> +	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).

> +  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

> +
> +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 "*/"

> +
> +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>

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

  parent reply	other threads:[~2025-11-12  9:46 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 [this message]
2025-11-13 19:35           ` Andrew Burgess
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=PH0PR11MB763600D6F65E6AD41BC7E156F9CCA@PH0PR11MB7636.namprd11.prod.outlook.com \
    --to=christina.schimpe@intel.com \
    --cc=aburgess@redhat.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