diff mbox series

Extend struct r_debug to support multiple namespaces

Message ID YRhg4D0/39CIDFIL@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 fail Patch series failed to build

Commit Message

H.J. Lu Aug. 15, 2021, 12:33 a.m. UTC
On Mon, Aug 09, 2021 at 10:16:53AM -0700, Daniel Walker wrote:
> On Mon, Aug 09, 2021 at 07:32:26AM -0700, H.J. Lu wrote:
> > We need a new DT_XXX to support dlmopen.  We have 2 choices:
> > 
> > 1. Similar to DT_DEBUG.  Linker will allocate a new DT_XXX and
> > ld.so will fill it with the address of the new debug data structure for
> > dlmopen.
> > 2. Similar to DT_MIPS_RLD_MAP_REL/DT_MIPS_RLD_MAP.
> > Linker will allocate a space for a pointer, a new DT_XXX and fill
> > the DT_XXX entry with the address of the pointer.   ld.so will update
> > the pointer with the address of the new debug data structure for
> > dlmopen.
> > 
> > #1 is the most straightforward to implement.  #2 is compatible with
> > the current MIPS implementation.
> > 
> > Does anyone have any preferences?
> 
> 
> I have #1 fully implemented already.
> 

Here is a slightly different approach with DT_DEBUGSZ.  The corresponding
linker patch is at

https://sourceware.org/pipermail/binutils/2021-August/117706.html

H.J.
---
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).

Based on the patch from Conan C Huang <conhuang@cisco.com>:

https://sourceware.org/pipermail/libc-alpha/2020-June/115448.html

1. Extend struct r_debug into a linked-list, where each element correlates
to an unique namespace.
2. Add a new dynamic tag, DT_DEBUGSZ,

 #define DT_DEBUGSZ 0x6ffffff8

which records the size of the extended struct r_debug.
3. For executables with a DT_DEBUGSZ dynamic tag, debugger (GDB) can
traverse r_debug linked-list in the extended struct r_debug from the
DT_DEBUG dynamic tag to retrieve information for all loaded namespaces.
4. Provide the compatibility symbol, _r_debug, for programs which
reference _r_debug and update the copy of _r_debug in executable if
copy relocation is used.
---
 config.h.in                     |  4 +++
 configure                       | 43 +++++++++++++++++++++++
 configure.ac                    | 12 +++++++
 csu/Makefile                    |  3 ++
 csu/libc-start.c                |  2 +-
 csu/rtld-sizes.sym              |  4 +++
 elf/Makefile                    | 24 +++++++++++--
 elf/dl-close.c                  |  7 +++-
 elf/dl-debug-symbols-gen.c      | 24 +++++++++++++
 elf/dl-debug-symbols.S          | 34 +++++++++++++++++++
 elf/dl-debug.c                  | 35 +++++++++++++------
 elf/dl-load.c                   |  7 +++-
 elf/dl-open.c                   | 19 +++++++----
 elf/dl-reloc-static-pie.c       |  8 ++++-
 elf/elf.h                       |  1 +
 elf/link.h                      | 13 ++++++-
 elf/rtld-debugger-interface.txt | 12 +++++++
 elf/rtld.c                      | 10 ++++--
 elf/tst-_r_gnu_debug.c          | 60 +++++++++++++++++++++++++++++++++
 include/link.h                  |  4 +++
 sysdeps/generic/ldsodefs.h      |  6 ++--
 21 files changed, 304 insertions(+), 28 deletions(-)
 create mode 100644 csu/rtld-sizes.sym
 create mode 100644 elf/dl-debug-symbols-gen.c
 create mode 100644 elf/dl-debug-symbols.S
 create mode 100644 elf/tst-_r_gnu_debug.c

Comments

Daniel Walker Aug. 16, 2021, 4:20 p.m. UTC | #1
On Sat, Aug 14, 2021 at 05:33:36PM -0700, H.J. Lu wrote:
> On Mon, Aug 09, 2021 at 10:16:53AM -0700, Daniel Walker wrote:
> > On Mon, Aug 09, 2021 at 07:32:26AM -0700, H.J. Lu wrote:
> > > We need a new DT_XXX to support dlmopen.  We have 2 choices:
> > > 
> > > 1. Similar to DT_DEBUG.  Linker will allocate a new DT_XXX and
> > > ld.so will fill it with the address of the new debug data structure for
> > > dlmopen.
> > > 2. Similar to DT_MIPS_RLD_MAP_REL/DT_MIPS_RLD_MAP.
> > > Linker will allocate a space for a pointer, a new DT_XXX and fill
> > > the DT_XXX entry with the address of the pointer.   ld.so will update
> > > the pointer with the address of the new debug data structure for
> > > dlmopen.
> > > 
> > > #1 is the most straightforward to implement.  #2 is compatible with
> > > the current MIPS implementation.
> > > 
> > > Does anyone have any preferences?
> > 
> > 
> > I have #1 fully implemented already.
> > 
> 
> Here is a slightly different approach with DT_DEBUGSZ.  The corresponding
> linker patch is at
> 
> https://sourceware.org/pipermail/binutils/2021-August/117706.html
> 
> H.J.

What advantages does this provide over have a different structure ?

Lets say we wanted to extend the structure further in the future , is that
possible in this implementation ?

Daniel
H.J. Lu Aug. 17, 2021, 1:07 a.m. UTC | #2
On Mon, Aug 16, 2021 at 9:20 AM Daniel Walker <danielwa@cisco.com> wrote:
>
> On Sat, Aug 14, 2021 at 05:33:36PM -0700, H.J. Lu wrote:
> > On Mon, Aug 09, 2021 at 10:16:53AM -0700, Daniel Walker wrote:
> > > On Mon, Aug 09, 2021 at 07:32:26AM -0700, H.J. Lu wrote:
> > > > We need a new DT_XXX to support dlmopen.  We have 2 choices:
> > > >
> > > > 1. Similar to DT_DEBUG.  Linker will allocate a new DT_XXX and
> > > > ld.so will fill it with the address of the new debug data structure for
> > > > dlmopen.
> > > > 2. Similar to DT_MIPS_RLD_MAP_REL/DT_MIPS_RLD_MAP.
> > > > Linker will allocate a space for a pointer, a new DT_XXX and fill
> > > > the DT_XXX entry with the address of the pointer.   ld.so will update
> > > > the pointer with the address of the new debug data structure for
> > > > dlmopen.
> > > >
> > > > #1 is the most straightforward to implement.  #2 is compatible with
> > > > the current MIPS implementation.
> > > >
> > > > Does anyone have any preferences?
> > >
> > >
> > > I have #1 fully implemented already.
> > >
> >
> > Here is a slightly different approach with DT_DEBUGSZ.  The corresponding
> > linker patch is at
> >
> > https://sourceware.org/pipermail/binutils/2021-August/117706.html
> >
> > H.J.
>
> What advantages does this provide over have a different structure ?
>
> Lets say we wanted to extend the structure further in the future , is that
> possible in this implementation ?

I submitted the v2 patch.
diff mbox series

Patch

diff --git a/config.h.in b/config.h.in
index 0d92504f65..f05cf57f38 100644
--- a/config.h.in
+++ b/config.h.in
@@ -59,6 +59,10 @@ 
 /* Define if the linker supports the -z combreloc option.  */
 #undef	HAVE_Z_COMBRELOC
 
+/* Define to 1 if the linker supports the -z dt-debugsz option to emit
+   the DT_DEBUGSZ dynamic tag in executables.  */
+#undef HAVE_Z_DT_DEBUGSZ
+
 /* Define if _rtld_local structure should be forced into .sdata section.  */
 #undef	HAVE_SDATA_SECTION
 
diff --git a/configure b/configure
index 7272fbf6ea..6d06a1c87b 100755
--- a/configure
+++ b/configure
@@ -6003,6 +6003,49 @@  $as_echo "$libc_linker_feature" >&6; }
 config_vars="$config_vars
 have-no-dynamic-linker = $libc_cv_no_dynamic_linker"
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for linker that supports -z dt-debugsz" >&5
+$as_echo_n "checking for linker that supports -z dt-debugsz... " >&6; }
+libc_linker_feature=no
+if test x"$gnu_ld" = x"yes"; then
+  libc_linker_check=`$LD -v --help 2>/dev/null | grep "\-z dt-debugsz"`
+  if test -n "$libc_linker_check"; then
+    cat > conftest.c <<EOF
+int _start (void) { return 42; }
+EOF
+    if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
+				-Wl,-z,dt-debugsz -nostdlib -nostartfiles
+				-fPIC -shared -o conftest.so conftest.c
+				1>&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+    then
+      libc_linker_feature=yes
+    fi
+    rm -f conftest*
+  fi
+fi
+if test $libc_linker_feature = yes; then
+  libc_cv_z_dt_debugsz=yes
+else
+  libc_cv_z_dt_debugsz=no
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
+$as_echo "$libc_linker_feature" >&6; }
+config_vars="$config_vars
+have-z-dt-debugsz = $libc_cv_z_dt_debugsz"
+if test $libc_cv_z_dt_debugsz = yes; then
+  have_z_dt_debugsz=1
+else
+  have_z_dt_debugsz=0
+fi
+cat >>confdefs.h <<_ACEOF
+#define HAVE_Z_DT_DEBUGSZ $have_z_dt_debugsz
+_ACEOF
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -static-pie" >&5
 $as_echo_n "checking for -static-pie... " >&6; }
 if ${libc_cv_static_pie+:} false; then :
diff --git a/configure.ac b/configure.ac
index af47cd51e6..c5ffa381eb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1343,6 +1343,18 @@  LIBC_LINKER_FEATURE([--no-dynamic-linker],
 		    [libc_cv_no_dynamic_linker=no])
 LIBC_CONFIG_VAR([have-no-dynamic-linker], [$libc_cv_no_dynamic_linker])
 
+LIBC_LINKER_FEATURE([-z dt-debugsz],
+		    [-Wl,-z,dt-debugsz],
+		    [libc_cv_z_dt_debugsz=yes],
+		    [libc_cv_z_dt_debugsz=no])
+LIBC_CONFIG_VAR([have-z-dt-debugsz], [$libc_cv_z_dt_debugsz])
+if test $libc_cv_z_dt_debugsz = yes; then
+  have_z_dt_debugsz=1
+else
+  have_z_dt_debugsz=0
+fi
+AC_DEFINE_UNQUOTED([HAVE_Z_DT_DEBUGSZ], [$have_z_dt_debugsz])
+
 AC_CACHE_CHECK(for -static-pie, libc_cv_static_pie, [dnl
 LIBC_TRY_CC_OPTION([-static-pie],
 		   [libc_cv_static_pie=yes],
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/libc-start.c b/csu/libc-start.c
index 0350b006fd..8cfa68a057 100644
--- a/csu/libc-start.c
+++ b/csu/libc-start.c
@@ -403,7 +403,7 @@  LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
 #endif
 
 #ifndef SHARED
-  _dl_debug_initialize (0, LM_ID_BASE);
+  _dl_debug_initialize (0, LM_ID_BASE, NULL);
 #endif
 
   __libc_start_call_main (main, argc, argv MAIN_AUXVEC_PARAM);
diff --git a/csu/rtld-sizes.sym b/csu/rtld-sizes.sym
new file mode 100644
index 0000000000..2583e58b19
--- /dev/null
+++ b/csu/rtld-sizes.sym
@@ -0,0 +1,4 @@ 
+#include <link.h>
+
+--
+COMPAT_R_DEBUG_SIZE	offsetof (struct r_debug, r_ldbase) + sizeof (ElfW(Addr))
diff --git a/elf/Makefile b/elf/Makefile
index d05f410592..c97c9f8468 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
@@ -224,7 +225,7 @@  tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 tst-tls-ie tst-tls-ie-dlmopen argv0test \
 	 tst-glibc-hwcaps tst-glibc-hwcaps-prepend tst-glibc-hwcaps-mask \
 	 tst-tls20 tst-tls21 tst-dlmopen-dlerror tst-dlmopen-gethostbyname \
-	 tst-dl-is_dso
+	 tst-dl-is_dso tst-_r_gnu_debug
 #	 reldep9
 tests-internal += loadtest unload unload2 circleload1 \
 	 neededtest neededtest2 neededtest3 neededtest4 \
@@ -672,6 +673,21 @@  LC_ALL=C sed $(ldd-rewrite) < $< \
 endef
 endif
 
+ifeq ($(build-shared),yes)
+generated += dl-debug-compat-symbols.os dl-debug-compat-symbols.o
+
+libof-dl-debug-compat-symbols = rtld
+
+$(objpfx)dl-debug-compat-symbols.os: dl-debug-symbols-gen.c
+	$(compile-command.c) -S
+
+$(objpfx)dl-debug-compat-symbols.o: dl-debug-symbols-gen.c
+	$(compile-command.c) -S
+
+$(objpfx)dl-debug-symbols.os: $(objpfx)dl-debug-compat-symbols.os
+$(objpfx)dl-debug-symbols.o: $(objpfx)dl-debug-compat-symbols.o
+endif
+
 $(objpfx)ldd: ldd.bash.in $(common-objpfx)soversions.mk \
 	      $(common-objpfx)config.make
 	$(gen-ldd)
@@ -1906,3 +1922,7 @@  $(objpfx)tst-getauxval-static.out: $(objpfx)tst-auxvalmod.so
 tst-getauxval-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
 
 $(objpfx)tst-dlmopen-gethostbyname.out: $(objpfx)tst-dlmopen-gethostbyname-mod.so
+
+ifeq ($(have-z-dt-debugsz),yes)
+LDFLAGS-tst-_r_gnu_debug += -Wl,-z,dt-debugsz
+endif
diff --git a/elf/dl-close.c b/elf/dl-close.c
index f39001cab9..647a5fafe1 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -500,8 +500,11 @@  _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_debug_compat;
+  struct r_debug *r = _dl_debug_initialize (0, nsid, &r_debug_compat);
   r->r_state = RT_DELETE;
+  if (r_debug_compat != NULL)
+    r_debug_compat->r_state = RT_DELETE;
   _dl_debug_state ();
   LIBC_PROBE (unmap_start, 2, nsid, r);
 
@@ -821,6 +824,8 @@  _dl_close_worker (struct link_map *map, bool force)
 
   /* Notify the debugger those objects are finalized and gone.  */
   r->r_state = RT_CONSISTENT;
+  if (r_debug_compat != NULL)
+    r_debug_compat->r_state = RT_CONSISTENT;
   _dl_debug_state ();
   LIBC_PROBE (unmap_complete, 2, nsid, r);
 
diff --git a/elf/dl-debug-symbols-gen.c b/elf/dl-debug-symbols-gen.c
new file mode 100644
index 0000000000..a8d2aea75d
--- /dev/null
+++ b/elf/dl-debug-symbols-gen.c
@@ -0,0 +1,24 @@ 
+/* Generate the _r_gnu_debug symbol 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 <link.h>
+
+/* This structure communicates dl state to the debugger.  The debugger
+   finds it via the DT_DEBUG entry in the dynamic section.  */
+struct r_debug _r_gnu_debug;
diff --git a/elf/dl-debug-symbols.S b/elf/dl-debug-symbols.S
new file mode 100644
index 0000000000..b43a2c310d
--- /dev/null
+++ b/elf/dl-debug-symbols.S
@@ -0,0 +1,34 @@ 
+/* 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>
+
+#ifdef SHARED
+# include "dl-debug-compat-symbols.os"
+#else
+# include "dl-debug-compat-symbols.o"
+#endif
+
+/* Define the compatibility symbol, _r_debug, used to communicate
+   dynamic linker 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.
+   This must be done after _r_gnu_debug has been defined first.  */
+declare_object_symbol_alias (_r_debug, _r_gnu_debug, COMPAT_R_DEBUG_SIZE);
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index 2cd5f09753..e7d634e237 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -30,34 +30,47 @@  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;
-
+/* The compatibility symbol, _r_debug, which can have a different
+   address from _r_gnu_debug at run-time due to copy relocation.  */
+extern struct r_debug _r_debug;
 
 /* 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.  */
 
 struct r_debug *
-_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
+_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns,
+		      struct r_debug ** _r_debug_compat_p)
 {
-  struct r_debug *r;
+  struct r_debug *r_debug_compat = NULL;
+  struct r_debug *r, *rp;
 
   if (ns == LM_ID_BASE)
-    r = &_r_debug;
+    {
+      r = &_r_gnu_debug;
+      if (r != &_r_debug)
+	r_debug_compat = &_r_debug;
+    }
   else
-    r = &GL(dl_ns)[ns]._ns_debug;
+    {
+      r = &GL(dl_ns)[ns]._ns_debug;
+      rp = &GL(dl_ns)[ns - 1]._ns_debug;
+      rp->next = r;
+      if (ns - 1 == LM_ID_BASE)
+        _r_gnu_debug.next = r;
+    }
+
+  if (_r_debug_compat_p != NULL)
+    *_r_debug_compat_p = r_debug_compat;
 
   if (r->r_map == NULL || ldbase != 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_ldbase = ldbase ?: _r_gnu_debug.r_ldbase;
       r->r_map = (void *) GL(dl_ns)[ns]._ns_loaded;
       r->r_brk = (ElfW(Addr)) &_dl_debug_state;
+      r->next = NULL;
     }
 
   return r;
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 650e4edc35..370f56a6d6 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -949,7 +949,8 @@  _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_debug_compat;
+  struct r_debug *r = _dl_debug_initialize (0, nsid, &r_debug_compat);
   bool make_consistent = false;
 
   /* Get file information.  To match the kernel behavior, do not fill
@@ -986,6 +987,8 @@  _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 	  if (make_consistent && r != NULL)
 	    {
 	      r->r_state = RT_CONSISTENT;
+	      if (r_debug_compat != NULL)
+		r_debug_compat->r_state = RT_CONSISTENT;
 	      _dl_debug_state ();
 	      LIBC_PROBE (map_failed, 2, nsid, r);
 	    }
@@ -1082,6 +1085,8 @@  _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 	 call _dl_debug_initialize in a static program in case dynamic
 	 linking has not been used before.  */
       r->r_state = RT_ADD;
+      if (r_debug_compat != NULL)
+	r_debug_compat->r_state = RT_ADD;
       _dl_debug_state ();
       LIBC_PROBE (map_start, 2, nsid, r);
       make_consistent = true;
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..cf4f381b3f 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -523,7 +523,7 @@  dl_open_worker (void *a)
 
   /* One might be tempted to assert that we are RT_CONSISTENT at this point, but that
      may not be true if this is a recursive call to dlopen.  */
-  _dl_debug_initialize (0, args->nsid);
+  _dl_debug_initialize (0, args->nsid, NULL);
 
   /* Load the named object.  */
   struct link_map *new;
@@ -574,7 +574,8 @@  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_initialize (0, args->nsid, NULL)->r_state
+	      == RT_CONSISTENT);
 
       return;
     }
@@ -630,8 +631,12 @@  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_debug_compat;
+  struct r_debug *r = _dl_debug_initialize (0, args->nsid,
+					    &r_debug_compat);
   r->r_state = RT_CONSISTENT;
+  if (r_debug_compat != NULL)
+    r_debug_compat->r_state = RT_CONSISTENT;
   _dl_debug_state ();
   LIBC_PROBE (map_complete, 3, args->nsid, r, new);
 
@@ -830,7 +835,7 @@  no more namespaces available for dlmopen()"));
 	  ++GL(dl_nns);
 	}
 
-      _dl_debug_initialize (0, nsid)->r_state = RT_CONSISTENT;
+      _dl_debug_initialize (0, nsid, NULL)->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 +904,8 @@  no more namespaces available for dlmopen()"));
 	     the flag here.  */
 	}
 
-      assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);
+      assert (_dl_debug_initialize (0, args.nsid, NULL)->r_state
+	      == RT_CONSISTENT);
 
       /* Release the lock.  */
       __rtld_lock_unlock_recursive (GL(dl_load_lock));
@@ -908,7 +914,8 @@  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_initialize (0, args.nsid, NULL)->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..30c8f1a8bc 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -52,7 +52,7 @@  _dl_relocate_static_pie (void)
   main_map->l_relocated = 1;
 
   /* Initialize _r_debug.  */
-  struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE);
+  struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE, NULL);
   r->r_state = RT_CONSISTENT;
 
   /* Set up debugging before the debugger is notified for the first
@@ -66,5 +66,11 @@  _dl_relocate_static_pie (void)
        with the run-time address of the r_debug structure  */
     main_map->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
 # endif
+
+  /* There is a DT_DEBUGSZ entry in the dynamic section.  Fill in the
+     size of the r_debug structure  */
+  if (main_map->l_info[VERSYMIDX (DT_DEBUGSZ)] != NULL)
+    main_map->l_info[VERSYMIDX (DT_DEBUGSZ)]->d_un.d_val
+      = sizeof (struct r_debug);
 }
 #endif
diff --git a/elf/elf.h b/elf/elf.h
index 4738dfa28f..f9f4d217c2 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -939,6 +939,7 @@  typedef struct
    GNU extension.  */
 #define DT_VERSYM	0x6ffffff0
 
+#define DT_DEBUGSZ	0x6ffffff8
 #define DT_RELACOUNT	0x6ffffff9
 #define DT_RELCOUNT	0x6ffffffa
 
diff --git a/elf/link.h b/elf/link.h
index ff3a85c847..229b22c57a 100644
--- a/elf/link.h
+++ b/elf/link.h
@@ -61,9 +61,20 @@  struct r_debug
       } r_state;
 
     ElfW(Addr) r_ldbase;	/* Base address the linker is loaded at.  */
+
+    /* The following extended fields should be accessed only after
+       verifying the size of the r_debug structure via the DT_DEBUGSZ
+       dynamic tag.  */
+
+    /* Link to next r_debug structure.  Each r_debug structure represents
+       a different namespace.  The first r_debug structure is for the
+       default namespace.  */
+    struct r_debug *next;
   };
 
-/* 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.  Access to its fields beyond r_ldbase may be invalid.
+ */
 extern struct r_debug _r_debug;
 
 /* This symbol refers to the "dynamic structure" in the `.dynamic' section
diff --git a/elf/rtld-debugger-interface.txt b/elf/rtld-debugger-interface.txt
index 61bc99e4b0..920da9c21a 100644
--- a/elf/rtld-debugger-interface.txt
+++ b/elf/rtld-debugger-interface.txt
@@ -32,6 +32,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
+==================================
+
+If the executable's dynamic section has a DT_DEBUGSZ element, the
+run-time linker sets that element's value to the size of the extended
+r_debug structure which has the additional field:
+
+  struct r_debug *next;
+    Link to next r_debug structure.  Each r_debug structure represents a
+    different namespace.  The first r_debug structure is for the default
+    namespace.
+
 Probe-based debugger interface
 ==============================
 
diff --git a/elf/rtld.c b/elf/rtld.c
index 878e6480f4..a652dd3729 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1662,7 +1662,7 @@  dl_main (const ElfW(Phdr) *phdr,
 
   /* Initialize _r_debug.  */
   struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr,
-					    LM_ID_BASE);
+					    LM_ID_BASE, NULL);
   r->r_state = RT_CONSISTENT;
 
   /* Put the link_map for ourselves on the chain so it can be found by
@@ -1771,6 +1771,12 @@  dl_main (const ElfW(Phdr) *phdr,
     GL(dl_rtld_map).l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
 #endif
 
+  /* There is a DT_DEBUGSZ entry in the dynamic section.  Fill in the
+     size of the r_debug structure  */
+  if (main_map->l_info[VERSYMIDX (DT_DEBUGSZ)] != NULL)
+    main_map->l_info[VERSYMIDX (DT_DEBUGSZ)]->d_un.d_val
+      = sizeof (struct r_debug);
+
   /* We start adding objects.  */
   r->r_state = RT_ADD;
   _dl_debug_state ();
@@ -2491,7 +2497,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_initialize (0, LM_ID_BASE, NULL);
   r->r_state = RT_CONSISTENT;
   _dl_debug_state ();
   LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);
diff --git a/elf/tst-_r_gnu_debug.c b/elf/tst-_r_gnu_debug.c
new file mode 100644
index 0000000000..cf0f99c9da
--- /dev/null
+++ b/elf/tst-_r_gnu_debug.c
@@ -0,0 +1,60 @@ 
+/* Test _r_gnu_debug via DT_DEBUG and DT_DEBUGSZ.
+   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 <config.h>
+#include <link.h>
+#include <stdlib.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 *debug;				\
+      if ((d)->d_tag == DT_DEBUG)			\
+	debug = (struct r_debug *) (d)->d_un.d_ptr;	\
+      else						\
+	debug = NULL;					\
+      debug; }))
+#endif
+
+static int
+do_test (void)
+{
+  int found = 0;
+  ElfW(Dyn) *d;
+  for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d)
+    if (d->d_tag == DT_DEBUGSZ)
+      {
+	TEST_VERIFY_EXIT (d->d_un.d_val == sizeof (struct r_debug));
+	found++;
+      }
+    else
+      {
+	struct r_debug *debug = ELF_MACHINE_GET_R_DEBUG (d);
+	if (debug != NULL)
+	  {
+	    TEST_VERIFY_EXIT (debug->r_version == 1);
+	    found++;
+	  }
+      }
+  return (found == (HAVE_Z_DT_DEBUGSZ ? 2 : 1)
+	  ? EXIT_SUCCESS : EXIT_UNSUPPORTED);
+}
+
+#include <support/test-driver.c>
diff --git a/include/link.h b/include/link.h
index 4af16cb596..a762fccdc1 100644
--- a/include/link.h
+++ b/include/link.h
@@ -353,6 +353,10 @@  struct auditstate
 };
 
 
+/* This is the hidden instance of struct r_debug used by the dynamic
+   linker.  */
+extern struct r_debug _r_gnu_debug 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..c02027b309 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1095,8 +1095,10 @@  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.  */
-extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
+   in the `r_ldbase' member.  Returns the address of the structure and
+   the address of the compatibility symbol.  */
+extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns,
+					     struct r_debug **)
      attribute_hidden;
 
 /* Initialize the basic data structure for the search paths.  SOURCE