5f7b84
commit a509eb117fac1d764b15eba64993f4bdb63d7f3c
5f7b84
Author: Florian Weimer <fweimer@redhat.com>
5f7b84
Date:   Wed Nov 27 16:37:17 2019 +0100
5f7b84
5f7b84
    Avoid late dlopen failure due to scope, TLS slotinfo updates [BZ #25112]
5f7b84
    
5f7b84
    This change splits the scope and TLS slotinfo updates in dlopen into
5f7b84
    two parts: one to resize the data structures, and one to actually apply
5f7b84
    the update.  The call to add_to_global_resize in dl_open_worker is moved
5f7b84
    before the demarcation point at which no further memory allocations are
5f7b84
    allowed.
5f7b84
    
5f7b84
    _dl_add_to_slotinfo is adjusted to make the list update optional.  There
5f7b84
    is some optimization possibility here because we could grow the slotinfo
5f7b84
    list of arrays in a single call, one the largest TLS modid is known.
5f7b84
    
5f7b84
    This commit does not fix the fatal meory allocation failure in
5f7b84
    _dl_update_slotinfo.  Ideally, this error during dlopen should be
5f7b84
    recoverable.
5f7b84
    
5f7b84
    The update order of scopes and TLS data structures is retained, although
5f7b84
    it appears to be more correct to fully initialize TLS first, and then
5f7b84
    expose symbols in the newly loaded objects via the scope update.
5f7b84
    
5f7b84
    Tested on x86_64-linux-gnu.
5f7b84
    
5f7b84
    Change-Id: I240c58387dabda3ca1bcab48b02115175fa83d6c
5f7b84
5f7b84
diff --git a/elf/dl-open.c b/elf/dl-open.c
5f7b84
index 85db4f0ecb5f29ce..b330cff7d349224a 100644
5f7b84
--- a/elf/dl-open.c
5f7b84
+++ b/elf/dl-open.c
5f7b84
@@ -33,6 +33,7 @@
5f7b84
 #include <stap-probe.h>
5f7b84
 #include <atomic.h>
5f7b84
 #include <libc-internal.h>
5f7b84
+#include <array_length.h>
5f7b84
 
5f7b84
 #include <dl-dst.h>
5f7b84
 #include <dl-prop.h>
5f7b84
@@ -214,6 +215,215 @@ _dl_find_dso_for_object (const ElfW(Addr) addr)
5f7b84
 }
5f7b84
 rtld_hidden_def (_dl_find_dso_for_object);
5f7b84
 
5f7b84
+/* Return true if NEW is found in the scope for MAP.  */
5f7b84
+static size_t
5f7b84
+scope_has_map (struct link_map *map, struct link_map *new)
5f7b84
+{
5f7b84
+  size_t cnt;
5f7b84
+  for (cnt = 0; map->l_scope[cnt] != NULL; ++cnt)
5f7b84
+    if (map->l_scope[cnt] == &new->l_searchlist)
5f7b84
+      return true;
5f7b84
+  return false;
5f7b84
+}
5f7b84
+
5f7b84
+/* Return the length of the scope for MAP.  */
5f7b84
+static size_t
5f7b84
+scope_size (struct link_map *map)
5f7b84
+{
5f7b84
+  size_t cnt;
5f7b84
+  for (cnt = 0; map->l_scope[cnt] != NULL; )
5f7b84
+    ++cnt;
5f7b84
+  return cnt;
5f7b84
+}
5f7b84
+
5f7b84
+/* Resize the scopes of depended-upon objects, so that the new object
5f7b84
+   can be added later without further allocation of memory.  This
5f7b84
+   function can raise an exceptions due to malloc failure.  */
5f7b84
+static void
5f7b84
+resize_scopes (struct link_map *new)
5f7b84
+{
5f7b84
+  /* If the file is not loaded now as a dependency, add the search
5f7b84
+     list of the newly loaded object to the scope.  */
5f7b84
+  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
5f7b84
+    {
5f7b84
+      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
+
5f7b84
+      /* If the initializer has been called already, the object has
5f7b84
+	 not been loaded here and now.  */
5f7b84
+      if (imap->l_init_called && imap->l_type == lt_loaded)
5f7b84
+	{
5f7b84
+	  if (scope_has_map (imap, new))
5f7b84
+	    /* Avoid duplicates.  */
5f7b84
+	    continue;
5f7b84
+
5f7b84
+	  size_t cnt = scope_size (imap);
5f7b84
+	  if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
5f7b84
+	    {
5f7b84
+	      /* The l_scope array is too small.  Allocate a new one
5f7b84
+		 dynamically.  */
5f7b84
+	      size_t new_size;
5f7b84
+	      struct r_scope_elem **newp;
5f7b84
+
5f7b84
+	      if (imap->l_scope != imap->l_scope_mem
5f7b84
+		  && imap->l_scope_max < array_length (imap->l_scope_mem))
5f7b84
+		{
5f7b84
+		  /* If the current l_scope memory is not pointing to
5f7b84
+		     the static memory in the structure, but the
5f7b84
+		     static memory in the structure is large enough to
5f7b84
+		     use for cnt + 1 scope entries, then switch to
5f7b84
+		     using the static memory.  */
5f7b84
+		  new_size = array_length (imap->l_scope_mem);
5f7b84
+		  newp = imap->l_scope_mem;
5f7b84
+		}
5f7b84
+	      else
5f7b84
+		{
5f7b84
+		  new_size = imap->l_scope_max * 2;
5f7b84
+		  newp = (struct r_scope_elem **)
5f7b84
+		    malloc (new_size * sizeof (struct r_scope_elem *));
5f7b84
+		  if (newp == NULL)
5f7b84
+		    _dl_signal_error (ENOMEM, "dlopen", NULL,
5f7b84
+				      N_("cannot create scope list"));
5f7b84
+		}
5f7b84
+
5f7b84
+	      /* Copy the array and the terminating NULL.  */
5f7b84
+	      memcpy (newp, imap->l_scope,
5f7b84
+		      (cnt + 1) * sizeof (imap->l_scope[0]));
5f7b84
+	      struct r_scope_elem **old = imap->l_scope;
5f7b84
+
5f7b84
+	      imap->l_scope = newp;
5f7b84
+
5f7b84
+	      if (old != imap->l_scope_mem)
5f7b84
+		_dl_scope_free (old);
5f7b84
+
5f7b84
+	      imap->l_scope_max = new_size;
5f7b84
+	    }
5f7b84
+	}
5f7b84
+    }
5f7b84
+}
5f7b84
+
5f7b84
+/* Second stage of resize_scopes: Add NEW to the scopes.  Also print
5f7b84
+   debugging information about scopes if requested.
5f7b84
+
5f7b84
+   This function cannot raise an exception because all required memory
5f7b84
+   has been allocated by a previous call to resize_scopes.  */
5f7b84
+static void
5f7b84
+update_scopes (struct link_map *new)
5f7b84
+{
5f7b84
+  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
5f7b84
+    {
5f7b84
+      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
+      int from_scope = 0;
5f7b84
+
5f7b84
+      if (imap->l_init_called && imap->l_type == lt_loaded)
5f7b84
+	{
5f7b84
+	  if (scope_has_map (imap, new))
5f7b84
+	    /* Avoid duplicates.  */
5f7b84
+	    continue;
5f7b84
+
5f7b84
+	  size_t cnt = scope_size (imap);
5f7b84
+	  /* Assert that resize_scopes has sufficiently enlarged the
5f7b84
+	     array.  */
5f7b84
+	  assert (cnt + 1 < imap->l_scope_max);
5f7b84
+
5f7b84
+	  /* First terminate the extended list.  Otherwise a thread
5f7b84
+	     might use the new last element and then use the garbage
5f7b84
+	     at offset IDX+1.  */
5f7b84
+	  imap->l_scope[cnt + 1] = NULL;
5f7b84
+	  atomic_write_barrier ();
5f7b84
+	  imap->l_scope[cnt] = &new->l_searchlist;
5f7b84
+
5f7b84
+	  from_scope = cnt;
5f7b84
+	}
5f7b84
+
5f7b84
+      /* Print scope information.  */
5f7b84
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
5f7b84
+	_dl_show_scope (imap, from_scope);
5f7b84
+    }
5f7b84
+}
5f7b84
+
5f7b84
+/* Call _dl_add_to_slotinfo with DO_ADD set to false, to allocate
5f7b84
+   space in GL (dl_tls_dtv_slotinfo_list).  This can raise an
5f7b84
+   exception.  The return value is true if any of the new objects use
5f7b84
+   TLS.  */
5f7b84
+static bool
5f7b84
+resize_tls_slotinfo (struct link_map *new)
5f7b84
+{
5f7b84
+  bool any_tls = false;
5f7b84
+  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
5f7b84
+    {
5f7b84
+      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
+
5f7b84
+      /* Only add TLS memory if this object is loaded now and
5f7b84
+	 therefore is not yet initialized.  */
5f7b84
+      if (! imap->l_init_called && imap->l_tls_blocksize > 0)
5f7b84
+	{
5f7b84
+	  _dl_add_to_slotinfo (imap, false);
5f7b84
+	  any_tls = true;
5f7b84
+	}
5f7b84
+    }
5f7b84
+  return any_tls;
5f7b84
+}
5f7b84
+
5f7b84
+/* Second stage of TLS update, after resize_tls_slotinfo.  This
5f7b84
+   function does not raise any exception.  It should only be called if
5f7b84
+   resize_tls_slotinfo returned true.  */
5f7b84
+static void
5f7b84
+update_tls_slotinfo (struct link_map *new)
5f7b84
+{
5f7b84
+  unsigned int first_static_tls = new->l_searchlist.r_nlist;
5f7b84
+  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
5f7b84
+    {
5f7b84
+      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
+
5f7b84
+      /* Only add TLS memory if this object is loaded now and
5f7b84
+	 therefore is not yet initialized.  */
5f7b84
+      if (! imap->l_init_called && imap->l_tls_blocksize > 0)
5f7b84
+	{
5f7b84
+	  _dl_add_to_slotinfo (imap, true);
5f7b84
+
5f7b84
+	  if (imap->l_need_tls_init
5f7b84
+	      && first_static_tls == new->l_searchlist.r_nlist)
5f7b84
+	    first_static_tls = i;
5f7b84
+	}
5f7b84
+    }
5f7b84
+
5f7b84
+  if (__builtin_expect (++GL(dl_tls_generation) == 0, 0))
5f7b84
+    _dl_fatal_printf (N_("\
5f7b84
+TLS generation counter wrapped!  Please report this."));
5f7b84
+
5f7b84
+  /* We need a second pass for static tls data, because
5f7b84
+     _dl_update_slotinfo must not be run while calls to
5f7b84
+     _dl_add_to_slotinfo are still pending.  */
5f7b84
+  for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
5f7b84
+    {
5f7b84
+      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
+
5f7b84
+      if (imap->l_need_tls_init
5f7b84
+	  && ! imap->l_init_called
5f7b84
+	  && imap->l_tls_blocksize > 0)
5f7b84
+	{
5f7b84
+	  /* For static TLS we have to allocate the memory here and
5f7b84
+	     now, but we can delay updating the DTV.  */
5f7b84
+	  imap->l_need_tls_init = 0;
5f7b84
+#ifdef SHARED
5f7b84
+	  /* Update the slot information data for at least the
5f7b84
+	     generation of the DSO we are allocating data for.  */
5f7b84
+
5f7b84
+	  /* FIXME: This can terminate the process on memory
5f7b84
+	     allocation failure.  It is not possible to raise
5f7b84
+	     exceptions from this context; to fix this bug,
5f7b84
+	     _dl_update_slotinfo would have to be split into two
5f7b84
+	     operations, similar to resize_scopes and update_scopes
5f7b84
+	     above.  This is related to bug 16134.  */
5f7b84
+	  _dl_update_slotinfo (imap->l_tls_modid);
5f7b84
+#endif
5f7b84
+
5f7b84
+	  GL(dl_init_static_tls) (imap);
5f7b84
+	  assert (imap->l_need_tls_init == 0);
5f7b84
+	}
5f7b84
+    }
5f7b84
+}
5f7b84
+
5f7b84
 /* struct dl_init_args and call_dl_init are used to call _dl_init with
5f7b84
    exception handling disabled.  */
5f7b84
 struct dl_init_args
5f7b84
@@ -431,133 +641,40 @@ dl_open_worker (void *a)
5f7b84
      relocation.  */
5f7b84
   _dl_open_check (new);
5f7b84
 
5f7b84
-  /* If the file is not loaded now as a dependency, add the search
5f7b84
-     list of the newly loaded object to the scope.  */
5f7b84
-  bool any_tls = false;
5f7b84
-  unsigned int first_static_tls = new->l_searchlist.r_nlist;
5f7b84
-  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
5f7b84
-    {
5f7b84
-      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
-      int from_scope = 0;
5f7b84
+  /* This only performs the memory allocations.  The actual update of
5f7b84
+     the scopes happens below, after failure is impossible.  */
5f7b84
+  resize_scopes (new);
5f7b84
 
5f7b84
-      /* If the initializer has been called already, the object has
5f7b84
-	 not been loaded here and now.  */
5f7b84
-      if (imap->l_init_called && imap->l_type == lt_loaded)
5f7b84
-	{
5f7b84
-	  struct r_scope_elem **runp = imap->l_scope;
5f7b84
-	  size_t cnt = 0;
5f7b84
-
5f7b84
-	  while (*runp != NULL)
5f7b84
-	    {
5f7b84
-	      if (*runp == &new->l_searchlist)
5f7b84
-		break;
5f7b84
-	      ++cnt;
5f7b84
-	      ++runp;
5f7b84
-	    }
5f7b84
-
5f7b84
-	  if (*runp != NULL)
5f7b84
-	    /* Avoid duplicates.  */
5f7b84
-	    continue;
5f7b84
-
5f7b84
-	  if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
5f7b84
-	    {
5f7b84
-	      /* The 'r_scope' array is too small.  Allocate a new one
5f7b84
-		 dynamically.  */
5f7b84
-	      size_t new_size;
5f7b84
-	      struct r_scope_elem **newp;
5f7b84
-
5f7b84
-#define SCOPE_ELEMS(imap) \
5f7b84
-  (sizeof (imap->l_scope_mem) / sizeof (imap->l_scope_mem[0]))
5f7b84
+  /* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
5f7b84
+     structure.  */
5f7b84
+  bool any_tls = resize_tls_slotinfo (new);
5f7b84
 
5f7b84
-	      if (imap->l_scope != imap->l_scope_mem
5f7b84
-		  && imap->l_scope_max < SCOPE_ELEMS (imap))
5f7b84
-		{
5f7b84
-		  new_size = SCOPE_ELEMS (imap);
5f7b84
-		  newp = imap->l_scope_mem;
5f7b84
-		}
5f7b84
-	      else
5f7b84
-		{
5f7b84
-		  new_size = imap->l_scope_max * 2;
5f7b84
-		  newp = (struct r_scope_elem **)
5f7b84
-		    malloc (new_size * sizeof (struct r_scope_elem *));
5f7b84
-		  if (newp == NULL)
5f7b84
-		    _dl_signal_error (ENOMEM, "dlopen", NULL,
5f7b84
-				      N_("cannot create scope list"));
5f7b84
-		}
5f7b84
-
5f7b84
-	      memcpy (newp, imap->l_scope, cnt * sizeof (imap->l_scope[0]));
5f7b84
-	      struct r_scope_elem **old = imap->l_scope;
5f7b84
-
5f7b84
-	      imap->l_scope = newp;
5f7b84
-
5f7b84
-	      if (old != imap->l_scope_mem)
5f7b84
-		_dl_scope_free (old);
5f7b84
-
5f7b84
-	      imap->l_scope_max = new_size;
5f7b84
-	    }
5f7b84
-
5f7b84
-	  /* First terminate the extended list.  Otherwise a thread
5f7b84
-	     might use the new last element and then use the garbage
5f7b84
-	     at offset IDX+1.  */
5f7b84
-	  imap->l_scope[cnt + 1] = NULL;
5f7b84
-	  atomic_write_barrier ();
5f7b84
-	  imap->l_scope[cnt] = &new->l_searchlist;
5f7b84
-
5f7b84
-	  /* Print only new scope information.  */
5f7b84
-	  from_scope = cnt;
5f7b84
-	}
5f7b84
-      /* Only add TLS memory if this object is loaded now and
5f7b84
-	 therefore is not yet initialized.  */
5f7b84
-      else if (! imap->l_init_called
5f7b84
-	       /* Only if the module defines thread local data.  */
5f7b84
-	       && __builtin_expect (imap->l_tls_blocksize > 0, 0))
5f7b84
-	{
5f7b84
-	  /* Now that we know the object is loaded successfully add
5f7b84
-	     modules containing TLS data to the slot info table.  We
5f7b84
-	     might have to increase its size.  */
5f7b84
-	  _dl_add_to_slotinfo (imap);
5f7b84
-
5f7b84
-	  if (imap->l_need_tls_init
5f7b84
-	      && first_static_tls == new->l_searchlist.r_nlist)
5f7b84
-	    first_static_tls = i;
5f7b84
-
5f7b84
-	  /* We have to bump the generation counter.  */
5f7b84
-	  any_tls = true;
5f7b84
-	}
5f7b84
-
5f7b84
-      /* Print scope information.  */
5f7b84
-      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
5f7b84
-	_dl_show_scope (imap, from_scope);
5f7b84
-    }
5f7b84
-
5f7b84
-  /* Bump the generation number if necessary.  */
5f7b84
-  if (any_tls && __builtin_expect (++GL(dl_tls_generation) == 0, 0))
5f7b84
-    _dl_fatal_printf (N_("\
5f7b84
-TLS generation counter wrapped!  Please report this."));
5f7b84
-
5f7b84
-  /* We need a second pass for static tls data, because _dl_update_slotinfo
5f7b84
-     must not be run while calls to _dl_add_to_slotinfo are still pending.  */
5f7b84
-  for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
5f7b84
-    {
5f7b84
-      struct link_map *imap = new->l_searchlist.r_list[i];
5f7b84
-
5f7b84
-      if (imap->l_need_tls_init
5f7b84
-	  && ! imap->l_init_called
5f7b84
-	  && imap->l_tls_blocksize > 0)
5f7b84
-	{
5f7b84
-	  /* For static TLS we have to allocate the memory here and
5f7b84
-	     now, but we can delay updating the DTV.  */
5f7b84
-	  imap->l_need_tls_init = 0;
5f7b84
-#ifdef SHARED
5f7b84
-	  /* Update the slot information data for at least the
5f7b84
-	     generation of the DSO we are allocating data for.  */
5f7b84
-	  _dl_update_slotinfo (imap->l_tls_modid);
5f7b84
-#endif
5f7b84
+  /* Perform the necessary allocations for adding new global objects
5f7b84
+     to the global scope below.  */
5f7b84
+  if (mode & RTLD_GLOBAL)
5f7b84
+    add_to_global_resize (new);
5f7b84
 
5f7b84
-	  GL(dl_init_static_tls) (imap);
5f7b84
-	  assert (imap->l_need_tls_init == 0);
5f7b84
-	}
5f7b84
-    }
5f7b84
+  /* Demarcation point: After this, no recoverable errors are allowed.
5f7b84
+     All memory allocations for new objects must have happened
5f7b84
+     before.  */
5f7b84
+
5f7b84
+  /* Second stage after resize_scopes: Actually perform the scope
5f7b84
+     update.  After this, dlsym and lazy binding can bind to new
5f7b84
+     objects.  */
5f7b84
+  update_scopes (new);
5f7b84
+
5f7b84
+  /* FIXME: It is unclear whether the order here is correct.
5f7b84
+     Shouldn't new objects be made available for binding (and thus
5f7b84
+     execution) only after there TLS data has been set up fully?
5f7b84
+     Fixing bug 16134 will likely make this distinction less
5f7b84
+     important.  */
5f7b84
+
5f7b84
+  /* Second stage after resize_tls_slotinfo: Update the slotinfo data
5f7b84
+     structures.  */
5f7b84
+  if (any_tls)
5f7b84
+    /* FIXME: This calls _dl_update_slotinfo, which aborts the process
5f7b84
+       on memory allocation failure.  See bug 16134.  */
5f7b84
+    update_tls_slotinfo (new);
5f7b84
 
5f7b84
   /* Notify the debugger all new objects have been relocated.  */
5f7b84
   if (relocation_in_progress)
5f7b84
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
5f7b84
index c87caf13d6a97ba4..a2def280b7096960 100644
5f7b84
--- a/elf/dl-tls.c
5f7b84
+++ b/elf/dl-tls.c
5f7b84
@@ -883,7 +883,7 @@ _dl_tls_get_addr_soft (struct link_map *l)
5f7b84
 
5f7b84
 
5f7b84
 void
5f7b84
-_dl_add_to_slotinfo (struct link_map *l)
5f7b84
+_dl_add_to_slotinfo (struct link_map *l, bool do_add)
5f7b84
 {
5f7b84
   /* Now that we know the object is loaded successfully add
5f7b84
      modules containing TLS data to the dtv info table.  We
5f7b84
@@ -939,6 +939,9 @@ cannot create TLS data structures"));
5f7b84
     }
5f7b84
 
5f7b84
   /* Add the information into the slotinfo data structure.  */
5f7b84
-  listp->slotinfo[idx].map = l;
5f7b84
-  listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
5f7b84
+  if (do_add)
5f7b84
+    {
5f7b84
+      listp->slotinfo[idx].map = l;
5f7b84
+      listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
5f7b84
+    }
5f7b84
 }
5f7b84
diff --git a/elf/rtld.c b/elf/rtld.c
5f7b84
index 4ec26a79cbb0aa4f..0aa1a2a19f649e16 100644
5f7b84
--- a/elf/rtld.c
5f7b84
+++ b/elf/rtld.c
5f7b84
@@ -2167,7 +2167,7 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
5f7b84
 
5f7b84
 	  /* Add object to slot information data if necessasy.  */
5f7b84
 	  if (l->l_tls_blocksize != 0 && tls_init_tp_called)
5f7b84
-	    _dl_add_to_slotinfo (l);
5f7b84
+	    _dl_add_to_slotinfo (l, true);
5f7b84
 	}
5f7b84
     }
5f7b84
   else
5f7b84
@@ -2215,7 +2215,7 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
5f7b84
 
5f7b84
 	  /* Add object to slot information data if necessasy.  */
5f7b84
 	  if (l->l_tls_blocksize != 0 && tls_init_tp_called)
5f7b84
-	    _dl_add_to_slotinfo (l);
5f7b84
+	    _dl_add_to_slotinfo (l, true);
5f7b84
 	}
5f7b84
       HP_TIMING_NOW (stop);
5f7b84
 
5f7b84
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
5f7b84
index 57fbefea3cb841e9..c6b7e61badbfd513 100644
5f7b84
--- a/sysdeps/generic/ldsodefs.h
5f7b84
+++ b/sysdeps/generic/ldsodefs.h
5f7b84
@@ -1135,8 +1135,15 @@ extern void *_dl_open (const char *name, int mode, const void *caller,
5f7b84
    old scope, OLD can't be freed until no thread is using it.  */
5f7b84
 extern int _dl_scope_free (void *) attribute_hidden;
5f7b84
 
5f7b84
-/* Add module to slot information data.  */
5f7b84
-extern void _dl_add_to_slotinfo (struct link_map  *l) attribute_hidden;
5f7b84
+
5f7b84
+/* Add module to slot information data.  If DO_ADD is false, only the
5f7b84
+   required memory is allocated.  Must be called with GL
5f7b84
+   (dl_load_lock) acquired.  If the function has already been called
5f7b84
+   for the link map L with !do_add, then this function will not raise
5f7b84
+   an exception, otherwise it is possible that it encounters a memory
5f7b84
+   allocation failure.  */
5f7b84
+extern void _dl_add_to_slotinfo (struct link_map *l, bool do_add)
5f7b84
+  attribute_hidden;
5f7b84
 
5f7b84
 /* Update slot information data for at least the generation of the
5f7b84
    module with the given index.  */