From: Andrew Burgess Date: Tue, 21 Jan 2025 17:22:04 +0000 (+0000) Subject: [wip] gdb: include NT_I386_TLS note in generated core files X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fheads%2Fusers%2Faburgess%2Fgdb-core-i386-tls;p=thirdparty%2Fbinutils-gdb.git [wip] gdb: include NT_I386_TLS note in generated core files 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. --- diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c index 75e63c67ba0..932edc09cfb 100644 --- a/gdb/amd64-linux-nat.c +++ b/gdb/amd64-linux-nat.c @@ -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) } +/* 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); +} + + /* This function is called by libthread_db as part of its handling of a request for a thread's local storage address. */ diff --git a/gdb/i386-linux-nat.c b/gdb/i386-linux-nat.c index a5d55826f08..00dc4d14016 100644 --- a/gdb/i386-linux-nat.c +++ b/gdb/i386-linux-nat.c @@ -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 () diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c index 4b05cc6870b..929eef02b98 100644 --- a/gdb/i386-linux-tdep.c +++ b/gdb/i386-linux-tdep.c @@ -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 +i386_linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size) +{ + gdb::unique_xmalloc_ptr note_data + = linux_make_corefile_notes (gdbarch, obfd, note_size); + + /* Fetch the TLS data. */ + std::optional 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 (); diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 141c1199c8a..4dbe67bc4c7 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -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 +gdb::unique_xmalloc_ptr linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size) { struct elf_internal_linux_prpsinfo prpsinfo; diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h index 7485fc132a6..b45535eb253 100644 --- a/gdb/linux-tdep.h +++ b/gdb/linux-tdep.h @@ -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 +linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size); + #endif /* GDB_LINUX_TDEP_H */ diff --git a/gdb/target.h b/gdb/target.h index 004494dc3c4..202ddf4cf27 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -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, ... */ }; diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index fc7c5f642ed..22c7e47e9f7 100644 --- a/gdb/x86-linux-nat.c +++ b/gdb/x86-linux-nat.c @@ -43,6 +43,13 @@ #include "nat/linux-ptrace.h" #include "nat/x86-linux-tdesc.h" +#include +#include +#include +#include +#include +#include + /* 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; +} + /* Helper for ps_get_thread_area. Sets BASE_ADDR to a pointer to diff --git a/gdb/x86-linux-nat.h b/gdb/x86-linux-nat.h index a62cc4d7ac0..9ef1784392d 100644 --- a/gdb/x86-linux-nat.h +++ b/gdb/x86-linux-nat.h @@ -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; };