From: Kevin Buettner Date: Thu, 24 Apr 2025 04:39:29 +0000 (-0700) Subject: Internal TLS support for aarch64, x86_64, riscv, ppc64, and s390x X-Git-Tag: binutils-2_45~806 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c34309bea221718964b2a1ec3156393df95faf3f;p=thirdparty%2Fbinutils-gdb.git Internal TLS support for aarch64, x86_64, riscv, ppc64, and s390x For each architecture, aarch64, x86_64, riscv, ppc64, and s390x, this commit defines a suitable 'get_tls_dtv_addr' method and, when necessary, a 'get_tls_dtp_offset' method. It also registers svr4_tls_get_thread_local_address, defined in svr4-tls-tdep.c (in an earlier commit), as the get_thread_local_address gdbarch method. It also registers its architecture specific code using svr4_tls_register_tls_methods(). Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24548 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563 Tested-By: Luis Machado Approved-By: Luis Machado --- diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index 9cee3554187..b6cdcf03072 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -24,6 +24,7 @@ #include "gdbarch.h" #include "glibc-tdep.h" #include "linux-tdep.h" +#include "svr4-tls-tdep.h" #include "aarch64-tdep.h" #include "aarch64-linux-tdep.h" #include "osabi.h" @@ -35,6 +36,7 @@ #include "target/target.h" #include "expop.h" #include "auxv.h" +#include "inferior.h" #include "regcache.h" #include "regset.h" @@ -2701,6 +2703,57 @@ aarch64_use_target_description_from_corefile_notes (gdbarch *gdbarch, return true; } +/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID. + Throw a suitable TLS error if something goes wrong. */ + +static CORE_ADDR +aarch64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, + svr4_tls_libc libc) +{ + /* On aarch64, the thread pointer is found in the TPIDR register. + Note that this is the first register in the TLS feature - see + features/aarch64-tls.c - and it will always be present. */ + regcache *regcache + = get_thread_arch_regcache (current_inferior (), ptid, gdbarch); + aarch64_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + target_fetch_registers (regcache, tdep->tls_regnum_base); + ULONGEST thr_ptr; + if (regcache->cooked_read (tdep->tls_regnum_base, &thr_ptr) != REG_VALID) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer")); + + CORE_ADDR dtv_ptr_addr; + switch (libc) + { + case svr4_tls_libc_musl: + /* MUSL: The DTV pointer is found at the very end of the pthread + struct which is located *before* the thread pointer. I.e. + the thread pointer will be just beyond the end of the struct, + so the address of the DTV pointer is found one pointer-size + before the thread pointer. */ + dtv_ptr_addr = thr_ptr - (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + break; + case svr4_tls_libc_glibc: + /* GLIBC: The thread pointer (tpidr) points at the TCB (thread control + block). On aarch64, this struct (tcbhead_t) is defined to + contain two pointers. The first is a pointer to the DTV and + the second is a pointer to private data. So the DTV pointer + address is the same as the thread pointer. */ + dtv_ptr_addr = thr_ptr; + break; + default: + throw_error (TLS_GENERIC_ERROR, _("Unknown aarch64 C library")); + break; + } + gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address")); + + const struct builtin_type *builtin = builtin_type (gdbarch); + CORE_ADDR dtv_addr = gdbarch_pointer_to_address + (gdbarch, builtin->builtin_data_ptr, buf.data ()); + return dtv_addr; +} + static void aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) { @@ -2722,6 +2775,9 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + set_gdbarch_get_thread_local_address (gdbarch, + svr4_tls_get_thread_local_address); + svr4_tls_register_tls_methods (info, gdbarch, aarch64_linux_get_tls_dtv_addr); /* Shared library handling. */ set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target); diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index 494e744bd1c..e5a2ab9dd52 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -33,7 +33,9 @@ #include "amd64-linux-tdep.h" #include "i386-linux-tdep.h" #include "linux-tdep.h" +#include "svr4-tls-tdep.h" #include "gdbsupport/x86-xstate.h" +#include "inferior.h" #include "amd64-tdep.h" #include "solib-svr4.h" @@ -1832,6 +1834,39 @@ amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch, return (addr & amd64_linux_lam_untag_mask ()); } +/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID. + Throw a suitable TLS error if something goes wrong. */ + +static CORE_ADDR +amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, + enum svr4_tls_libc libc) +{ + /* On x86-64, the thread pointer is found in the fsbase register. */ + regcache *regcache + = get_thread_arch_regcache (current_inferior (), ptid, gdbarch); + target_fetch_registers (regcache, AMD64_FSBASE_REGNUM); + ULONGEST fsbase; + if (regcache->cooked_read (AMD64_FSBASE_REGNUM, &fsbase) != REG_VALID) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer")); + + /* The thread pointer (fsbase) points at the TCB (thread control + block). The first two members of this struct are both pointers, + where the first will be a pointer to the TCB (i.e. it points at + itself) and the second will be a pointer to the DTV (dynamic + thread vector). There are many other fields too, but the one + we care about here is the DTV pointer. Compute the address + of the DTV pointer, fetch it, and convert it to an address. */ + CORE_ADDR dtv_ptr_addr = fsbase + gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT; + gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address")); + + const struct builtin_type *builtin = builtin_type (gdbarch); + CORE_ADDR dtv_addr = gdbarch_pointer_to_address + (gdbarch, builtin->builtin_data_ptr, buf.data ()); + return dtv_addr; +} + static void amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch, int num_disp_step_buffers) @@ -1862,6 +1897,9 @@ amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch, /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + set_gdbarch_get_thread_local_address (gdbarch, + svr4_tls_get_thread_local_address); + svr4_tls_register_tls_methods (info, gdbarch, amd64_linux_get_tls_dtv_addr); /* GNU/Linux uses SVR4-style shared libraries. */ set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target); diff --git a/gdb/configure.tgt b/gdb/configure.tgt index 44da2257be8..72cdceeaed9 100644 --- a/gdb/configure.tgt +++ b/gdb/configure.tgt @@ -150,7 +150,7 @@ aarch64*-*-linux*) arch/aarch64-scalable-linux.o \ arch/arm.o arch/arm-linux.o arch/arm-get-next-pcs.o \ arm-tdep.o arm-linux-tdep.o \ - glibc-tdep.o linux-tdep.o solib-svr4.o \ + glibc-tdep.o linux-tdep.o solib-svr4.o svr4-tls-tdep.o \ symfile-mem.o linux-record.o" ;; @@ -503,7 +503,7 @@ powerpc-*-aix* | rs6000-*-* | powerpc64-*-aix*) powerpc*-*-linux*) # Target: PowerPC running Linux gdb_target_obs="rs6000-tdep.o ppc-linux-tdep.o ppc-sysv-tdep.o \ - ppc64-tdep.o solib-svr4.o \ + ppc64-tdep.o solib-svr4.o svr4-tls-tdep.o \ glibc-tdep.o symfile-mem.o linux-tdep.o \ ravenscar-thread.o ppc-ravenscar-thread.o \ linux-record.o \ @@ -524,7 +524,8 @@ powerpc*-*-*) s390*-*-linux*) # Target: S390 running Linux gdb_target_obs="s390-linux-tdep.o s390-tdep.o solib-svr4.o \ - linux-tdep.o linux-record.o symfile-mem.o" + linux-tdep.o linux-record.o symfile-mem.o \ + svr4-tls-tdep.o" ;; riscv*-*-freebsd*) @@ -536,7 +537,7 @@ riscv*-*-linux*) # Target: Linux/RISC-V gdb_target_obs="riscv-linux-tdep.o riscv-canonicalize-syscall-gen.o \ glibc-tdep.o linux-tdep.o solib-svr4.o symfile-mem.o \ - linux-record.o" + linux-record.o svr4-tls-tdep.o" ;; riscv*-*-*) @@ -706,7 +707,7 @@ x86_64-*-elf*) x86_64-*-linux*) # Target: GNU/Linux x86-64 gdb_target_obs="amd64-linux-tdep.o ${i386_tobjs} \ - i386-linux-tdep.o glibc-tdep.o \ + i386-linux-tdep.o glibc-tdep.o svr4-tls-tdep.o \ solib-svr4.o symfile-mem.o linux-tdep.o linux-record.o \ arch/i386-linux-tdesc.o arch/amd64-linux-tdesc.o \ arch/x86-linux-tdesc-features.o" diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c index 3050a9e2177..441f3176751 100644 --- a/gdb/ppc-linux-tdep.c +++ b/gdb/ppc-linux-tdep.c @@ -49,6 +49,7 @@ #include "arch-utils.h" #include "xml-syscall.h" #include "linux-tdep.h" +#include "svr4-tls-tdep.h" #include "linux-record.h" #include "record-full.h" #include "infrun.h" @@ -2071,6 +2072,63 @@ ppc64_linux_gcc_target_options (struct gdbarch *gdbarch) return ""; } +/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID. + Throw a suitable TLS error if something goes wrong. */ + +static CORE_ADDR +ppc64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, + enum svr4_tls_libc libc) +{ + /* On ppc64, the thread pointer is found in r13. Fetch this + register. */ + regcache *regcache + = get_thread_arch_regcache (current_inferior (), ptid, gdbarch); + int thread_pointer_regnum = PPC_R0_REGNUM + 13; + target_fetch_registers (regcache, thread_pointer_regnum); + ULONGEST thr_ptr; + if (regcache->cooked_read (thread_pointer_regnum, &thr_ptr) != REG_VALID) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer")); + + /* The thread pointer (r13) is an address that is 0x7000 ahead of + the *end* of the TCB (thread control block). The field + holding the DTV address is at the very end of the TCB. + Therefore, the DTV pointer address can be found by + subtracting (0x7000+8) from the thread pointer. Compute the + address of the DTV pointer, fetch it, and convert it to an + address. */ + CORE_ADDR dtv_ptr_addr = thr_ptr - 0x7000 - 8; + gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address")); + + const struct builtin_type *builtin = builtin_type (gdbarch); + CORE_ADDR dtv_addr = gdbarch_pointer_to_address + (gdbarch, builtin->builtin_data_ptr, buf.data ()); + return dtv_addr; +} + +/* For internal TLS lookup, return the DTP offset, which is the offset + to subtract from a DTV entry, in order to obtain the address of the + TLS block. */ + +static ULONGEST +ppc_linux_get_tls_dtp_offset (struct gdbarch *gdbarch, ptid_t ptid, + svr4_tls_libc libc) +{ + if (libc == svr4_tls_libc_musl) + { + /* This value is DTP_OFFSET, which represents the value to + subtract from the DTV entry. For PPC, it can be found in + MUSL's arch/powerpc64/pthread_arch.h and + arch/powerpc32/pthread_arch.h. (Both values are the same.) + It represents the value to subtract from the DTV entry, once + it has been fetched from the DTV array. */ + return 0x8000; + } + else + return 0; +} + static displaced_step_prepare_status ppc_linux_displaced_step_prepare (gdbarch *arch, thread_info *thread, CORE_ADDR &displaced_pc) @@ -2284,6 +2342,11 @@ ppc_linux_init_abi (struct gdbarch_info info, set_gdbarch_gnu_triplet_regexp (gdbarch, ppc64_gnu_triplet_regexp); /* Set GCC target options. */ set_gdbarch_gcc_target_options (gdbarch, ppc64_linux_gcc_target_options); + /* Internal thread local address support. */ + set_gdbarch_get_thread_local_address (gdbarch, + svr4_tls_get_thread_local_address); + svr4_tls_register_tls_methods (info, gdbarch, ppc64_linux_get_tls_dtv_addr, + ppc_linux_get_tls_dtp_offset); } set_gdbarch_core_read_description (gdbarch, ppc_linux_core_read_description); diff --git a/gdb/riscv-linux-tdep.c b/gdb/riscv-linux-tdep.c index f21039a613c..e1ea6158a45 100644 --- a/gdb/riscv-linux-tdep.c +++ b/gdb/riscv-linux-tdep.c @@ -20,6 +20,7 @@ #include "osabi.h" #include "glibc-tdep.h" #include "linux-tdep.h" +#include "svr4-tls-tdep.h" #include "solib-svr4.h" #include "regset.h" #include "tramp-frame.h" @@ -28,6 +29,7 @@ #include "record-full.h" #include "linux-record.h" #include "riscv-linux-tdep.h" +#include "inferior.h" extern unsigned int record_debug; @@ -426,6 +428,79 @@ riscv64_linux_record_tdep_init (struct gdbarch *gdbarch, riscv_linux_record_tdep.arg6 = RISCV_A5_REGNUM; } +/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID. + Throw a suitable TLS error if something goes wrong. */ + +static CORE_ADDR +riscv_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, + svr4_tls_libc libc) +{ + /* On RISC-V, the thread pointer is found in TP. */ + regcache *regcache + = get_thread_arch_regcache (current_inferior (), ptid, gdbarch); + int thread_pointer_regnum = RISCV_TP_REGNUM; + target_fetch_registers (regcache, thread_pointer_regnum); + ULONGEST thr_ptr; + if (regcache->cooked_read (thread_pointer_regnum, &thr_ptr) != REG_VALID) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer")); + + CORE_ADDR dtv_ptr_addr; + switch (libc) + { + case svr4_tls_libc_musl: + /* MUSL: The DTV pointer is found at the very end of the pthread + struct which is located *before* the thread pointer. I.e. + the thread pointer will be just beyond the end of the struct, + so the address of the DTV pointer is found one pointer-size + before the thread pointer. */ + dtv_ptr_addr + = thr_ptr - (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + break; + case svr4_tls_libc_glibc: + /* GLIBC: The thread pointer (TP) points just beyond the end of + the TCB (thread control block). On RISC-V, this struct + (tcbhead_t) is defined to contain two pointers. The first is + a pointer to the DTV and the second is a pointer to private + data. So the DTV pointer address is 16 bytes (i.e. the size of + two pointers) before thread pointer. */ + + dtv_ptr_addr + = thr_ptr - 2 * (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + break; + default: + throw_error (TLS_GENERIC_ERROR, _("Unknown RISC-V C library")); + break; + } + + gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address")); + + const struct builtin_type *builtin = builtin_type (gdbarch); + CORE_ADDR dtv_addr = gdbarch_pointer_to_address + (gdbarch, builtin->builtin_data_ptr, buf.data ()); + return dtv_addr; +} + +/* For internal TLS lookup, return the DTP offset, which is the offset + to subtract from a DTV entry, in order to obtain the address of the + TLS block. */ + +static ULONGEST +riscv_linux_get_tls_dtp_offset (struct gdbarch *gdbarch, ptid_t ptid, + svr4_tls_libc libc) +{ + if (libc == svr4_tls_libc_musl) + { + /* This value is DTP_OFFSET in MUSL's arch/riscv64/pthread_arch.h. + It represents the value to subtract from the DTV entry, once + it has been loaded. */ + return 0x800; + } + else + return 0; +} + /* Initialize RISC-V Linux ABI info. */ static void @@ -451,6 +526,10 @@ riscv_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + set_gdbarch_get_thread_local_address (gdbarch, + svr4_tls_get_thread_local_address); + svr4_tls_register_tls_methods (info, gdbarch, riscv_linux_get_tls_dtv_addr, + riscv_linux_get_tls_dtp_offset); set_gdbarch_iterate_over_regset_sections (gdbarch, riscv_linux_iterate_over_regset_sections); diff --git a/gdb/s390-linux-tdep.c b/gdb/s390-linux-tdep.c index 04c523b979b..bd1f42ca8d2 100644 --- a/gdb/s390-linux-tdep.c +++ b/gdb/s390-linux-tdep.c @@ -29,6 +29,7 @@ #include "gdbcore.h" #include "linux-record.h" #include "linux-tdep.h" +#include "svr4-tls-tdep.h" #include "objfiles.h" #include "osabi.h" #include "regcache.h" @@ -40,6 +41,7 @@ #include "target.h" #include "trad-frame.h" #include "xml-syscall.h" +#include "inferior.h" #include "features/s390-linux32v1.c" #include "features/s390-linux32v2.c" @@ -1124,6 +1126,45 @@ s390_init_linux_record_tdep (struct linux_record_tdep *record_tdep, record_tdep->ioctl_FIOQSIZE = 0x545e; } +/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID. + Throw a suitable TLS error if something goes wrong. */ + +static CORE_ADDR +s390_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, + enum svr4_tls_libc libc) +{ + /* On S390, the thread pointer is found in two registers A0 and A1 + (or, using gdb naming, acr0 and acr1) A0 contains the top 32 + bits of the address and A1 contains the bottom 32 bits. */ + regcache *regcache + = get_thread_arch_regcache (current_inferior (), ptid, gdbarch); + target_fetch_registers (regcache, S390_A0_REGNUM); + target_fetch_registers (regcache, S390_A1_REGNUM); + ULONGEST thr_ptr_lo, thr_ptr_hi, thr_ptr; + if (regcache->cooked_read (S390_A0_REGNUM, &thr_ptr_hi) != REG_VALID + || regcache->cooked_read (S390_A1_REGNUM, &thr_ptr_lo) != REG_VALID) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer")); + thr_ptr = (thr_ptr_hi << 32) + thr_ptr_lo; + + /* The thread pointer points at the TCB (thread control block). The + first two members of this struct are both pointers, where the + first will be a pointer to the TCB (i.e. it points at itself) + and the second will be a pointer to the DTV (dynamic thread + vector). There are many other fields too, but the one we care + about here is the DTV pointer. Compute the address of the DTV + pointer, fetch it, and convert it to an address. */ + CORE_ADDR dtv_ptr_addr + = thr_ptr + gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT; + gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT); + if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0) + throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address")); + + const struct builtin_type *builtin = builtin_type (gdbarch); + CORE_ADDR dtv_addr = gdbarch_pointer_to_address + (gdbarch, builtin->builtin_data_ptr, buf.data ()); + return dtv_addr; +} + /* Initialize OSABI common for GNU/Linux on 31- and 64-bit systems. */ static void @@ -1152,6 +1193,9 @@ s390_linux_init_abi_any (struct gdbarch_info info, struct gdbarch *gdbarch) /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + set_gdbarch_get_thread_local_address (gdbarch, + svr4_tls_get_thread_local_address); + svr4_tls_register_tls_methods (info, gdbarch, s390_linux_get_tls_dtv_addr); /* Support reverse debugging. */ set_gdbarch_process_record_signal (gdbarch, s390_linux_record_signal);