diff mbox series

[v9,1/1] Extend struct r_debug to support multiple namespaces [BZ #15971]

Message ID 20210910201219.156682-2-hjl.tools@gmail.com
State New
Headers show
Series Extend struct r_debug to support multiple namespaces | expand

Commit Message

H.J. Lu Sept. 10, 2021, 8:12 p.m. UTC
Glibc does not provide an interface for debugger to access libraries
loaded in multiple namespaces via dlmopen.

The current rtld-debugger interface is described in the file:

elf/rtld-debugger-interface.txt

under the "Standard debugger interface" heading.  This interface only
provides access to the first link-map (LM_ID_BASE).

1. Bump r_version to 2 when multiple namespaces are used.  This triggers
the GDB bug:

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

2. Add struct r_debug_extended to extend struct r_debug into a linked-list,
where each element correlates to an unique namespace.
3. Initialize the r_debug_extended structure.  Bump r_version to 2 for
the new namespace and add the new namespace to the namespace linked list.
4. Add _dl_debug_update to return the address of struct r_debug' of a
namespace.
5. Add a hidden symbol, _r_debug_extended, for struct r_debug_extended.
6. Provide the compatibility symbol, _r_debug, with size of struct r_debug,
as an alise of _r_debug_extended, for programs which reference _r_debug.

This fixes BZ #15971.
---
 NEWS                            | 11 ++++-
 csu/Makefile                    |  3 ++
 csu/rtld-sizes.sym              |  6 +++
 elf/Makefile                    |  7 +++-
 elf/dl-close.c                  |  2 +-
 elf/dl-debug-symbols.S          | 37 +++++++++++++++++
 elf/dl-debug.c                  | 72 ++++++++++++++++++++++++---------
 elf/dl-load.c                   |  2 +-
 elf/dl-open.c                   | 10 ++---
 elf/dl-reloc-static-pie.c       |  2 +-
 elf/link.h                      | 35 +++++++++++-----
 elf/rtld-debugger-interface.txt | 15 +++++++
 elf/rtld.c                      |  4 +-
 elf/tst-dlmopen4.c              | 72 +++++++++++++++++++++++++++++++++
 include/link.h                  |  4 ++
 sysdeps/generic/ldsodefs.h      | 12 ++++--
 16 files changed, 249 insertions(+), 45 deletions(-)
 create mode 100644 csu/rtld-sizes.sym
 create mode 100644 elf/dl-debug-symbols.S
 create mode 100644 elf/tst-dlmopen4.c

Comments

Florian Weimer Sept. 13, 2021, 2:40 p.m. UTC | #1
* H. J. Lu:

> +	  /* Add the new namespace to the linked list.  After a namespace
> +	     is initialized, r_brk becomes non-zero.  A namespace becomes
> +	     empty (r_map == NULL) when it is unused.  But it is never
> +	     removed from the linked list.  */
> +	  r->r_next = _r_debug_extended.r_next;
> +	  atomic_store_release (&_r_debug_extended.r_next, r);

I think the addition should be at the end of the list, and the list
update (with release MO) should come last.  That should help with
reading the list concurrently.

Rest looks okay to me.

Thanks,
Florian
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 5b014fabbf..fd9d204022 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,9 @@  Version 2.35
 
 Major new features:
 
+* Bump r_version in the debugger interface to 2 and add a new field,
+  r_next, support multiple namespaces.
+
 * Support for the C.UTF-8 locale has been added to glibc.  The locale
   supports full code-point sorting for all valid Unicode code points.  A
   limitation in the framework for fnmatch, regexec, and regcomp requires
@@ -21,7 +24,13 @@  Major new features:
 
 Deprecated and removed features, and other changes affecting compatibility:
 
-  [Add deprecations, removals and changes affecting compatibility here]
+* The r_version update in the debugger interface makes the glibc binary
+  incompatible with GDB binaries built without the following commits:
+
+  c0154a4a21a gdb: Don't assume r_ldsomap when r_version > 1 on Linux
+  4eb629d50d4 gdbserver: Check r_version < 1 for Linux debugger interface
+
+  when audit modules or dlmopen are used.
 
 Changes to build and runtime requirements:
 
diff --git a/csu/Makefile b/csu/Makefile
index 3054329cea..e2390e4a7d 100644
--- a/csu/Makefile
+++ b/csu/Makefile
@@ -88,6 +88,9 @@  endif
 before-compile += $(objpfx)abi-tag.h
 generated += abi-tag.h
 
+# Put it here to generate it earlier.
+gen-as-const-headers += rtld-sizes.sym
+
 # These are the special initializer/finalizer files.  They are always the
 # first and last file in the link.  crti.o ... crtn.o define the global
 # "functions" _init and _fini to run the .init and .fini sections.
diff --git a/csu/rtld-sizes.sym b/csu/rtld-sizes.sym
new file mode 100644
index 0000000000..13924d5efd
--- /dev/null
+++ b/csu/rtld-sizes.sym
@@ -0,0 +1,6 @@ 
+#include <link.h>
+
+--
+R_DEBUG_SIZE		sizeof (struct r_debug)
+R_DEBUG_EXTENDED_SIZE	sizeof (struct r_debug_extended)
+R_DEBUG_EXTENDED_ALIGN	__alignof (struct r_debug_extended)
diff --git a/elf/Makefile b/elf/Makefile
index 9f3fadc37e..835b85bd7c 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -35,7 +35,8 @@  dl-routines	= $(addprefix dl-,load lookup object reloc deps \
 				  execstack open close trampoline \
 				  exception sort-maps lookup-direct \
 				  call-libc-early-init write \
-				  thread_gscope_wait tls_init_tp)
+				  thread_gscope_wait tls_init_tp \
+				  debug-symbols)
 ifeq (yes,$(use-ldconfig))
 dl-routines += dl-cache
 endif
@@ -203,7 +204,7 @@  tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 tst-tls16 tst-tls17 tst-tls18 tst-tls19 tst-tls-dlinfo \
 	 tst-align tst-align2 \
 	 tst-dlmodcount tst-dlopenrpath tst-deep1 \
-	 tst-dlmopen1 tst-dlmopen3 \
+	 tst-dlmopen1 tst-dlmopen3 tst-dlmopen4 \
 	 unload3 unload4 unload5 unload6 unload7 unload8 tst-global1 order2 \
 	 tst-audit1 tst-audit2 tst-audit8 tst-audit9 \
 	 tst-addr1 tst-thrlock \
@@ -1244,6 +1245,8 @@  $(objpfx)tst-dlmopen2.out: $(objpfx)tst-dlmopen1mod.so
 
 $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so
 
+$(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so
+
 $(objpfx)tst-audit1.out: $(objpfx)tst-auditmod1.so
 tst-audit1-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
 
diff --git a/elf/dl-close.c b/elf/dl-close.c
index f39001cab9..93ff5c96e9 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -500,7 +500,7 @@  _dl_close_worker (struct link_map *map, bool force)
 #endif
 
   /* Notify the debugger we are about to remove some loaded objects.  */
-  struct r_debug *r = _dl_debug_initialize (0, nsid);
+  struct r_debug *r = _dl_debug_update (nsid);
   r->r_state = RT_DELETE;
   _dl_debug_state ();
   LIBC_PROBE (unmap_start, 2, nsid, r);
diff --git a/elf/dl-debug-symbols.S b/elf/dl-debug-symbols.S
new file mode 100644
index 0000000000..5eb7e8fa3c
--- /dev/null
+++ b/elf/dl-debug-symbols.S
@@ -0,0 +1,37 @@ 
+/* Define symbols used to communicate dynamic linker state to the
+   debugger at runtime.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <rtld-sizes.h>
+
+/* Define 2 symbols, _r_debug_extended and _r_debug, which is an alias
+   of _r_debug_extended, but with the size of struct r_debug and is
+   provided as a compatibility symbol.   */
+
+	.globl	_r_debug
+	.type	_r_debug, %object
+	.size	_r_debug, R_DEBUG_SIZE
+	.hidden	_r_debug_extended
+	.globl	_r_debug_extended
+	.type	_r_debug_extended, %object
+	.size	_r_debug_extended, R_DEBUG_EXTENDED_SIZE
+	.section .bss
+	.balign	R_DEBUG_EXTENDED_ALIGN
+_r_debug:
+_r_debug_extended:
+	.zero	R_DEBUG_EXTENDED_SIZE
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index 2cd5f09753..e64ce9105f 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -30,37 +30,71 @@  extern const int verify_link_map_members[(VERIFY_MEMBER (l_addr)
 					  && VERIFY_MEMBER (l_prev))
 					 ? 1 : -1];
 
-/* This structure communicates dl state to the debugger.  The debugger
-   normally finds it via the DT_DEBUG entry in the dynamic section, but in
-   a statically-linked program there is no dynamic section for the debugger
-   to examine and it looks for this particular symbol name.  */
-struct r_debug _r_debug;
+/* Update the `r_map' member and return the address of `struct r_debug'
+   of the namespace NS. */
 
+struct r_debug *
+_dl_debug_update (Lmid_t ns)
+{
+  struct r_debug_extended *r;
+  if (ns == LM_ID_BASE)
+    r = &_r_debug_extended;
+  else
+    r = &GL(dl_ns)[ns]._ns_debug;
+  if (r->base.r_map == NULL)
+    atomic_store_release (&r->base.r_map,
+			  (void *) GL(dl_ns)[ns]._ns_loaded);
+  return &r->base;
+}
 
-/* Initialize _r_debug if it has not already been done.  The argument is
-   the run-time load address of the dynamic linker, to be put in
-   _r_debug.r_ldbase.  Returns the address of _r_debug.  */
+/* Initialize _r_debug_extended for the namespace NS.  LDBASE is the
+   run-time load address of the dynamic linker, to be put in
+   _r_debug_extended.r_ldbase.  Return the address of _r_debug.  */
 
 struct r_debug *
 _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
 {
-  struct r_debug *r;
+  struct r_debug_extended *r;
 
   if (ns == LM_ID_BASE)
-    r = &_r_debug;
-  else
-    r = &GL(dl_ns)[ns]._ns_debug;
+    {
+      r = &_r_debug_extended;
+      /* Initialize r_version to 1.  */
+      if (_r_debug_extended.base.r_version == 0)
+	_r_debug_extended.base.r_version = 1;
+    }
+  else if (DL_NNS > 1)
+    {
+      r = &GL(dl_ns)[ns]._ns_debug;
+      if (r->base.r_brk == 0)
+	{
+	  /* Add the new namespace to the linked list.  After a namespace
+	     is initialized, r_brk becomes non-zero.  A namespace becomes
+	     empty (r_map == NULL) when it is unused.  But it is never
+	     removed from the linked list.  */
+	  r->r_next = _r_debug_extended.r_next;
+	  atomic_store_release (&_r_debug_extended.r_next, r);
+	  /* Bump r_version to 2 for the new namespace.  */
+	  r->base.r_version = 2;
+	  atomic_store_release (&_r_debug_extended.base.r_version, 2);
+	}
+    }
 
-  if (r->r_map == NULL || ldbase != 0)
+  if (r->base.r_brk == 0)
     {
-      /* Tell the debugger where to find the map of loaded objects.  */
-      r->r_version = 1	/* R_DEBUG_VERSION XXX */;
-      r->r_ldbase = ldbase ?: _r_debug.r_ldbase;
-      r->r_map = (void *) GL(dl_ns)[ns]._ns_loaded;
-      r->r_brk = (ElfW(Addr)) &_dl_debug_state;
+      /* Tell the debugger where to find the map of loaded objects.
+	 This function is called from dlopen.  Initialize the namespace
+	 only once.  */
+      r->base.r_ldbase = ldbase ?: _r_debug_extended.base.r_ldbase;
+      r->base.r_brk = (ElfW(Addr)) &_dl_debug_state;
+      r->r_next = NULL;
     }
 
-  return r;
+  if (r->base.r_map == NULL)
+    atomic_store_release (&r->base.r_map,
+			  (void *) GL(dl_ns)[ns]._ns_loaded);
+
+  return &r->base;
 }
 
 
diff --git a/elf/dl-load.c b/elf/dl-load.c
index dc911e326a..9b30cd7ff1 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -949,7 +949,7 @@  _dl_map_object_from_fd (const char *name, const char *origname, int fd,
   /* Initialize to keep the compiler happy.  */
   const char *errstring = NULL;
   int errval = 0;
-  struct r_debug *r = _dl_debug_initialize (0, nsid);
+  struct r_debug *r = _dl_debug_update (nsid);
   bool make_consistent = false;
 
   /* Get file information.  To match the kernel behavior, do not fill
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 98f8bf81b3..d519d32961 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -574,7 +574,7 @@  dl_open_worker (void *a)
       if ((mode & RTLD_GLOBAL) && new->l_global == 0)
 	add_to_global_update (new);
 
-      assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
+      assert (_dl_debug_update (args->nsid)->r_state == RT_CONSISTENT);
 
       return;
     }
@@ -630,7 +630,7 @@  dl_open_worker (void *a)
 #endif
 
   /* Notify the debugger all new objects are now ready to go.  */
-  struct r_debug *r = _dl_debug_initialize (0, args->nsid);
+  struct r_debug *r = _dl_debug_update (args->nsid);
   r->r_state = RT_CONSISTENT;
   _dl_debug_state ();
   LIBC_PROBE (map_complete, 3, args->nsid, r, new);
@@ -830,7 +830,7 @@  no more namespaces available for dlmopen()"));
 	  ++GL(dl_nns);
 	}
 
-      _dl_debug_initialize (0, nsid)->r_state = RT_CONSISTENT;
+      _dl_debug_update (nsid)->r_state = RT_CONSISTENT;
     }
   /* Never allow loading a DSO in a namespace which is empty.  Such
      direct placements is only causing problems.  Also don't allow
@@ -899,7 +899,7 @@  no more namespaces available for dlmopen()"));
 	     the flag here.  */
 	}
 
-      assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);
+      assert (_dl_debug_update (args.nsid)->r_state == RT_CONSISTENT);
 
       /* Release the lock.  */
       __rtld_lock_unlock_recursive (GL(dl_load_lock));
@@ -908,7 +908,7 @@  no more namespaces available for dlmopen()"));
       _dl_signal_exception (errcode, &exception, NULL);
     }
 
-  assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);
+  assert (_dl_debug_update (args.nsid)->r_state == RT_CONSISTENT);
 
   /* Release the lock.  */
   __rtld_lock_unlock_recursive (GL(dl_load_lock));
diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
index d5bd2f31e9..289651b341 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -51,7 +51,7 @@  _dl_relocate_static_pie (void)
   ELF_DYNAMIC_RELOCATE (main_map, 0, 0, 0);
   main_map->l_relocated = 1;
 
-  /* Initialize _r_debug.  */
+  /* Initialize _r_debug_extended.  */
   struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE);
   r->r_state = RT_CONSISTENT;
 
diff --git a/elf/link.h b/elf/link.h
index ff3a85c847..200d40c9c6 100644
--- a/elf/link.h
+++ b/elf/link.h
@@ -34,14 +34,13 @@ 
 #include <bits/elfclass.h>		/* Defines __ELF_NATIVE_CLASS.  */
 #include <bits/link.h>
 
-/* Rendezvous structure used by the run-time dynamic linker to communicate
-   details of shared object loading to the debugger.  If the executable's
-   dynamic section has a DT_DEBUG element, the run-time linker sets that
-   element's value to the address where this structure can be found.  */
+/* The legacy rendezvous structure used by the run-time dynamic linker to
+   communicate details of shared object loading to the debugger.  */
 
 struct r_debug
   {
-    int r_version;		/* Version number for this protocol.  */
+    /* Version number for this protocol.  It should be greater than 0.  */
+    int r_version;
 
     struct link_map *r_map;	/* Head of the chain of loaded objects.  */
 
@@ -63,16 +62,34 @@  struct r_debug
     ElfW(Addr) r_ldbase;	/* Base address the linker is loaded at.  */
   };
 
-/* This is the instance of that structure used by the dynamic linker.  */
+/* This is the symbol of that structure provided by the dynamic linker.  */
 extern struct r_debug _r_debug;
 
+/* The extended rendezvous structure used by the run-time dynamic linker
+   to communicate details of shared object loading to the debugger.  If
+   the executable's dynamic section has a DT_DEBUG element, the run-time
+   linker sets that element's value to the address where this structure
+   can be found.  */
+
+struct r_debug_extended
+  {
+    struct r_debug base;
+
+    /* The following field is added by r_version == 2.  */
+
+    /* Link to the next r_debug_extended structure.  Each r_debug_extended
+       structure represents a different namespace.  The first
+       r_debug_extended structure is for the default namespace.  */
+    struct r_debug_extended *r_next;
+  };
+
 /* This symbol refers to the "dynamic structure" in the `.dynamic' section
    of whatever module refers to `_DYNAMIC'.  So, to find its own
-   `struct r_debug', a program could do:
+   `struct r_debug_extended', a program could do:
      for (dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn)
        if (dyn->d_tag == DT_DEBUG)
-	 r_debug = (struct r_debug *) dyn->d_un.d_ptr;
-   */
+	 r_debug_extended = (struct r_debug_extended *) dyn->d_un.d_ptr;
+ */
 extern ElfW(Dyn) _DYNAMIC[];
 
 /* Structure describing a loaded shared object.  The `l_next' and `l_prev'
diff --git a/elf/rtld-debugger-interface.txt b/elf/rtld-debugger-interface.txt
index 61bc99e4b0..f3476d8308 100644
--- a/elf/rtld-debugger-interface.txt
+++ b/elf/rtld-debugger-interface.txt
@@ -9,6 +9,9 @@  structure can be found.
 
 The r_debug structure contains (amongst others) the following fields:
 
+  int r_version:
+    Version number for this protocol.  It should be greater than 0.
+
   struct link_map *r_map:
     A linked list of loaded objects.
 
@@ -32,6 +35,18 @@  but there is no way for the debugger to discover whether any of the
 objects in the link-map have been relocated or not.
 
 
+Extension to the r_debug structure
+==================================
+
+The r_debug_extended structure is an extension of the r_debug interface.
+If r_version is 2, one additional field is available:
+
+  struct r_debug_extended *r_next;
+    Link to the next r_debug_extended structure.  Each r_debug_extended
+    structure represents a different namespace.  A namespace is active
+    if its r_map field isn't NULL.  The first r_debug_extended structure
+    is for the default namespace.
+
 Probe-based debugger interface
 ==============================
 
diff --git a/elf/rtld.c b/elf/rtld.c
index 878e6480f4..742c413c48 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1660,7 +1660,7 @@  dl_main (const ElfW(Phdr) *phdr,
      objects.  */
   call_init_paths (&state);
 
-  /* Initialize _r_debug.  */
+  /* Initialize _r_debug_extended.  */
   struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr,
 					    LM_ID_BASE);
   r->r_state = RT_CONSISTENT;
@@ -2491,7 +2491,7 @@  dl_main (const ElfW(Phdr) *phdr,
 
   /* Notify the debugger all new objects are now ready to go.  We must re-get
      the address since by now the variable might be in another object.  */
-  r = _dl_debug_initialize (0, LM_ID_BASE);
+  r = _dl_debug_update (LM_ID_BASE);
   r->r_state = RT_CONSISTENT;
   _dl_debug_state ();
   LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);
diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c
new file mode 100644
index 0000000000..3fe150e50b
--- /dev/null
+++ b/elf/tst-dlmopen4.c
@@ -0,0 +1,72 @@ 
+/* Test struct r_debug_extended via DT_DEBUG.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <link.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gnu/lib-names.h>
+#include <support/xdlfcn.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+
+#ifndef ELF_MACHINE_GET_R_DEBUG
+# define ELF_MACHINE_GET_R_DEBUG(d) \
+    (__extension__ ({ 						\
+      struct r_debug_extended *debug;				\
+      if ((d)->d_tag == DT_DEBUG)				\
+	debug = (struct r_debug_extended *) (d)->d_un.d_ptr;	\
+      else							\
+	debug = NULL;						\
+      debug; }))
+#endif
+
+static int
+do_test (void)
+{
+  ElfW(Dyn) *d;
+  struct r_debug_extended *debug = NULL;
+
+  for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d)
+    {
+      debug = ELF_MACHINE_GET_R_DEBUG (d);
+      if (debug != NULL)
+	break;
+    }
+
+  TEST_VERIFY_EXIT (debug != NULL);
+  TEST_COMPARE (debug->base.r_version, 1);
+  TEST_VERIFY_EXIT (debug->r_next == NULL);
+
+  void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so",
+		      RTLD_LAZY);
+
+  TEST_COMPARE (debug->base.r_version, 2);
+  TEST_VERIFY_EXIT (debug->r_next != NULL);
+  TEST_VERIFY_EXIT (debug->r_next->r_next == NULL);
+  TEST_VERIFY_EXIT (debug->r_next->base.r_map != NULL);
+  TEST_VERIFY_EXIT (debug->r_next->base.r_map->l_name != NULL);
+  const char *name = basename (debug->r_next->base.r_map->l_name);
+  TEST_COMPARE_STRING (name, "tst-dlmopen1mod.so");
+
+  xdlclose (h);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/include/link.h b/include/link.h
index 4af16cb596..7b8250db36 100644
--- a/include/link.h
+++ b/include/link.h
@@ -353,6 +353,10 @@  struct auditstate
 };
 
 
+/* This is the hidden instance of struct r_debug_extended used by the
+   dynamic linker.  */
+extern struct r_debug_extended _r_debug_extended attribute_hidden;
+
 #if __ELF_NATIVE_CLASS == 32
 # define symbind symbind32
 #elif __ELF_NATIVE_CLASS == 64
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 9c15259236..0cbcbfb5b0 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -355,7 +355,7 @@  struct rtld_global
       void (*free) (void *);
     } _ns_unique_sym_table;
     /* Keep track of changes to each namespace' list.  */
-    struct r_debug _ns_debug;
+    struct r_debug_extended _ns_debug;
   } _dl_ns[DL_NNS];
   /* One higher than index of last used namespace.  */
   EXTERN size_t _dl_nns;
@@ -1093,12 +1093,16 @@  extern void _dl_sort_maps (struct link_map **maps, unsigned int nmaps,
 extern void _dl_debug_state (void);
 rtld_hidden_proto (_dl_debug_state)
 
-/* Initialize `struct r_debug' if it has not already been done.  The
-   argument is the run-time load address of the dynamic linker, to be put
-   in the `r_ldbase' member.  Returns the address of the structure.  */
+/* Initialize `struct r_debug_extended' for the namespace NS.  LDBASE
+   is the run-time load address of the dynamic linker, to be put in the
+   `r_ldbase' member.  Return the address of the structure.  */
 extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
      attribute_hidden;
 
+/* Update the `r_map' member and return the address of `struct r_debug'
+   of the namespace NS.  */
+extern struct r_debug *_dl_debug_update (Lmid_t ns) attribute_hidden;
+
 /* Initialize the basic data structure for the search paths.  SOURCE
    is either "LD_LIBRARY_PATH" or "--library-path".
    GLIBC_HWCAPS_PREPEND adds additional glibc-hwcaps subdirectories to