diff options
-rw-r--r-- | gdb/amd64-linux-nat.c | 25 | ||||
-rw-r--r-- | gdb/i386-linux-nat.c | 24 | ||||
-rw-r--r-- | gdb/i386-linux-tdep.c | 30 | ||||
-rw-r--r-- | gdb/linux-tdep.c | 5 | ||||
-rw-r--r-- | gdb/linux-tdep.h | 10 | ||||
-rw-r--r-- | gdb/target.h | 2 | ||||
-rw-r--r-- | gdb/x86-linux-nat.c | 86 | ||||
-rw-r--r-- | gdb/x86-linux-nat.h | 13 |
8 files changed, 192 insertions, 3 deletions
diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c index 75e63c6..932edc0 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 a5d5582..00dc4d1 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 4b05cc6..929eef0 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<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 (); diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 141c119..4dbe67b 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<char> +gdb::unique_xmalloc_ptr<char> 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 7485fc1..b45535e 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<char> +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 004494d..202ddf4 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 fc7c5f6..22c7e47 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 <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; +} + /* 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 a62cc4d..9ef1784 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; }; |