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
next prev 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