]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: include NT_I386_TLS note in generated core files
authorAndrew Burgess <aburgess@redhat.com>
Tue, 21 Jan 2025 17:22:04 +0000 (17:22 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Thu, 20 Nov 2025 16:41:19 +0000 (16:41 +0000)
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>
18 files changed:
gdb/NEWS
gdb/amd64-linux-nat.c
gdb/doc/gdb.texinfo
gdb/features/i386/32bit-linux.c
gdb/features/i386/32bit-linux.xml
gdb/i386-linux-nat.c
gdb/i386-linux-tdep.c
gdb/i386-linux-tdep.h
gdb/i386-tdep.h
gdb/nat/amd64-linux.h [new file with mode: 0644]
gdb/nat/i386-linux.h
gdb/nat/x86-linux.c
gdb/nat/x86-linux.h
gdb/testsuite/gdb.arch/i386-tls-regs.c [new file with mode: 0644]
gdb/testsuite/gdb.arch/i386-tls-regs.exp [new file with mode: 0644]
gdb/x86-linux-nat.c
gdb/x86-linux-nat.h
gdbserver/linux-x86-low.cc

index c19f0ed1e3684cb571b0cd9b568ee271b6dfdd8d..c976727ee455e48e843208cff832e703365b9105 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -181,6 +181,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
index 96acfda7d009b8656d49913a2e762cb45ec45a2c..53a92f899579ddee549ff2c838557a0064a176c2 100644 (file)
@@ -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.  */
 };
 \f
 
@@ -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"));
 
index 87e994defa63df6ba87f210d6510e1dd74b78adf..1cc7d295fbfb06da73bce54be78348f139aa66dd 100644 (file)
@@ -50388,8 +50388,15 @@ describe the upper 128 bits of @sc{ymm} registers:
 @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}.
index 3289f07d332fe190fcde73f538e675f295f148b3..6fba8a6880ba99ad66591a7a1c13a19d8a74ccb1 100644 (file)
@@ -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;
 }
index 0bfc0bc5563476b4d52cd6248eae402843f35670..7de3198b7da6a5737cf13bd0e0886f2f744d1fa1 100644 (file)
@@ -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>
index 4af06c448a8bfb45d38e894944ea236631634bfd..f86005e30e84caa1fb9d662af4cde62fb4388107 100644 (file)
@@ -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);
 }
 \f
index 20adf169c81aca153f7a79b18f5bbb4ca036527d..1d6abe2f546036add4df40695a511a01dd64a524 100644 (file)
@@ -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));
+
+      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);
 
index 8a1a24477767687667113f2c9bd033ed3aeac963..e75e53aba95eb5e7355c5aefbace8dee997a5afa 100644 (file)
@@ -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 */
index cfe0d7383d6cf2463ea624dba97994262c0cb531..ad355eb597a3a3245c44030939b46c2331b4f0e5 100644 (file)
@@ -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 (file)
index 0000000..1d5bf57
--- /dev/null
@@ -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 */
index d80e0f999bf623e0da382a086278a7a8ca4bdfcd..b99f0a02418c5e9b776332da4334e389c35556cc 100644 (file)
    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 */
index 55158268970469068760c82f7fbbb1e1efa18f97..8906b7e610414b710bac228637aeeca66d52417f 100644 (file)
 #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;
+}
index 1783aae05d03549f39cb0c4e57c052e14db6a04a..c982a46bf2e6c4e56f13975ba4a1a450ee4fdba9 100644 (file)
@@ -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 (file)
index 0000000..420d156
--- /dev/null
@@ -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 (file)
index 0000000..378a8e8
--- /dev/null
@@ -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"
+       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
index 660a906cdb6ff9f17d27c5b6c94e5e57e6f571b7..0b84f4c0ada77e77e3465088e05246a89af81d27 100644 (file)
@@ -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)
+{
+  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.  */
index c4556532226363ec832a9d7b45d8b5e05271b597..16a3b35b5479f7155b329fedd22afcc2661b5fd5 100644 (file)
@@ -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 */
index 2aa85ec3d9c2305af9d94f5c33bcf3b32c1cf7a2..0fd32a22b42b6dcb909f8e097cbe8691159b8910 100644 (file)
@@ -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 ()
 {