[v6,05/11] Internal TLS support for aarch64, x86_64, riscv, ppc64, and s390x

Message ID 20250404234324.1931302-6-kevinb@redhat.com
State New
Headers
Series GDB-internal TLS support for Linux targets |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed

Commit Message

Kevin Buettner April 4, 2025, 11:37 p.m. UTC
  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

Reviewed-By: Luis Machado <luis.machado@arm.com>
---
 gdb/aarch64-linux-tdep.c | 56 ++++++++++++++++++++++++++++
 gdb/amd64-linux-tdep.c   | 38 +++++++++++++++++++
 gdb/configure.tgt        | 11 +++---
 gdb/ppc-linux-tdep.c     | 63 ++++++++++++++++++++++++++++++++
 gdb/riscv-linux-tdep.c   | 79 ++++++++++++++++++++++++++++++++++++++++
 gdb/s390-linux-tdep.c    | 44 ++++++++++++++++++++++
 6 files changed, 286 insertions(+), 5 deletions(-)
  

Patch

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index c8256997c97..db3a557e622 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<aarch64_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 a7868c35cd5..cd690326c30 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 18a15c032c3..d8d0c5f4aeb 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*)
@@ -534,7 +535,7 @@  riscv*-*-freebsd*)
 
 riscv*-*-linux*)
 	# Target: Linux/RISC-V
-	gdb_target_obs="riscv-linux-tdep.o glibc-tdep.o \
+	gdb_target_obs="riscv-linux-tdep.o glibc-tdep.o svr4-tls-tdep.o \
  			linux-tdep.o solib-svr4.o symfile-mem.o linux-record.o"
 	;;
 
@@ -705,7 +706,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 a9f43c43861..590c4b2a96c 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 ff478cf4c28..21345fb25c3 100644
--- a/gdb/riscv-linux-tdep.c
+++ b/gdb/riscv-linux-tdep.c
@@ -20,11 +20,13 @@ 
 #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"
 #include "trad-frame.h"
 #include "gdbarch.h"
+#include "inferior.h"
 
 /* The following value is derived from __NR_rt_sigreturn in
    <include/uapi/asm-generic/unistd.h> from the Linux source tree.  */
@@ -173,6 +175,79 @@  riscv_linux_syscall_next_pc (const frame_info_ptr &frame)
   return pc + 4 /* Length of the ECALL insn.  */;
 }
 
+/* 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
@@ -198,6 +273,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 96d6d446219..aa444a8dc00 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);