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>
Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>
Reviewed-By: Keith Seitz <keiths@redhat.com>
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
#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. */
-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. */
};
\f
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)
{
}
else
{
+ elf_fpregset_t fpregs;
+
if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0)
perror_with_name (_("Couldn't get floating point status"));
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)
{
}
else
{
+ elf_fpregset_t fpregs;
+
if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0)
perror_with_name (_("Couldn't get floating point status"));
@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}.
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;
}
<!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>
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
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'. */
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);
}
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
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;
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
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);
-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
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));
+
+ 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
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
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);
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
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 */
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. */
--- /dev/null
+/* 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 */
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 */
#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
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;
+}
#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
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 */
--- /dev/null
+/* 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;
+}
--- /dev/null
+# 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"
+ 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
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)
+{
+ 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 (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. */
+
+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"));
+}
+
+\f
+
INIT_GDB_FILE (x86_linux_nat)
{
/* Initialize the debug register function vectors. */
#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>
{
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 */
#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
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;
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 ()
{