]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[wip] gdb: include NT_I386_TLS note in generated core files users/aburgess/gdb-core-i386-tls
authorAndrew Burgess <aburgess@redhat.com>
Tue, 21 Jan 2025 17:22:04 +0000 (17:22 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Tue, 22 Apr 2025 12:01:59 +0000 (13:01 +0100)
This commit extends GDB for x86/Linux to include the NT_I386_TLS note
in generated core file (i.e. created with `generate-core-file`
command).

The NT_I386_TLS note should be added for i386 binaries, and for
binaries compiled with -m32.

[TODO] This commit does adds a basic framework for accessing the TLS
data, but currently only reads the information for one thread (the
current thread when the core file is created), which is not correct.
An actual kernel create core file includes a NT_I386_TLS for each
thread, so that needs fixing.

[TODO] Also, this commit doesn't know how to fetch the TLS descriptor
information for remote targets, so that needs fixing.

[TODO] Looking at how ARM/AArch64 handles similar TLS data, that
targets seems to use a custom register set.  I wonder if the approach
I've taken here is the wrong one; maybe we should add 3 custom
registers, each register representing 1 GDT entry, with an entry being
a base-address and limit.  GDB would then try to read that register
set for each thread...  I'm not entirely sure on the details, I'll
need to investigate how ARM/AArch64 does this a little more.

[TODO] This commit could do with a test in gdb.arch/.  Not sure
exactly what the test should do yet, but probably create a
multi-threaded inferior (with some TLS usage?) and generate a core
file.  We can then check that there is a NT_I386_TLS for each thread.
We can then load the core file and perform some action that depends on
the TLS descriptor ... need to figure out exactly what that is though.

[TODO] Finally, I wonder if we should add a new command that allows a
user to inspect the TLS descriptor information, though this should
probably be broken out into a separate commit.

gdb/amd64-linux-nat.c
gdb/i386-linux-nat.c
gdb/i386-linux-tdep.c
gdb/linux-tdep.c
gdb/linux-tdep.h
gdb/target.h
gdb/x86-linux-nat.c
gdb/x86-linux-nat.h

index 75e63c67ba0c5ca4bebd41b65d97be0413a87569..932edc09cfb352e1d59e6c143aa90dc286ee30cb 100644 (file)
@@ -52,6 +52,14 @@ struct amd64_linux_nat_target final : public x86_linux_nat_target
 
   bool low_siginfo_fixup (siginfo_t *ptrace, gdb_byte *inf, int direction)
     override;
+
+  /* Override default xfer_partial, adding support for x86 specific OBJECT
+     types.  */
+  enum target_xfer_status xfer_partial (enum target_object object,
+                                       const char *annex, gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset, ULONGEST len,
+                                       ULONGEST *xfered_len) override;
 };
 
 static amd64_linux_nat_target the_amd64_linux_nat_target;
@@ -330,6 +338,23 @@ amd64_linux_nat_target::store_registers (struct regcache *regcache, int regnum)
 }
 \f
 
+/* See class declaration above.  */
+
+enum target_xfer_status
+amd64_linux_nat_target::xfer_partial (enum target_object object,
+                                     const char *annex, gdb_byte *readbuf,
+                                     const gdb_byte *writebuf,
+                                     ULONGEST offset, ULONGEST len, ULONGEST *xfered_len)
+{
+  if (object == TARGET_OBJECT_X86_LINUX_TLS_DESC)
+    return x86_linux_nat_target::xfer_tls_desc (readbuf, writebuf, offset,
+                                               len, xfered_len, 12);
+
+  return x86_linux_nat_target::xfer_partial (object, annex, readbuf, writebuf,
+                                            offset, len, xfered_len);
+}
+\f
+
 /* This function is called by libthread_db as part of its handling of
    a request for a thread's local storage address.  */
 
index a5d55826f0820228e83332f6aba4684cd06d474f..00dc4d14016c303ed57aff4974a13b5468dbd6bb 100644 (file)
@@ -44,6 +44,14 @@ struct i386_linux_nat_target final : public x86_linux_nat_target
 
   /* Override the default ptrace resume method.  */
   void low_resume (ptid_t ptid, int step, enum gdb_signal sig) override;
+
+  /* Override default xfer_partial, adding support for x86 specific OBJECT
+     types.  */
+  enum target_xfer_status xfer_partial (enum target_object object,
+                                       const char *annex, gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset, ULONGEST len,
+                                       ULONGEST *xfered_len) override;
 };
 
 static i386_linux_nat_target the_i386_linux_nat_target;
@@ -696,6 +704,22 @@ i386_linux_nat_target::low_resume (ptid_t ptid, int step, enum gdb_signal signal
     perror_with_name (("ptrace"));
 }
 
+/* See class declaration above.  */
+
+enum target_xfer_status
+i386_linux_nat_target::xfer_partial (enum target_object object,
+                                   const char *annex, gdb_byte *readbuf,
+                                   const gdb_byte *writebuf,
+                                   ULONGEST offset, ULONGEST len, ULONGEST *xfered_len)
+{
+  if (object == TARGET_OBJECT_X86_LINUX_TLS_DESC)
+    return x86_linux_nat_target::xfer_tls_desc (readbuf, writebuf, offset,
+                                               len, xfered_len, 6);
+
+  return x86_linux_nat_target::xfer_partial (object, annex, readbuf, writebuf,
+                                            offset, len, xfered_len);
+}
+
 void _initialize_i386_linux_nat ();
 void
 _initialize_i386_linux_nat ()
index 4b05cc6870b52876db50802c4add8c3af1735e28..929eef02b98e25ac4513a83c0a159dc68d6f4888 100644 (file)
@@ -37,6 +37,8 @@
 #include "arch-utils.h"
 #include "xml-syscall.h"
 #include "infrun.h"
+#include "elf/common.h"
+#include "elf-bfd.h"
 
 #include "i387-tdep.h"
 #include "gdbsupport/x86-xstate.h"
@@ -1237,6 +1239,31 @@ i386_linux_displaced_step_copy_insn (struct gdbarch *gdbarch,
   return closure_;
 }
 
+/* Wrap around linux_make_corefile_notes, this adds the TLS related note.  */
+
+static gdb::unique_xmalloc_ptr<char>
+i386_linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
+{
+  gdb::unique_xmalloc_ptr<char> note_data
+    = linux_make_corefile_notes (gdbarch, obfd, note_size);
+
+  /* Fetch the TLS data.  */
+  std::optional<gdb::byte_vector> tls =
+    target_read_alloc (current_inferior ()->top_target (),
+                      TARGET_OBJECT_X86_LINUX_TLS_DESC, nullptr);
+  if (tls.has_value () && !tls->empty ())
+    {
+      note_data.reset (elfcore_write_note (obfd, note_data.release (),
+                                          note_size, "LINUX", NT_386_TLS,
+                                          tls->data (), tls->size ()));
+
+      if (!note_data)
+       return nullptr;
+    }
+
+  return note_data;
+}
+
 static void
 i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1488,6 +1515,9 @@ i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   set_xml_syscall_file_name (gdbarch, XML_SYSCALL_FILENAME_I386);
   set_gdbarch_get_syscall_number (gdbarch,
                                  i386_linux_get_syscall_number);
+
+  /* Custom core file notes function adds TLS descriptors.  */
+  set_gdbarch_make_corefile_notes (gdbarch, i386_linux_make_corefile_notes);
 }
 
 void _initialize_i386_linux_tdep ();
index 141c1199c8a71e97d8b2601abe37ba534f7db84c..4dbe67bc4c7fed38787fb2282f3b6b5e2335f85b 100644 (file)
@@ -2343,10 +2343,9 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
   return 1;
 }
 
-/* Build the note section for a corefile, and return it in a malloc
-   buffer.  */
+/* See linux-tdep.h.  */
 
-static gdb::unique_xmalloc_ptr<char>
+gdb::unique_xmalloc_ptr<char>
 linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 {
   struct elf_internal_linux_prpsinfo prpsinfo;
index 7485fc132a6390102b4709bd743b61ab1ae74dd2..b45535eb2531415dfba36f2c155616837d2f165b 100644 (file)
@@ -117,4 +117,14 @@ extern CORE_ADDR linux_get_hwcap2 ();
 extern struct link_map_offsets *linux_ilp32_fetch_link_map_offsets ();
 extern struct link_map_offsets *linux_lp64_fetch_link_map_offsets ();
 
+/* Build the note section for a corefile, and return it in an allocated
+   buffer.  OBFD is the bfd object the notes are being written into,
+   GDBARCH is the architecture of the notes to be written.  *NOTE_SIZE will
+   be updated with the size of the allocated buffer.  If something goes
+   wrong then this function returns NULL.  *NOTE_SIZE can be updated even
+   if NULL is returned, but the updated value is meaningless.  */
+
+extern gdb::unique_xmalloc_ptr<char>
+linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size);
+
 #endif /* GDB_LINUX_TDEP_H */
index 004494dc3c484a2ba46f65206e21e1687098ddbb..202ddf4cf27f6f27e120eb23176506b1623e0cbd 100644 (file)
@@ -208,6 +208,8 @@ enum target_object
   TARGET_OBJECT_FREEBSD_VMMAP,
   /* FreeBSD process strings.  */
   TARGET_OBJECT_FREEBSD_PS_STRINGS,
+  /* The x86/Linux GDT entries for TLS regions.  */
+  TARGET_OBJECT_X86_LINUX_TLS_DESC,
   /* Possible future objects: TARGET_OBJECT_FILE, ...  */
 };
 
index fc7c5f642eded2fc72467f26fa45f719bb6cf511..22c7e47e9f74ac7a2aaef40f357eca2766757109 100644 (file)
 #include "nat/linux-ptrace.h"
 #include "nat/x86-linux-tdesc.h"
 
+#include <asm/ldt.h>
+#include <linux/unistd.h>
+#include <linux/elf.h>
+#include <sys/user.h>
+#include <sys/procfs.h>
+#include <sys/uio.h>
+
 /* linux_nat_target::low_new_fork implementation.  */
 
 void
@@ -166,6 +173,85 @@ x86_linux_nat_target::btrace_conf (const struct btrace_target_info *btinfo)
   return linux_btrace_conf (btinfo);
 }
 
+/* ... */
+
+static int
+get_thread_area (pid_t pid, unsigned int idx, struct user_desc *ud)
+{
+  void *addr = (void *) (uintptr_t) idx;
+
+#ifndef PTRACE_GET_THREAD_AREA
+#define PTRACE_GET_THREAD_AREA 25
+#endif
+
+  if (ptrace (PTRACE_GET_THREAD_AREA, pid, addr, ud) < 0)
+    return -1;
+
+  return 0;
+}
+
+/* Get all TLS area data.  */
+
+static gdb::byte_vector
+x86_linux_get_tls_desc (unsigned int base_gdt_idx)
+{
+  static_assert (sizeof (unsigned int) == 4);
+
+  struct user_desc tls_desc[3];
+  memset (tls_desc, 0, sizeof (tls_desc));
+
+  pid_t pid = inferior_ptid.pid ();
+
+  /* Set true if we see a non-empty GDT entry.  */
+  bool seen_non_empty = false;
+
+  /* Linux reserves 3 GDT entries for TLS.  */
+  for (unsigned int pos = 0; pos < 3; ++pos)
+    {
+      if (get_thread_area (pid, base_gdt_idx + pos, &tls_desc[pos]) != 0)
+       return {};
+      seen_non_empty |= (tls_desc[pos].base_addr != 0 || tls_desc[pos].limit != 0);
+    }
+
+  /* The kernel doesn't emit NT_I386_TLS if all the descriptors are empty.  */
+  if (!seen_non_empty)
+    return {};
+
+  /* Copy the descriptors into a byte vector for return.  */
+  gdb::byte_vector buf;
+  buf.resize (sizeof (tls_desc));
+  memcpy (buf.data (), tls_desc, sizeof (tls_desc));
+  return buf;
+}
+
+/* See x86-linux-nat.h.  */
+
+enum target_xfer_status
+x86_linux_nat_target::xfer_tls_desc (gdb_byte *readbuf, const gdb_byte *writebuf,
+                                    ULONGEST offset, ULONGEST len, ULONGEST *xfered_len,
+                                    uint32_t gdt_base_idx)
+{
+  /* Don't allow writting to the TLS GDT descriptors.  */
+  if (writebuf != nullptr)
+    return TARGET_XFER_E_IO;
+
+  gdb::byte_vector buf = x86_linux_get_tls_desc (gdt_base_idx);
+  if (buf.empty ())
+    return TARGET_XFER_E_IO;
+
+  ssize_t buf_size = buf.size ();
+  if (offset >= buf_size)
+    return TARGET_XFER_EOF;
+  buf_size -= offset;
+
+  if (buf_size > len)
+    buf_size = len;
+
+  memcpy (readbuf, buf.data () + offset, buf_size);
+  *xfered_len = buf_size;
+  return TARGET_XFER_OK;
+}
+
 \f
 
 /* Helper for ps_get_thread_area.  Sets BASE_ADDR to a pointer to
index a62cc4d7ac0abc14134970ca7fa0a5830b8f49c0..9ef1784392db54df4b7841659f4b0bde5c9da7f4 100644 (file)
@@ -79,6 +79,19 @@ protected:
   /* Override the GNU/Linux inferior startup hook.  */
   void post_startup_inferior (ptid_t) override;
 
+  /* Read TLS descriptor information from the target.  Writing is TLS
+     descriptor information is not supported.  The GDB_BASE_IDX is the
+     array index into the Linux kernel get_thread_area data; the index
+     differs between i386 and amd64, but is a fixed value for each.  All
+     other arguments are as for xfer_partial.
+
+     If there is no TLS descriptor information then this function will
+     return TARGET_XFER_E_IO.  */
+  enum target_xfer_status xfer_tls_desc
+       (gdb_byte *readbuf, const gdb_byte *writebuf,
+        ULONGEST offset, ULONGEST len, ULONGEST *xfered_len,
+        uint32_t gdt_base_idx);
+
 private:
   x86_xsave_layout m_xsave_layout;
 };