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

Message ID 20241010022552.47637-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
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Kevin Buettner Oct. 10, 2024, 2:16 a.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 linux_get_thread_local_address, defined in
linux-tdep.c (in an earlier commit), as the get_thread_local_address
gdbarch method.  It also registers its architecture specific code
using linux_register_tls_methods().

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24548
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
---
 gdb/aarch64-linux-tdep.c | 53 +++++++++++++++++++++++++++
 gdb/amd64-linux-tdep.c   | 37 +++++++++++++++++++
 gdb/ppc-linux-tdep.c     | 62 ++++++++++++++++++++++++++++++++
 gdb/riscv-linux-tdep.c   | 78 ++++++++++++++++++++++++++++++++++++++++
 gdb/s390-linux-tdep.c    | 43 ++++++++++++++++++++++
 5 files changed, 273 insertions(+)
  

Comments

Luis Machado Oct. 11, 2024, 8:12 a.m. UTC | #1
Thanks for the patch.

On 10/10/24 03:16, Kevin Buettner wrote:
> 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 linux_get_thread_local_address, defined in
> linux-tdep.c (in an earlier commit), as the get_thread_local_address
> gdbarch method.  It also registers its architecture specific code
> using linux_register_tls_methods().
> 
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24548
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
> ---
>  gdb/aarch64-linux-tdep.c | 53 +++++++++++++++++++++++++++
>  gdb/amd64-linux-tdep.c   | 37 +++++++++++++++++++
>  gdb/ppc-linux-tdep.c     | 62 ++++++++++++++++++++++++++++++++
>  gdb/riscv-linux-tdep.c   | 78 ++++++++++++++++++++++++++++++++++++++++
>  gdb/s390-linux-tdep.c    | 43 ++++++++++++++++++++++
>  5 files changed, 273 insertions(+)
> 
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index c608a84bc71..b03c2162a1a 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -35,6 +35,7 @@
>  #include "target/target.h"
>  #include "expop.h"
>  #include "auxv.h"
> +#include "inferior.h"
>  
>  #include "regcache.h"
>  #include "regset.h"
> @@ -2693,6 +2694,55 @@ 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,
> +				linux_libc libc)
> +{
> +  /* On aarch64, the thread pointer is found in the tpidr register.  */
> +  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)

Since we're relying on TPIDR now, might be worth mentioning it is currently the
first register in the TLS feature. Just in case things change later on?

Otherwise the AArch64 bits look OK to me.

Reviewed-By: Luis Machado <luis.machado@arm.com>
  
Kevin Buettner Oct. 11, 2024, 7:48 p.m. UTC | #2
On Fri, 11 Oct 2024 09:12:55 +0100
Luis Machado <luis.machado@arm.com> wrote:

> Thanks for the patch.
> 
> On 10/10/24 03:16, Kevin Buettner wrote:
> > 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 linux_get_thread_local_address, defined in
> > linux-tdep.c (in an earlier commit), as the get_thread_local_address
> > gdbarch method.  It also registers its architecture specific code
> > using linux_register_tls_methods().
> > 
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24548
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
> > ---
> >  gdb/aarch64-linux-tdep.c | 53 +++++++++++++++++++++++++++
> >  gdb/amd64-linux-tdep.c   | 37 +++++++++++++++++++
> >  gdb/ppc-linux-tdep.c     | 62 ++++++++++++++++++++++++++++++++
> >  gdb/riscv-linux-tdep.c   | 78 ++++++++++++++++++++++++++++++++++++++++
> >  gdb/s390-linux-tdep.c    | 43 ++++++++++++++++++++++
> >  5 files changed, 273 insertions(+)
> > 
> > diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> > index c608a84bc71..b03c2162a1a 100644
> > --- a/gdb/aarch64-linux-tdep.c
> > +++ b/gdb/aarch64-linux-tdep.c
> > @@ -35,6 +35,7 @@
> >  #include "target/target.h"
> >  #include "expop.h"
> >  #include "auxv.h"
> > +#include "inferior.h"
> >  
> >  #include "regcache.h"
> >  #include "regset.h"
> > @@ -2693,6 +2694,55 @@ 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,
> > +				linux_libc libc)
> > +{
> > +  /* On aarch64, the thread pointer is found in the tpidr register.  */
> > +  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)  
> 
> Since we're relying on TPIDR now, might be worth mentioning it is currently the
> first register in the TLS feature. Just in case things change later on?

I've changed the relevant comment in my local sources to read as follows:

  /* 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.  */

Kevin
  

Patch

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index c608a84bc71..b03c2162a1a 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -35,6 +35,7 @@ 
 #include "target/target.h"
 #include "expop.h"
 #include "auxv.h"
+#include "inferior.h"
 
 #include "regcache.h"
 #include "regset.h"
@@ -2693,6 +2694,55 @@  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,
+				linux_libc libc)
+{
+  /* On aarch64, the thread pointer is found in the tpidr register.  */
+  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 linux_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 linux_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)
 {
@@ -2714,6 +2764,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,
+					linux_get_thread_local_address);
+  linux_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 77de8211d86..7512448d987 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -34,6 +34,7 @@ 
 #include "i386-linux-tdep.h"
 #include "linux-tdep.h"
 #include "gdbsupport/x86-xstate.h"
+#include "inferior.h"
 
 #include "amd64-tdep.h"
 #include "solib-svr4.h"
@@ -1767,6 +1768,39 @@  amd64_dtrace_parse_probe_argument (struct gdbarch *gdbarch,
     }
 }
 
+/* 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 linux_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)
@@ -1797,6 +1831,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,
+					linux_get_thread_local_address);
+  linux_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/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
index 8a5eea765c2..42a4bc71263 100644
--- a/gdb/ppc-linux-tdep.c
+++ b/gdb/ppc-linux-tdep.c
@@ -2070,6 +2070,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 linux_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,
+			      linux_libc libc)
+{
+  if (libc == linux_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)
@@ -2283,6 +2340,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,
+					    linux_get_thread_local_address);
+      linux_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..31d90a32ce4 100644
--- a/gdb/riscv-linux-tdep.c
+++ b/gdb/riscv-linux-tdep.c
@@ -25,6 +25,7 @@ 
 #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 +174,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,
+			      linux_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 linux_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 linux_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,
+				linux_libc libc)
+{
+  if (libc == linux_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 +272,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,
+					linux_get_thread_local_address);
+  linux_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 bc1db550d2e..17055d9f7b5 100644
--- a/gdb/s390-linux-tdep.c
+++ b/gdb/s390-linux-tdep.c
@@ -40,6 +40,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"
@@ -1123,6 +1124,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 linux_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
@@ -1151,6 +1191,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,
+					linux_get_thread_local_address);
+  linux_register_tls_methods (info, gdbarch, s390_linux_get_tls_dtv_addr);
 
   /* Support reverse debugging.  */
   set_gdbarch_process_record_signal (gdbarch, s390_linux_record_signal);