diff mbox series

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

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

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent
dj/TryBot-32bit success Build for i686

Commit Message

H.J. Lu Sept. 8, 2021, 6:25 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                      | 36 ++++++++++++-----
 elf/rtld-debugger-interface.txt | 14 +++++++
 elf/rtld.c                      |  4 +-
 elf/tst-dlmopen4.c              | 68 +++++++++++++++++++++++++++++++
 elf/tst-dlopen-nodelete-reloc.c |  2 +
 include/link.h                  |  4 ++
 sysdeps/generic/ldsodefs.h      | 12 ++++--
 17 files changed, 247 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. 10, 2021, 6:59 p.m. UTC | #1
* H. J. Lu:

> diff --git a/elf/link.h b/elf/link.h
> index ff3a85c847..a297318236 100644
> --- a/elf/link.h
> +++ b/elf/link.h
> @@ -34,14 +34,13 @@

> -/* This is the instance of that structure used by the dynamic linker.  */
> +/* This is the compatibility symbol of that structure provided by the
> +   dynamic linker.  */
>  extern struct r_debug _r_debug;

I don't think we should say “compatibility symbol” in a public header.

Can we move GNAT off this symbol and deprecate it at least?

> +/* 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[];

What about shared objects?  How can they find r_debug_extended?  Should
they just make sure they have DT_DEBUG in their dynamic section?

Calling getauxval (AT_PHDR) has a relocation dependencies, which I
expect some consumers want to avoid.

> +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.  The first r_debug_extended
> +    structure is for the default namespace.

I think this should say how a reader can determine which list elements
are in fact active.

> diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c
> new file mode 100644
> index 0000000000..7a6c502e8c
> --- /dev/null
> +++ b/elf/tst-dlmopen4.c

> +static int
> +do_test (void)
> +{
> +  void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so",
> +		      RTLD_LAZY);

I think this should test that r_version is 1 before the dlmopen call.

> +
> +  int status = EXIT_FAILURE;
> +  ElfW(Dyn) *d;
> +  for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d)
> +    {
> +      struct r_debug_extended *debug = ELF_MACHINE_GET_R_DEBUG (d);
> +      if (debug != NULL)
> +	{
> +	  TEST_VERIFY_EXIT (debug->base.r_version == 2);

You could use TEST_COMPARE.

> +	  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_VERIFY_EXIT (strcmp (name, "tst-dlmopen1mod.so") == 0);

You could use TEST_COMPARE_STRING.

Sorry, I have not reviewed the actual mechanics of the patch.

Thanks,
Florian
H.J. Lu Sept. 10, 2021, 7:26 p.m. UTC | #2
On Fri, Sep 10, 2021 at 11:59 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu:
>
> > diff --git a/elf/link.h b/elf/link.h
> > index ff3a85c847..a297318236 100644
> > --- a/elf/link.h
> > +++ b/elf/link.h
> > @@ -34,14 +34,13 @@
>
> > -/* This is the instance of that structure used by the dynamic linker.  */
> > +/* This is the compatibility symbol of that structure provided by the
> > +   dynamic linker.  */
> >  extern struct r_debug _r_debug;
>
> I don't think we should say “compatibility symbol” in a public header.

I will remove "compatibility".

> Can we move GNAT off this symbol and deprecate it at least?

There is no harm in keeping it.

> > +/* 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[];
>
> What about shared objects?  How can they find r_debug_extended?  Should
> they just make sure they have DT_DEBUG in their dynamic section?

Linker generates DT_DEBUG only in the executable.   dl_iterate_phdr can be
used to locate DT_DEBUG in the executable.

>
> Calling getauxval (AT_PHDR) has a relocation dependencies, which I
> expect some consumers want to avoid.
>
> > +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.  The first r_debug_extended
> > +    structure is for the default namespace.
>
> I think this should say how a reader can determine which list elements
> are in fact active.

I will update it.

>
> > diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c
> > new file mode 100644
> > index 0000000000..7a6c502e8c
> > --- /dev/null
> > +++ b/elf/tst-dlmopen4.c
>
> > +static int
> > +do_test (void)
> > +{
> > +  void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so",
> > +                   RTLD_LAZY);
>
> I think this should test that r_version is 1 before the dlmopen call.

I will add the test.

> > +
> > +  int status = EXIT_FAILURE;
> > +  ElfW(Dyn) *d;
> > +  for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d)
> > +    {
> > +      struct r_debug_extended *debug = ELF_MACHINE_GET_R_DEBUG (d);
> > +      if (debug != NULL)
> > +     {
> > +       TEST_VERIFY_EXIT (debug->base.r_version == 2);
>
> You could use TEST_COMPARE.

I will fix it.

>
> > +       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_VERIFY_EXIT (strcmp (name, "tst-dlmopen1mod.so") == 0);
>
> You could use TEST_COMPARE_STRING.

I will fix it.

> Sorry, I have not reviewed the actual mechanics of the patch.
>
> Thanks,
> Florian
>

Thanks.
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..a297318236 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,35 @@  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 compatibility 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..f6aaa28706 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,17 @@  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.  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..7a6c502e8c
--- /dev/null
+++ b/elf/tst-dlmopen4.c
@@ -0,0 +1,68 @@ 
+/* 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)
+{
+  void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so",
+		      RTLD_LAZY);
+
+  int status = EXIT_FAILURE;
+  ElfW(Dyn) *d;
+  for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d)
+    {
+      struct r_debug_extended *debug = ELF_MACHINE_GET_R_DEBUG (d);
+      if (debug != NULL)
+	{
+	  TEST_VERIFY_EXIT (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_VERIFY_EXIT (strcmp (name, "tst-dlmopen1mod.so") == 0);
+	  status = EXIT_SUCCESS;
+	}
+    }
+
+  xdlclose (h);
+
+  return status;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-nodelete-reloc.c b/elf/tst-dlopen-nodelete-reloc.c
index 6b07d88581..22bdf2521d 100644
--- a/elf/tst-dlopen-nodelete-reloc.c
+++ b/elf/tst-dlopen-nodelete-reloc.c
@@ -77,6 +77,7 @@ 
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
+#include <link.h>
 #include <support/check.h>
 #include <support/xdlfcn.h>
 
@@ -161,6 +162,7 @@  do_test (void)
                == NULL);
   const char *message = dlerror ();
   printf ("info: test 6 message: %s\n", message);
+  TEST_VERIFY_EXIT (_r_debug.r_version == 1);
   /* This must not close the object, it must still be NODELETE.  */
   xdlclose (mod14);
   xdlopen ("tst-dlopen-nodelete-reloc-mod14.so", RTLD_NOW | RTLD_NOLOAD);
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