From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCH] gdb: include NT_I386_TLS note in generated core files
Date: Tue, 2 Sep 2025 12:22:34 +0100 [thread overview]
Message-ID: <35c3bcb9e7cd1b7b9192adeda9acd3417525f97d.1756812133.git.aburgess@redhat.com> (raw)
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
---
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 | 171 +++++++++-
gdb/i386-linux-tdep.h | 18 +-
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-linux-tls-regs.c | 74 +++++
.../gdb.arch/i386-linux-tls-regs.exp | 314 ++++++++++++++++++
gdb/x86-linux-nat.c | 67 ++++
gdb/x86-linux-nat.h | 17 +
gdbserver/linux-x86-low.cc | 99 ++++++
18 files changed, 930 insertions(+), 7 deletions(-)
create mode 100644 gdb/nat/amd64-linux.h
create mode 100644 gdb/testsuite/gdb.arch/i386-linux-tls-regs.c
create mode 100644 gdb/testsuite/gdb.arch/i386-linux-tls-regs.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 2d410a75265..aa9bba6b920 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -52,6 +52,10 @@
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 e35527de2a6..d8ca1d85934 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. */
@@ -87,7 +90,8 @@ static int amd64_linux_gregset32_reg_offset[] =
-1, -1, -1, -1, -1, -1, -1, -1, /* k0 ... k7 (AVX512) */
-1, -1, -1, -1, -1, -1, -1, -1, /* zmm0 ... zmm7 (AVX512) */
-1, /* PKEYS register PKRU */
- ORIG_RAX * 8 /* "orig_eax" */
+ ORIG_RAX * 8, /* "orig_eax" */
+ -1, -1, -1, /* TLS GDT regs: i386_tls_gdt_0...2. */
};
\f
@@ -234,7 +238,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)
{
@@ -264,6 +274,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"));
@@ -306,7 +318,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)
{
@@ -335,6 +353,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 4c33cd2a521..dbf04240346 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -50109,8 +50109,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 @samp{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 f5112d2b868..657fc52595c 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)
{
+ struct 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)
{
+ struct 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);
}
\f
diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
index efbde6a3ccf..fbbfe17bcd9 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);
@@ -1045,6 +1045,7 @@ int i386_linux_gregset_reg_offset[] =
-1, -1, -1, -1, -1, -1, -1, -1, /* zmm0 ... zmm7 (AVX512) */
-1, /* PKRU register */
11 * 4, /* "orig_eax" */
+ -1, -1, -1, /* TLS GDT regs: i386_tls_gdt_0...2. */
};
/* Mapping between the general-purpose registers in `struct
@@ -1162,6 +1163,81 @@ 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 struct regset *regset,
+ struct regcache *regcache, int regnum,
+ const void *buffer, size_t len)
+{
+ struct gdbarch *gdbarch = regcache->arch ();
+ i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
+
+ if (!tdep->i386_linux_tls)
+ return;
+
+ gdb_assert (len == sizeof (struct 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 (struct x86_user_desc));
+
+ regcache->raw_supply (tls_regno, buffer);
+ buffer = static_cast<const struct 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 struct regset *regset,
+ const struct regcache *regcache,
+ int regnum, void *buffer, size_t len)
+{
+ struct gdbarch *gdbarch = regcache->arch ();
+ i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
+
+ if (!tdep->i386_linux_tls)
+ return;
+
+ gdb_assert (len == sizeof (struct x86_user_desc) * 3);
+
+ for (int i = 0; i < 3; ++i)
+ {
+ struct 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<struct x86_user_desc *> (buffer) + 1;
+ }
+}
+
/* Register set definitions. */
static const struct regset i386_linux_xstateregset =
@@ -1171,6 +1247,65 @@ static const struct regset i386_linux_xstateregset =
i386_linux_collect_xstateregset
};
+static const struct 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 struct regcache *regcache)
+{
+ if (regcache == nullptr)
+ return true;
+
+ /* Check the pre-condition. */
+ struct 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 (struct x86_user_desc));
+
+ /* Read the TLS GDT entry. If it is in use then we want to
+ write the NT_386_TLS note. */
+ struct 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
@@ -1192,6 +1327,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
@@ -1271,6 +1409,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 struct tdesc_feature *i386_feature,
+ struct tdesc_arch_data *local_tdesc_data,
+ const char *tls_reg_name, int tls_regno) -> bool
+ {
+ static constexpr int required_reg_size
+ = sizeof (struct 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 de98256462a..421de6befcb 100644
--- a/gdb/i386-linux-tdep.h
+++ b/gdb/i386-linux-tdep.h
@@ -21,6 +21,7 @@
#define GDB_I386_LINUX_TDEP_H
#include "gdbsupport/x86-xstate.h"
+#include "i386-tdep.h"
/* The Linux kernel pretends there is an additional "orig_eax"
register. Since GDB needs access to that register to be able to
@@ -33,8 +34,15 @@
system call number that the kernel is supposed to restart. */
#define I386_LINUX_ORIG_EAX_REGNUM (I386_PKRU_REGNUM + 1)
+/* 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. */
+#define I386_LINUX_TLS_GDT_0 (I386_LINUX_ORIG_EAX_REGNUM + 1)
+#define I386_LINUX_TLS_GDT_1 (I386_LINUX_ORIG_EAX_REGNUM + 2)
+#define I386_LINUX_TLS_GDT_2 (I386_LINUX_ORIG_EAX_REGNUM + 3)
+
/* Total number of registers for GNU/Linux. */
-#define I386_LINUX_NUM_REGS (I386_LINUX_ORIG_EAX_REGNUM + 1)
+#define I386_LINUX_NUM_REGS (I386_LINUX_TLS_GDT_2 + 1)
/* Read the XSAVE extended state xcr0 value from the ABFD core file.
If it appears to be valid, return it and fill LAYOUT with values
@@ -51,4 +59,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 84f1bbde007..dc3ffa7bad1 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..017fe1314ad 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<struct 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<struct 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..cebe105b294 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<struct 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<struct user_desc> buffer);
+
#endif /* GDB_NAT_X86_LINUX_H */
diff --git a/gdb/testsuite/gdb.arch/i386-linux-tls-regs.c b/gdb/testsuite/gdb.arch/i386-linux-tls-regs.c
new file mode 100644
index 00000000000..420d1566f20
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/i386-linux-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-linux-tls-regs.exp b/gdb/testsuite/gdb.arch/i386-linux-tls-regs.exp
new file mode 100644
index 00000000000..449bcf345c7
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/i386-linux-tls-regs.exp
@@ -0,0 +1,314 @@
+# 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"
+}
+
+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 $::binfile
+
+ if {![runto_main]} {
+ return -1
+ }
+
+ 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 $::binfile
+
+ 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 $::binfile
+
+ if {![runto_main]} {
+ return -1
+ }
+
+ 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 $::binfile
+
+ if {![runto_main]} {
+ return -1
+ }
+
+ 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 $::binfile
+
+ 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]
+
+ 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..f81622f4fea 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 (struct regcache *regcache, int regnum,
+ gdb::array_view<struct 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_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. */
+
+static void
+i386_collect_tls_regs (struct regcache *regcache, int regnum,
+ gdb::array_view<struct 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. */
+
+void
+i386_fetch_tls_regs (struct regcache *regcache, int tid, int regnum)
+{
+ struct 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 (struct regcache *regcache, int tid, int regnum)
+{
+ /* Read current values in to TLS_UD. */
+ struct 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"));
+}
+
+\f
+
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..327ee9434b0 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,20 @@ 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 (struct 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 (struct 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 44257d5d1ce..28edd9d8086 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,99 @@ 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.
+ This function returns true if the data was fetched and stored to
+ REGCACHE successfully, otherwise, it returns false. */
+
+static bool
+fetch_tls_area_register (regcache *regcache, int regno)
+{
+ int tid = current_thread->id.lwp ();
+
+ /* Fetch all the TLS area data from the kernel. */
+ struct user_desc tls_ud[3];
+ if (!i386_ptrace_get_tls_data (tid, tls_ud))
+ return false;
+
+ /* 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]);
+
+ return true;
+}
+
+/* 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. This
+ function returns true if the data was stored successfully, otherwise,
+ it returns false. */
+
+static bool
+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. */
+ struct user_desc tls_ud[3];
+ if (!i386_ptrace_get_tls_data (tid, tls_ud))
+ return false;
+
+ 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. */
+ if (!i386_ptrace_set_tls_data (tid, tls_ud))
+ return false;
+
+ return true;
+}
+
+/* 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: 7bdcd19cc6d8137ecdca83571946d6ffb7b4be26
--
2.47.1
next reply other threads:[~2025-09-02 11:23 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-02 11:22 Andrew Burgess [this message]
2025-09-23 17:31 ` Andrew Burgess
2025-09-23 17:33 ` [PATCHv2] " Andrew Burgess
2025-10-14 13:54 ` [PATCHv3] " Andrew Burgess
2025-10-14 14:12 ` Eli Zaretskii
2025-10-29 9:49 ` Schimpe, Christina
2025-11-05 13:57 ` [PATCHv4] " Andrew Burgess
2025-11-06 21:20 ` Keith Seitz
2025-11-12 9:44 ` Schimpe, Christina
2025-11-13 19:35 ` Andrew Burgess
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=35c3bcb9e7cd1b7b9192adeda9acd3417525f97d.1756812133.git.aburgess@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
/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