[BZ#17090/17620/17621] : fix DTV race, assert, and DTV_SURPLUS Static TLS limit

Message ID oregoomeda.fsf@livre.home
State Committed
Headers

Commit Message

Alexandre Oliva March 17, 2015, 4:34 a.m. UTC
  On Mar 16, 2015, Richard Henderson <rth@twiddle.net> wrote:

> On 03/05/2015 05:42 PM, Alexandre Oliva wrote:
>> On Mar  5, 2015, Alexandre Oliva <aoliva@redhat.com> wrote:
>> 
>>> > On Mar  4, 2015, Alan Modra <amodra@gmail.com> wrote:
>>>> >> On Wed, Mar 04, 2015 at 03:35:29PM +1030, Alan Modra wrote:
>>>>> >>> As does nptl/tst-stack4 on x86_64 if tst-stack4mod.so is built with
>>>>> >>> -mtls-dialect=gnu2.
>>>> >> This on top of your patch gets me past the segfault in free().
>>>> >> I now hit another segfault, tst-stack4mod.c:function somehow has
>>>> >> var == NULL.
>>> > Thanks, here's an incremental patch that fixes the attempt to release
>>> > the pointer from an entry past the end of the dtv (that you fixed
>>> > above); that ensures we don't use, in a TLS Descriptor, the generation
>>> > count of an earlier map that used the same dtv slot (this ensures we
>>> > update the DTV instead of happily using a NULL pointer in there); and
>>> > that silences a -Wundef warning in nptl_db, that I had missed when I
>>> > updated the #ifs for the last-posted version of the patch.
>>> > I'm now running a full build and test cycle with the combined patch,
>>> > that I will post once it (hopefully ;-) completes successfully.
>> Success (x86_64-linux-gnu, with -mtls-dialect=gnu2)!

> The patch looks good to me.  (And success with aarch64-linux-gnu!)

Thanks.  Here's what I've just pushed to master, after adjusting the
patch for changed context in NEWS and nptl_db/td_symbol_list.c.


Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage

for  ChangeLog

	[BZ #17090]
	[BZ #17620]
	[BZ #17621]
	[BZ #17628]
	* NEWS: Update.
	* elf/dl-tls.c (_dl_update_slotinfo): Clean up outdated DTV
	entries with Static TLS too.  Skip entries past the end of the
	allocated DTV, from Alan Modra.
	(tls_get_addr_tail): Update to glibc_likely/unlikely.  Move
	Static TLS DTV entry set up from...
	 (_dl_allocate_tls_init): ... here (fix modid assertion), ...
	* elf/dl-reloc.c (_dl_nothread_init_static_tls): ... here...
	* nptl/allocatestack.c (init_one_static_tls): ... and here...
	* elf/dlopen.c (dl_open_worker): Drop l_tls_modid upper bound
	for Static TLS.
	* elf/tlsdeschtab.h (map_generation): Return size_t.  Check
	that the slot we find is associated with the given map before
	using its generation count.
	* nptl_db/db_info.c: Include ldsodefs.h.
	(rtld_global, dtv_slotinfo_list, dtv_slotinfo): New typedefs.
	* nptl_db/structs.def (DB_RTLD_VARIABLE): New macro.
	(DB_MAIN_VARIABLE, DB_RTLD_GLOBAL_FIELD): Likewise.
	(link_map::l_tls_offset): New struct field.
	(dtv_t::counter): Likewise.
	(rtld_global): New struct.
	(_rtld_global): New rtld variable.
	(dl_tls_dtv_slotinfo_list): New rtld global field.
	(dtv_slotinfo_list): New struct.
	(dtv_slotinfo): Likewise.
	* nptl_db/td_symbol_list.c: Drop gnu/lib-names.h include.
	(td_lookup): Rename to...
	(td_mod_lookup): ... this.  Use new mod parameter instead of
	LIBPTHREAD_SO.
	* nptl_db/td_thr_tlsbase.c: Include link.h.
	(dtv_slotinfo_list, dtv_slotinfo): New functions.
	(td_thr_tlsbase): Check DTV generation.  Compute Static TLS
	addresses even if the DTV is out of date or missing them.
	* nptl_db/fetch-value.c (_td_locate_field): Do not refuse to
	index zero-length arrays.
	* nptl_db/thread_dbP.h: Include gnu/lib-names.h.
	(td_lookup): Make it a macro implemented in terms of...
	(td_mod_lookup): ... this declaration.
	* nptl_db/db-symbols.awk (DB_RTLD_VARIABLE): Override.
	(DB_MAIN_VARIABLE): Likewise.
---
 NEWS                     |   11 ++-
 elf/dl-open.c            |   12 ---
 elf/dl-reloc.c           |    6 --
 elf/dl-tls.c             |   63 +++++++++--------
 elf/tlsdeschtab.h        |    4 +
 nptl/allocatestack.c     |    9 +-
 nptl_db/db-symbols.awk   |    2 +
 nptl_db/db_info.c        |    4 +
 nptl_db/fetch-value.c    |    3 +
 nptl_db/structs.def      |   39 ++++++++++
 nptl_db/td_symbol_list.c |    7 +-
 nptl_db/td_thr_tlsbase.c |  172 +++++++++++++++++++++++++++++++++++++++++++++-
 nptl_db/thread_dbP.h     |   11 ++-
 13 files changed, 270 insertions(+), 73 deletions(-)
  

Comments

Szabolcs Nagy March 18, 2015, 2:46 p.m. UTC | #1
On 17/03/15 04:34, Alexandre Oliva wrote:
> On Mar 16, 2015, Richard Henderson <rth@twiddle.net> wrote:
>> The patch looks good to me.  (And success with aarch64-linux-gnu!)
> 
> Thanks.  Here's what I've just pushed to master, after adjusting the
> patch for changed context in NEWS and nptl_db/td_symbol_list.c.
> 
> 
> Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage
> 

this is still not a complete fix
i can see races on aarch64 and x86_64 with the same test

i described what i think the problem is on

https://sourceware.org/bugzilla/show_bug.cgi?id=17918

i think the _rtld* members that are accessed without holding
the rtld lock should only be accessed using atomics (at least
_dl_tls_generation and _dl_tls_max_dtv_idx are affected)

and may be they should be volatile too to tell the compiler
not to add spurious load/store to these accesses (since
glibc does not use c11 atomics yet)
  
Alexandre Oliva June 9, 2015, 3:49 a.m. UTC | #2
On Mar 18, 2015, Szabolcs Nagy <szabolcs.nagy@arm.com> wrote:

> i think the _rtld* members that are accessed without holding
> the rtld lock should only be accessed using atomics (at least
> _dl_tls_generation and _dl_tls_max_dtv_idx are affected)

Is this problem the one you fixed with the patch in the other thread
I've just commented on?

Thanks!
  
Szabolcs Nagy June 9, 2015, 9:38 a.m. UTC | #3
On 09/06/15 04:49, Alexandre Oliva wrote:
> On Mar 18, 2015, Szabolcs Nagy <szabolcs.nagy@arm.com> wrote:
> 
>> i think the _rtld* members that are accessed without holding
>> the rtld lock should only be accessed using atomics (at least
>> _dl_tls_generation and _dl_tls_max_dtv_idx are affected)
> 
> Is this problem the one you fixed with the patch in the other thread
> I've just commented on?

no,

the patch is for lazy initialization of tlsdesc on aarch64 [BZ 18034]

this one is about dlopen/dlclose while creating threads [BZ 17918]
it causes

Inconsistency detected by ld.so: dl-tls.c: 493: _dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen <= _rtld_local._dl_tls_generation' failed!
  
Aurelien Jarno Nov. 23, 2015, 3:15 p.m. UTC | #4
On 2015-03-17 01:34, Alexandre Oliva wrote:
> On Mar 16, 2015, Richard Henderson <rth@twiddle.net> wrote:
> 
> > On 03/05/2015 05:42 PM, Alexandre Oliva wrote:
> >> On Mar  5, 2015, Alexandre Oliva <aoliva@redhat.com> wrote:
> >> 
> >>> > On Mar  4, 2015, Alan Modra <amodra@gmail.com> wrote:
> >>>> >> On Wed, Mar 04, 2015 at 03:35:29PM +1030, Alan Modra wrote:
> >>>>> >>> As does nptl/tst-stack4 on x86_64 if tst-stack4mod.so is built with
> >>>>> >>> -mtls-dialect=gnu2.
> >>>> >> This on top of your patch gets me past the segfault in free().
> >>>> >> I now hit another segfault, tst-stack4mod.c:function somehow has
> >>>> >> var == NULL.
> >>> > Thanks, here's an incremental patch that fixes the attempt to release
> >>> > the pointer from an entry past the end of the dtv (that you fixed
> >>> > above); that ensures we don't use, in a TLS Descriptor, the generation
> >>> > count of an earlier map that used the same dtv slot (this ensures we
> >>> > update the DTV instead of happily using a NULL pointer in there); and
> >>> > that silences a -Wundef warning in nptl_db, that I had missed when I
> >>> > updated the #ifs for the last-posted version of the patch.
> >>> > I'm now running a full build and test cycle with the combined patch,
> >>> > that I will post once it (hopefully ;-) completes successfully.
> >> Success (x86_64-linux-gnu, with -mtls-dialect=gnu2)!
> 
> > The patch looks good to me.  (And success with aarch64-linux-gnu!)
> 
> Thanks.  Here's what I've just pushed to master, after adjusting the
> patch for changed context in NEWS and nptl_db/td_symbol_list.c.
> 
> 
> Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage

Sorry for the (huge) delay. I have identified that this patch is the
reason why the test nptl/tst-cancel24-static now fails with a segfault
on at least arm and mips [1].

Do you have an idea what could be the reason?

Thanks,
Aurelien

[1] https://sourceware.org/glibc/wiki/Release/2.22
  
Andreas Schwab Sept. 15, 2016, 2:51 p.m. UTC | #5
On Mär 17 2015, Alexandre Oliva <aoliva@redhat.com> wrote:

> Thanks.  Here's what I've just pushed to master, after adjusting the
> patch for changed context in NEWS and nptl_db/td_symbol_list.c.
>
>
> Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage
>
> for  ChangeLog
>
> 	[BZ #17090]
> 	[BZ #17620]
> 	[BZ #17621]
> 	[BZ #17628]

This breaks TLS in static programs on armv7, see BZ #19826.

Andreas.
  
Alexandre Oliva Sept. 23, 2016, 3:21 p.m. UTC | #6
On Sep 15, 2016, Andreas Schwab <schwab@suse.de> wrote:

> On Mär 17 2015, Alexandre Oliva <aoliva@redhat.com> wrote:
>> Thanks.  Here's what I've just pushed to master, after adjusting the
>> patch for changed context in NEWS and nptl_db/td_symbol_list.c.
>> 
>> 
>> Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage
>> 
>> for  ChangeLog
>> 
>> [BZ #17090]
>> [BZ #17620]
>> [BZ #17621]
>> [BZ #17628]

> This breaks TLS in static programs on armv7, see BZ #19826.

Do we want the fix for BZ#19826 backported to older branches?
  
Florian Weimer Sept. 23, 2016, 8:39 p.m. UTC | #7
* Alexandre Oliva:

> On Sep 15, 2016, Andreas Schwab <schwab@suse.de> wrote:
>
>> On Mär 17 2015, Alexandre Oliva <aoliva@redhat.com> wrote:
>>> Thanks.  Here's what I've just pushed to master, after adjusting the
>>> patch for changed context in NEWS and nptl_db/td_symbol_list.c.
>>> 
>>> 
>>> Fix DTV race, assert, DTV_SURPLUS Static TLS limit, and nptl_db garbage
>>> 
>>> for  ChangeLog
>>> 
>>> [BZ #17090]
>>> [BZ #17620]
>>> [BZ #17621]
>>> [BZ #17628]
>
>> This breaks TLS in static programs on armv7, see BZ #19826.
>
> Do we want the fix for BZ#19826 backported to older branches?

That would make sense, yes.
  

Patch

diff --git a/NEWS b/NEWS
index 4ef81d3..86394b8 100644
--- a/NEWS
+++ b/NEWS
@@ -10,11 +10,12 @@  Version 2.22
 * The following bugs are resolved with this release:
 
   4719, 13064, 14094, 14841, 14906, 15319, 15467, 15790, 15969, 16351,
-  16512, 16560, 16783, 17269, 17523, 17569, 17588, 17631, 17711, 17776,
-  17779, 17792, 17836, 17912, 17916, 17932, 17944, 17949, 17964, 17965,
-  17967, 17969, 17978, 17987, 17991, 17996, 17998, 17999, 18019, 18020,
-  18029, 18030, 18032, 18036, 18038, 18039, 18042, 18043, 18046, 18047,
-  18068, 18080, 18093, 18104, 18110, 18111, 18128.
+  16512, 16560, 16783, 17090, 17269, 17523, 17569, 17588, 17620, 17621,
+  17628, 17631, 17711, 17776, 17779, 17792, 17836, 17912, 17916, 17932,
+  17944, 17949, 17964, 17965, 17967, 17969, 17978, 17987, 17991, 17996,
+  17998, 17999, 18019, 18020, 18029, 18030, 18032, 18036, 18038, 18039,
+  18042, 18043, 18046, 18047, 18068, 18080, 18093, 18104, 18110, 18111,
+  18128.
 
 * Character encoding and ctype tables were updated to Unicode 7.0.0, using
   new generator scripts contributed by Pravin Satpute and Mike FABIAN (Red
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 47b4cb5..0dbe07f 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -533,17 +533,7 @@  TLS generation counter wrapped!  Please report this."));
 	  && imap->l_tls_blocksize > 0)
 	{
 	  /* For static TLS we have to allocate the memory here and
-	     now.  This includes allocating memory in the DTV.  But we
-	     cannot change any DTV other than our own.  So, if we
-	     cannot guarantee that there is room in the DTV we don't
-	     even try it and fail the load.
-
-	     XXX We could track the minimum DTV slots allocated in
-	     all threads.  */
-	  if (! RTLD_SINGLE_THREAD_P && imap->l_tls_modid > DTV_SURPLUS)
-	    _dl_signal_error (0, "dlopen", NULL, N_("\
-cannot load any more object with static TLS"));
-
+	     now, but we can delay updating the DTV.  */
 	  imap->l_need_tls_init = 0;
 #ifdef SHARED
 	  /* Update the slot information data for at least the
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index b72287d..0872636 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -137,12 +137,6 @@  _dl_nothread_init_static_tls (struct link_map *map)
 # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
 #endif
 
-  /* Fill in the DTV slot so that a later LD/GD access will find it.  */
-  dtv_t *dtv = THREAD_DTV ();
-  assert (map->l_tls_modid <= dtv[-1].counter);
-  dtv[map->l_tls_modid].pointer.val = dest;
-  dtv[map->l_tls_modid].pointer.is_static = true;
-
   /* Initialize the memory.  */
   memset (__mempcpy (dest, map->l_tls_initimage, map->l_tls_initimage_size),
 	  '\0', map->l_tls_blocksize - map->l_tls_initimage_size);
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index 9d36d96..20c7e33 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -493,17 +493,14 @@  _dl_allocate_tls_init (void *result)
 	  assert (listp->slotinfo[cnt].gen <= GL(dl_tls_generation));
 	  maxgen = MAX (maxgen, listp->slotinfo[cnt].gen);
 
+	  dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;
+	  dtv[map->l_tls_modid].pointer.is_static = false;
+
 	  if (map->l_tls_offset == NO_TLS_OFFSET
 	      || map->l_tls_offset == FORCED_DYNAMIC_TLS_OFFSET)
-	    {
-	      /* For dynamically loaded modules we simply store
-		 the value indicating deferred allocation.  */
-	      dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;
-	      dtv[map->l_tls_modid].pointer.is_static = false;
-	      continue;
-	    }
+	    continue;
 
-	  assert (map->l_tls_modid == cnt);
+	  assert (map->l_tls_modid == total + cnt);
 	  assert (map->l_tls_blocksize >= map->l_tls_initimage_size);
 #if TLS_TCB_AT_TP
 	  assert ((size_t) map->l_tls_offset >= map->l_tls_blocksize);
@@ -515,8 +512,6 @@  _dl_allocate_tls_init (void *result)
 #endif
 
 	  /* Copy the initialization image and clear the BSS part.  */
-	  dtv[map->l_tls_modid].pointer.val = dest;
-	  dtv[map->l_tls_modid].pointer.is_static = true;
 	  memset (__mempcpy (dest, map->l_tls_initimage,
 			     map->l_tls_initimage_size), '\0',
 		  map->l_tls_blocksize - map->l_tls_initimage_size);
@@ -679,13 +674,16 @@  _dl_update_slotinfo (unsigned long int req_modid)
 	      struct link_map *map = listp->slotinfo[cnt].map;
 	      if (map == NULL)
 		{
-		  /* If this modid was used at some point the memory
-		     might still be allocated.  */
-		  if (! dtv[total + cnt].pointer.is_static
-		      && dtv[total + cnt].pointer.val != TLS_DTV_UNALLOCATED)
+		  if (dtv[-1].counter >= total + cnt)
 		    {
-		      free (dtv[total + cnt].pointer.val);
+		      /* If this modid was used at some point the memory
+			 might still be allocated.  */
+		      if (! dtv[total + cnt].pointer.is_static
+			  && (dtv[total + cnt].pointer.val
+			      != TLS_DTV_UNALLOCATED))
+			free (dtv[total + cnt].pointer.val);
 		      dtv[total + cnt].pointer.val = TLS_DTV_UNALLOCATED;
+		      dtv[total + cnt].pointer.is_static = false;
 		    }
 
 		  continue;
@@ -718,10 +716,8 @@  _dl_update_slotinfo (unsigned long int req_modid)
 		   memalign and not malloc.  */
 		free (dtv[modid].pointer.val);
 
-	      /* This module is loaded dynamically- We defer memory
-		 allocation.  */
-	      dtv[modid].pointer.is_static = false;
 	      dtv[modid].pointer.val = TLS_DTV_UNALLOCATED;
+	      dtv[modid].pointer.is_static = false;
 
 	      if (modid == req_modid)
 		the_map = map;
@@ -759,13 +755,12 @@  tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
       the_map = listp->slotinfo[idx].map;
     }
 
- again:
   /* Make sure that, if a dlopen running in parallel forces the
      variable into static storage, we'll wait until the address in the
      static TLS block is set up, and use that.  If we're undecided
      yet, make sure we make the decision holding the lock as well.  */
-  if (__builtin_expect (the_map->l_tls_offset
-			!= FORCED_DYNAMIC_TLS_OFFSET, 0))
+  if (__glibc_unlikely (the_map->l_tls_offset
+			!= FORCED_DYNAMIC_TLS_OFFSET))
     {
       __rtld_lock_lock_recursive (GL(dl_load_lock));
       if (__glibc_likely (the_map->l_tls_offset == NO_TLS_OFFSET))
@@ -773,22 +768,28 @@  tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
 	  the_map->l_tls_offset = FORCED_DYNAMIC_TLS_OFFSET;
 	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
 	}
-      else
+      else if (__glibc_likely (the_map->l_tls_offset
+			       != FORCED_DYNAMIC_TLS_OFFSET))
 	{
+#if TLS_TCB_AT_TP
+	  void *p = (char *) THREAD_SELF - the_map->l_tls_offset;
+#elif TLS_DTV_AT_TP
+	  void *p = (char *) THREAD_SELF + the_map->l_tls_offset + TLS_PRE_TCB_SIZE;
+#else
+# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
+#endif
 	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
-	  if (__builtin_expect (the_map->l_tls_offset
-				!= FORCED_DYNAMIC_TLS_OFFSET, 1))
-	    {
-	      void *p = dtv[GET_ADDR_MODULE].pointer.val;
-	      if (__glibc_unlikely (p == TLS_DTV_UNALLOCATED))
-		goto again;
 
-	      return (char *) p + GET_ADDR_OFFSET;
-	    }
+	  dtv[GET_ADDR_MODULE].pointer.is_static = true;
+	  dtv[GET_ADDR_MODULE].pointer.val = p;
+
+	  return (char *) p + GET_ADDR_OFFSET;
 	}
+      else
+	__rtld_lock_unlock_recursive (GL(dl_load_lock));
     }
   void *p = dtv[GET_ADDR_MODULE].pointer.val = allocate_and_init (the_map);
-  dtv[GET_ADDR_MODULE].pointer.is_static = false;
+  assert (!dtv[GET_ADDR_MODULE].pointer.is_static);
 
   return (char *) p + GET_ADDR_OFFSET;
 }
diff --git a/elf/tlsdeschtab.h b/elf/tlsdeschtab.h
index d7e7955..d13b4e5 100644
--- a/elf/tlsdeschtab.h
+++ b/elf/tlsdeschtab.h
@@ -42,7 +42,7 @@  eq_tlsdesc (void *p, void *q)
   return tdp->tlsinfo.ti_offset == tdq->tlsinfo.ti_offset;
 }
 
-inline static int
+inline static size_t
 map_generation (struct link_map *map)
 {
   size_t idx = map->l_tls_modid;
@@ -58,7 +58,7 @@  map_generation (struct link_map *map)
 	     we can assume that, if the generation count is zero, we
 	     still haven't determined the generation count for this
 	     module.  */
-	  if (listp->slotinfo[idx].gen)
+	  if (listp->slotinfo[idx].map == map && listp->slotinfo[idx].gen)
 	    return listp->slotinfo[idx].gen;
 	  else
 	    break;
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index 3c8e04699..8e620c46 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -1190,7 +1190,6 @@  __nptl_setxid (struct xid_command *cmdp)
 static inline void __attribute__((always_inline))
 init_one_static_tls (struct pthread *curp, struct link_map *map)
 {
-  dtv_t *dtv = GET_DTV (TLS_TPADJ (curp));
 # if TLS_TCB_AT_TP
   void *dest = (char *) curp - map->l_tls_offset;
 # elif TLS_DTV_AT_TP
@@ -1199,11 +1198,9 @@  init_one_static_tls (struct pthread *curp, struct link_map *map)
 #  error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
 # endif
 
-  /* Fill in the DTV slot so that a later LD/GD access will find it.  */
-  dtv[map->l_tls_modid].pointer.val = dest;
-  dtv[map->l_tls_modid].pointer.is_static = true;
-
-  /* Initialize the memory.  */
+  /* We cannot delay the initialization of the Static TLS area, since
+     it can be accessed with LE or IE, but since the DTV is only used
+     by GD and LD, we can delay its update to avoid a race.  */
   memset (__mempcpy (dest, map->l_tls_initimage, map->l_tls_initimage_size),
 	  '\0', map->l_tls_blocksize - map->l_tls_initimage_size);
 }
diff --git a/nptl_db/db-symbols.awk b/nptl_db/db-symbols.awk
index f9a91b9..eb089e1 100644
--- a/nptl_db/db-symbols.awk
+++ b/nptl_db/db-symbols.awk
@@ -2,6 +2,8 @@ 
 # we've just built.  It checks for all the symbols used in td_symbol_list.
 
 BEGIN {
+%define DB_RTLD_VARIABLE(name) /* Nothing. */
+%define DB_MAIN_VARIABLE(name) /* Nothing. */
 %define DB_LOOKUP_NAME(idx, name)		required[STRINGIFY (name)] = 1;
 %define DB_LOOKUP_NAME_TH_UNIQUE(idx, name)	th_unique[STRINGIFY (name)] = 1;
 %include "db-symbols.h"
diff --git a/nptl_db/db_info.c b/nptl_db/db_info.c
index d4a5438..b88b4c0 100644
--- a/nptl_db/db_info.c
+++ b/nptl_db/db_info.c
@@ -21,6 +21,7 @@ 
 #include <stdint.h>
 #include "thread_dbP.h"
 #include <tls.h>
+#include <ldsodefs.h>
 
 typedef struct pthread pthread;
 typedef struct pthread_key_struct pthread_key_struct;
@@ -37,6 +38,9 @@  typedef struct
 } dtv;
 
 typedef struct link_map link_map;
+typedef struct rtld_global rtld_global;
+typedef struct dtv_slotinfo_list dtv_slotinfo_list;
+typedef struct dtv_slotinfo dtv_slotinfo;
 
 /* Actually static in nptl/init.c, but we only need it for typeof.  */
 extern bool __nptl_initial_report_events;
diff --git a/nptl_db/fetch-value.c b/nptl_db/fetch-value.c
index afc26fc..8a5a30c 100644
--- a/nptl_db/fetch-value.c
+++ b/nptl_db/fetch-value.c
@@ -69,7 +69,8 @@  _td_locate_field (td_thragent_t *ta,
 	}
     }
 
-  if (idx != 0 && idx - (psaddr_t) 0 > DB_DESC_NELEM (desc))
+  if (idx != 0 && DB_DESC_NELEM (desc) != 0
+      && idx - (psaddr_t) 0 > DB_DESC_NELEM (desc))
     /* This is an internal indicator to callers with nonzero IDX
        that the IDX value is too big.  */
     return TD_NOAPLIC;
diff --git a/nptl_db/structs.def b/nptl_db/structs.def
index 42e8b4d..0d49a0a 100644
--- a/nptl_db/structs.def
+++ b/nptl_db/structs.def
@@ -22,6 +22,28 @@ 
 # define STRUCTS_DEF_DEFAULTS 1
 #endif
 
+#ifndef DB_RTLD_VARIABLE
+# define DB_RTLD_VARIABLE(name) DB_VARIABLE (name)
+#endif
+
+#ifndef DB_MAIN_VARIABLE
+# define DB_MAIN_VARIABLE(name) DB_VARIABLE (name)
+#endif
+
+#ifndef DB_RTLD_GLOBAL_FIELD
+# if !IS_IN (libpthread)
+#  define DB_RTLD_GLOBAL_FIELD(field)		\
+  DB_STRUCT_FIELD (rtld_global, _##field)	\
+  DB_MAIN_VARIABLE (_##field)
+# elif defined SHARED
+#  define DB_RTLD_GLOBAL_FIELD(field)		\
+  DB_STRUCT_FIELD (rtld_global, _##field)
+# else
+#  define DB_RTLD_GLOBAL_FIELD(field)		\
+  DB_MAIN_VARIABLE (_##field)
+# endif
+#endif /* DB_RTLD_GLOBAL_FIELD */
+
 DB_STRUCT (pthread)
 DB_STRUCT_FIELD (pthread, list)
 DB_STRUCT_FIELD (pthread, report_events)
@@ -70,14 +92,31 @@  DB_STRUCT (pthread_key_data_level2)
 DB_STRUCT_ARRAY_FIELD (pthread_key_data_level2, data)
 
 DB_STRUCT_FIELD (link_map, l_tls_modid)
+DB_STRUCT_FIELD (link_map, l_tls_offset)
 
 DB_STRUCT_ARRAY_FIELD (dtv, dtv)
 #define pointer_val pointer.val /* Field of anonymous struct in dtv_t.  */
 DB_STRUCT_FIELD (dtv_t, pointer_val)
+DB_STRUCT_FIELD (dtv_t, counter)
 #if !IS_IN (libpthread) || TLS_TCB_AT_TP
 DB_STRUCT_FIELD (pthread, dtvp)
 #endif
 
+#if !(IS_IN (libpthread) && !defined SHARED)
+DB_STRUCT (rtld_global)
+DB_RTLD_VARIABLE (_rtld_global)
+#endif
+DB_RTLD_GLOBAL_FIELD (dl_tls_dtv_slotinfo_list)
+
+DB_STRUCT (dtv_slotinfo_list)
+DB_STRUCT_FIELD (dtv_slotinfo_list, len)
+DB_STRUCT_FIELD (dtv_slotinfo_list, next)
+DB_STRUCT_ARRAY_FIELD (dtv_slotinfo_list, slotinfo)
+
+DB_STRUCT (dtv_slotinfo)
+DB_STRUCT_FIELD (dtv_slotinfo, gen)
+DB_STRUCT_FIELD (dtv_slotinfo, map)
+
 #ifdef STRUCTS_DEF_DEFAULTS
 # undef DB_STRUCT_ARRAY_FIELD
 # undef DB_ARRAY_VARIABLE
diff --git a/nptl_db/td_symbol_list.c b/nptl_db/td_symbol_list.c
index 6915ed7..b6c459f 100644
--- a/nptl_db/td_symbol_list.c
+++ b/nptl_db/td_symbol_list.c
@@ -18,7 +18,6 @@ 
    <http://www.gnu.org/licenses/>.  */
 
 #include <assert.h>
-#include <gnu/lib-names.h>
 #include "thread_dbP.h"
 
 static const char *symbol_list_arr[] =
@@ -41,12 +40,12 @@  td_symbol_list (void)
 
 
 ps_err_e
-td_lookup (struct ps_prochandle *ps, int idx, psaddr_t *sym_addr)
+td_mod_lookup (struct ps_prochandle *ps, const char *mod,
+	       int idx, psaddr_t *sym_addr)
 {
   ps_err_e result;
   assert (idx >= 0 && idx < SYM_NUM_MESSAGES);
-  result = ps_pglobal_lookup (ps, LIBPTHREAD_SO, symbol_list_arr[idx],
-			      sym_addr);
+  result = ps_pglobal_lookup (ps, mod, symbol_list_arr[idx], sym_addr);
 
   return result;
 }
diff --git a/nptl_db/td_thr_tlsbase.c b/nptl_db/td_thr_tlsbase.c
index 7092e31..24a489a 100644
--- a/nptl_db/td_thr_tlsbase.c
+++ b/nptl_db/td_thr_tlsbase.c
@@ -17,14 +17,118 @@ 
    <http://www.gnu.org/licenses/>.  */
 
 #include "thread_dbP.h"
+#include <link.h>
 
+/* Get the DTV slotinfo list head entry from the dynamic loader state
+   into *LISTHEAD.  */
+static td_err_e
+dtv_slotinfo_list (td_thragent_t *ta,
+		   psaddr_t *listhead)
+{
+  td_err_e err;
+  psaddr_t head;
+
+  if (ta->ta_addr__rtld_global == 0
+      && td_mod_lookup (ta->ph, LD_SO, SYM__rtld_global,
+			&ta->ta_addr__rtld_global) != PS_OK)
+    ta->ta_addr__rtld_global = (void*)-1;
+
+  if (ta->ta_addr__rtld_global != (void*)-1)
+    {
+      err = DB_GET_FIELD (head, ta, ta->ta_addr__rtld_global,
+			  rtld_global, _dl_tls_dtv_slotinfo_list, 0);
+      if (err != TD_OK)
+	return err;
+    }
+  else
+    {
+      if (ta->ta_addr__dl_tls_dtv_slotinfo_list == 0
+	  && td_mod_lookup (ta->ph, NULL, SYM__dl_tls_dtv_slotinfo_list,
+			    &ta->ta_addr__dl_tls_dtv_slotinfo_list) != PS_OK)
+	return TD_ERR;
+
+      err = _td_fetch_value (ta, ta->ta_var__dl_tls_dtv_slotinfo_list,
+			     SYM_DESC__dl_tls_dtv_slotinfo_list,
+			     0, ta->ta_addr__dl_tls_dtv_slotinfo_list, &head);
+      if (err != TD_OK)
+	return err;
+    }
+
+  *listhead = head;
+  return TD_OK;
+}
+
+/* Get the address of the DTV slotinfo entry for MODID into
+   *DTVSLOTINFO.  */
+static td_err_e
+dtv_slotinfo (td_thragent_t *ta,
+	      unsigned long int modid,
+	      psaddr_t *dtvslotinfo)
+{
+  td_err_e err;
+  psaddr_t slot, temp;
+  size_t slbase = 0;
+
+  err = dtv_slotinfo_list (ta, &slot);
+  if (err != TD_OK)
+    return err;
+
+  while (slot)
+    {
+      /* Get the number of entries in this list entry's array.  */
+      err = DB_GET_FIELD (temp, ta, slot, dtv_slotinfo_list, len, 0);
+      if (err != TD_OK)
+	return err;
+      size_t len = (uintptr_t)temp;
+
+      /* Did we find the list entry for modid?  */
+      if (modid < slbase + len)
+	break;
+
+      /* We didn't, so get the next list entry.  */
+      slbase += len;
+      err = DB_GET_FIELD (temp, ta, slot, dtv_slotinfo_list,
+			  next, 0);
+      if (err != TD_OK)
+	return err;
+      slot = temp;
+    }
+
+  /* We reached the end of the list and found nothing.  */
+  if (!slot)
+    return TD_ERR;
+
+  /* Take the slotinfo for modid from the list entry.  */
+  err = DB_GET_FIELD_ADDRESS (temp, ta, slot, dtv_slotinfo_list,
+			      slotinfo, modid - slbase);
+  if (err != TD_OK)
+    return err;
+  slot = temp;
+
+  *dtvslotinfo = slot;
+  return TD_OK;
+}
+
+/* Return in *BASE the base address of the TLS block for MODID within
+   TH.
+
+   It should return success and yield the correct pointer in any
+   circumstance where the TLS block for the module and thread
+   requested has already been initialized.
+
+   It should fail with TD_TLSDEFER only when the thread could not
+   possibly have observed any values in that TLS block.  That way, the
+   debugger can fall back to showing initial values from the PT_TLS
+   segment (and refusing attempts to mutate) for the TD_TLSDEFER case,
+   and never fail to make the values the program will actually see
+   available to the user of the debugger.  */
 td_err_e
 td_thr_tlsbase (const td_thrhandle_t *th,
 		unsigned long int modid,
 		psaddr_t *base)
 {
   td_err_e err;
-  psaddr_t dtv, dtvslot, dtvptr;
+  psaddr_t dtv, dtvslot, dtvptr, temp;
 
   if (modid < 1)
     return TD_NOTLS;
@@ -50,11 +154,75 @@  td_thr_tlsbase (const td_thrhandle_t *th,
 	return TD_TLSDEFER;
     }
 
+  err = dtv_slotinfo (th->th_ta_p, modid, &temp);
+  if (err != TD_OK)
+    return err;
+
+  psaddr_t slot;
+  err = DB_GET_STRUCT (slot, th->th_ta_p, temp, dtv_slotinfo);
+  if (err != TD_OK)
+    return err;
+
+  /* Take the link_map from the slotinfo.  */
+  psaddr_t map;
+  err = DB_GET_FIELD_LOCAL (map, th->th_ta_p, slot, dtv_slotinfo, map, 0);
+  if (err != TD_OK)
+    return err;
+  if (!map)
+    return TD_ERR;
+
+  /* Ok, the modid is good, now find out what DTV generation it
+     requires.  */
+  err = DB_GET_FIELD_LOCAL (temp, th->th_ta_p, slot, dtv_slotinfo, gen, 0);
+  if (err != TD_OK)
+    return err;
+  size_t modgen = (uintptr_t)temp;
+
   /* Get the DTV pointer from the thread descriptor.  */
   err = DB_GET_FIELD (dtv, th->th_ta_p, pd, pthread, dtvp, 0);
   if (err != TD_OK)
     return err;
 
+  psaddr_t dtvgenloc;
+  /* Get the DTV generation count at dtv[0].counter.  */
+  err = DB_GET_FIELD_ADDRESS (dtvgenloc, th->th_ta_p, dtv, dtv, dtv, 0);
+  if (err != TD_OK)
+    return err;
+  err = DB_GET_FIELD (temp, th->th_ta_p, dtvgenloc, dtv_t, counter, 0);
+  if (err != TD_OK)
+    return err;
+  size_t dtvgen = (uintptr_t)temp;
+
+  /* Is the DTV current enough?  */
+  if (dtvgen < modgen)
+    {
+    try_static_tls:
+      /* If the module uses Static TLS, we're still good.  */
+      err = DB_GET_FIELD (temp, th->th_ta_p, map, link_map, l_tls_offset, 0);
+      if (err != TD_OK)
+	return err;
+      ptrdiff_t tlsoff = (uintptr_t)temp;
+
+      if (tlsoff != FORCED_DYNAMIC_TLS_OFFSET
+	  && tlsoff != NO_TLS_OFFSET)
+	{
+	  psaddr_t tp = pd;
+
+#if TLS_TCB_AT_TP
+	  dtvptr = tp - tlsoff;
+#elif TLS_DTV_AT_TP
+	  dtvptr = tp + tlsoff + TLS_PRE_TCB_SIZE;
+#else
+# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
+#endif
+
+	  *base = dtvptr;
+	  return TD_OK;
+	}
+
+      return TD_TLSDEFER;
+    }
+
   /* Find the corresponding entry in the DTV.  */
   err = DB_GET_FIELD_ADDRESS (dtvslot, th->th_ta_p, dtv, dtv, dtv, modid);
   if (err != TD_OK)
@@ -68,7 +236,7 @@  td_thr_tlsbase (const td_thrhandle_t *th,
   /* It could be that the memory for this module is not allocated for
      the given thread.  */
   if ((uintptr_t) dtvptr & 1)
-    return TD_TLSDEFER;
+    goto try_static_tls;
 
   *base = dtvptr;
   return TD_OK;
diff --git a/nptl_db/thread_dbP.h b/nptl_db/thread_dbP.h
index 4b59ce6..445c797 100644
--- a/nptl_db/thread_dbP.h
+++ b/nptl_db/thread_dbP.h
@@ -29,6 +29,7 @@ 
 #include "thread_db.h"
 #include "../nptl/pthreadP.h"  	/* This is for *_BITMASK only.  */
 #include <list.h>
+#include <gnu/lib-names.h>
 
 /* Indeces for the symbol names.  */
 enum
@@ -139,11 +140,11 @@  ta_ok (const td_thragent_t *ta)
 }
 
 
-/* Internal wrapper around ps_pglobal_lookup.  */
-extern ps_err_e td_lookup (struct ps_prochandle *ps,
-			   int idx, psaddr_t *sym_addr) attribute_hidden;
-
-
+/* Internal wrappers around ps_pglobal_lookup.  */
+extern ps_err_e td_mod_lookup (struct ps_prochandle *ps, const char *modname,
+			       int idx, psaddr_t *sym_addr) attribute_hidden;
+#define td_lookup(ps, idx, sym_addr) \
+  td_mod_lookup ((ps), LIBPTHREAD_SO, (idx), (sym_addr))
 
 
 /* Store in psaddr_t VAR the address of inferior's symbol NAME.  */