public inbox for gdb-cvs@sourceware.org
help / color / mirror / Atom feed
* [binutils-gdb] gdb, gdbserver: support dlmopen()
@ 2022-10-18 13:24 Markus Metzger
  0 siblings, 0 replies; only message in thread
From: Markus Metzger @ 2022-10-18 13:24 UTC (permalink / raw)
  To: gdb-cvs

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=8d56636a0ecbe6c38bf52b0683326ee21693c548

commit 8d56636a0ecbe6c38bf52b0683326ee21693c548
Author: Markus Metzger <markus.t.metzger@intel.com>
Date:   Mon Oct 4 10:24:35 2021 +0200

    gdb, gdbserver: support dlmopen()
    
    In glibc, the r_debug structure contains (amongst others) the following
    fields:
    
      int r_version:
        Version number for this protocol.  It should be greater than 0.
    
    If r_version is 2, struct r_debug is extended to struct r_debug_extended
    with one additional field:
    
      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.
    
    1. Change solib_svr4_r_map argument to take the debug base.
    2. Add solib_svr4_r_next to find the link map in the next namespace from
    the r_next field.
    3. Update svr4_current_sos_direct to get the link map in the next namespace
    from the r_next field.
    4. Don't check shared libraries in other namespaces when updating shared
    libraries in a new namespace.
    5. Update svr4_same to check the load offset in addition to the name
    6. Update svr4_default_sos to also set l_addr_inferior
    7. Change the flat solib_list into a per-namespace list using the
    namespace's r_debug address to identify the namespace.
    
    Add gdb.base/dlmopen.exp to test this.
    
    To remain backwards compatible with older gdbserver, we reserve the
    namespace zero for a flat list of solibs from all namespaces.  Subsequent
    patches will extend RSP to allow listing libraries grouped by namespace.
    
    This fixes PR 11839.
    
    Co-authored-by: Lu, Hongjiu  <hongjiu.lu@intel.com>

Diff:
---
 gdb/linux-tdep.c                     |   2 +
 gdb/mips-fbsd-tdep.c                 |   2 +
 gdb/mips-netbsd-tdep.c               |   2 +
 gdb/solib-svr4.c                     | 385 ++++++++++++++++++++++++++---------
 gdb/solib-svr4.h                     |   3 +
 gdb/testsuite/gdb.base/dlmopen-lib.c |  25 +++
 gdb/testsuite/gdb.base/dlmopen.c     |  65 ++++++
 gdb/testsuite/gdb.base/dlmopen.exp   | 150 ++++++++++++++
 gdb/testsuite/lib/gdb.exp            |  96 +++++++++
 gdbserver/linux-low.cc               | 247 +++++++++++++---------
 10 files changed, 792 insertions(+), 185 deletions(-)

diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index dccb45d73a8..0a2fced7804 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2826,6 +2826,7 @@ linux_ilp32_fetch_link_map_offsets ()
       lmo.r_map_offset = 4;
       lmo.r_brk_offset = 8;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = 20;
 
       /* Everything we need is in the first 20 bytes.  */
       lmo.link_map_size = 20;
@@ -2854,6 +2855,7 @@ linux_lp64_fetch_link_map_offsets ()
       lmo.r_map_offset = 8;
       lmo.r_brk_offset = 16;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = 40;
 
       /* Everything we need is in the first 40 bytes.  */
       lmo.link_map_size = 40;
diff --git a/gdb/mips-fbsd-tdep.c b/gdb/mips-fbsd-tdep.c
index eae53108c03..0de1007d74f 100644
--- a/gdb/mips-fbsd-tdep.c
+++ b/gdb/mips-fbsd-tdep.c
@@ -495,6 +495,7 @@ mips_fbsd_ilp32_fetch_link_map_offsets (void)
       lmo.r_map_offset = 4;
       lmo.r_brk_offset = 8;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = -1;
 
       lmo.link_map_size = 24;
       lmo.l_addr_offset = 0;
@@ -522,6 +523,7 @@ mips_fbsd_lp64_fetch_link_map_offsets (void)
       lmo.r_map_offset = 8;
       lmo.r_brk_offset = 16;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = -1;
 
       lmo.link_map_size = 48;
       lmo.l_addr_offset = 0;
diff --git a/gdb/mips-netbsd-tdep.c b/gdb/mips-netbsd-tdep.c
index c13e1fd0802..344ec1fcfd4 100644
--- a/gdb/mips-netbsd-tdep.c
+++ b/gdb/mips-netbsd-tdep.c
@@ -308,6 +308,7 @@ mipsnbsd_ilp32_fetch_link_map_offsets (void)
       lmo.r_map_offset = 4;
       lmo.r_brk_offset = 8;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = -1;
 
       /* Everything we need is in the first 24 bytes.  */
       lmo.link_map_size = 24;
@@ -336,6 +337,7 @@ mipsnbsd_lp64_fetch_link_map_offsets (void)
       lmo.r_map_offset = 8;
       lmo.r_brk_offset = 16;
       lmo.r_ldsomap_offset = -1;
+      lmo.r_next_offset = -1;
 
       /* Everything we need is in the first 40 bytes.  */
       lmo.link_map_size = 48;
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 222b9df3c47..a80e7e30561 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -46,10 +46,12 @@
 #include "gdb_bfd.h"
 #include "probe.h"
 
+#include <map>
+
 static struct link_map_offsets *svr4_fetch_link_map_offsets (void);
 static int svr4_have_link_map_offsets (void);
 static void svr4_relocate_main_executable (void);
-static void svr4_free_library_list (void *p_list);
+static void svr4_free_library_list (so_list *solist);
 static void probes_table_remove_objfile_probes (struct objfile *objfile);
 static void svr4_iterate_over_objfiles_in_search_order
   (gdbarch *gdbarch, iterate_over_objfiles_in_search_order_cb_ftype cb,
@@ -174,7 +176,16 @@ svr4_same_1 (const char *gdb_so_name, const char *inferior_so_name)
 static int
 svr4_same (struct so_list *gdb, struct so_list *inferior)
 {
-  return (svr4_same_1 (gdb->so_original_name, inferior->so_original_name));
+  if (!svr4_same_1 (gdb->so_original_name, inferior->so_original_name))
+    return false;
+
+  /* There may be different instances of the same library, in different
+     namespaces.  Each instance, however, must have been loaded at a
+     different address so its relocation offset would be different.  */
+  const lm_info_svr4 *lmg = (const lm_info_svr4 *) gdb->lm_info;
+  const lm_info_svr4 *lmi = (const lm_info_svr4 *) inferior->lm_info;
+
+  return (lmg->l_addr_inferior == lmi->l_addr_inferior);
 }
 
 static std::unique_ptr<lm_info_svr4>
@@ -329,7 +340,7 @@ struct svr4_info
   svr4_info () = default;
   ~svr4_info ();
 
-  /* Base of dynamic linker structures.  */
+  /* Base of dynamic linker structures in default namespace.  */
   CORE_ADDR debug_base = 0;
 
   /* Validity flag for debug_loader_offset.  */
@@ -341,7 +352,7 @@ struct svr4_info
   /* Name of the dynamic linker, valid if debug_loader_offset_p.  */
   char *debug_loader_name = nullptr;
 
-  /* Load map address for the main executable.  */
+  /* Load map address for the main executable in default namespace.  */
   CORE_ADDR main_lm_addr = 0;
 
   CORE_ADDR interp_text_sect_low = 0;
@@ -349,9 +360,9 @@ struct svr4_info
   CORE_ADDR interp_plt_sect_low = 0;
   CORE_ADDR interp_plt_sect_high = 0;
 
-  /* Nonzero if the list of objects was last obtained from the target
+  /* True if the list of objects was last obtained from the target
      via qXfer:libraries-svr4:read.  */
-  int using_xfer = 0;
+  bool using_xfer = false;
 
   /* Table of struct probe_and_action instances, used by the
      probes-based interface to map breakpoint addresses to probes
@@ -359,14 +370,35 @@ struct svr4_info
      probe_and_action->prob->address.  */
   htab_up probes_table;
 
-  /* List of objects loaded into the inferior, used by the probes-
-     based interface.  */
-  struct so_list *solib_list = nullptr;
+  /* List of objects loaded into the inferior per namespace, used by the
+     probes-based interface.
+
+     The namespace is represented by the address of its corresponding
+     r_debug[_ext] object.  We get the namespace id as agrument to the
+     'reloc_complete' probe but we don't get it when scanning the load map
+     on attach.
+
+     The r_debug[_ext] objects may move when ld.so itself moves.  In that
+     case, we expect also the global _r_debug to move so we can detect
+     this and reload everything.  The r_debug[_ext] objects are not
+     expected to move individually.
+
+     The special entry zero is reserved for a linear list to support
+     gdbstubs that do not support namespaces.  */
+  std::map<CORE_ADDR, so_list *> solib_lists;
 };
 
 /* Per-program-space data key.  */
 static const registry<program_space>::key<svr4_info> solib_svr4_pspace_data;
 
+/* Return whether DEBUG_BASE is the default namespace of INFO.  */
+
+static bool
+svr4_is_default_namespace (const svr4_info *info, CORE_ADDR debug_base)
+{
+  return (debug_base == info->debug_base);
+}
+
 /* Free the probes table.  */
 
 static void
@@ -375,18 +407,21 @@ free_probes_table (struct svr4_info *info)
   info->probes_table.reset (nullptr);
 }
 
-/* Free the solib list.  */
+/* Free the solib lists for all namespaces.  */
 
 static void
-free_solib_list (struct svr4_info *info)
+free_solib_lists (svr4_info *info)
 {
-  svr4_free_library_list (&info->solib_list);
-  info->solib_list = NULL;
+  for (const std::pair<CORE_ADDR, so_list *> tuple
+	 : info->solib_lists)
+    svr4_free_library_list (tuple.second);
+
+  info->solib_lists.clear ();
 }
 
 svr4_info::~svr4_info ()
 {
-  free_solib_list (this);
+  free_solib_lists (this);
 }
 
 /* Get the svr4 data for program space PSPACE.  If none is found yet, add it now.
@@ -730,7 +765,7 @@ elf_locate_base (void)
    RT_CONSISTENT.  */
 
 static CORE_ADDR
-solib_svr4_r_map (struct svr4_info *info)
+solib_svr4_r_map (CORE_ADDR debug_base)
 {
   struct link_map_offsets *lmo = svr4_fetch_link_map_offsets ();
   struct type *ptr_type = builtin_type (target_gdbarch ())->builtin_data_ptr;
@@ -738,7 +773,7 @@ solib_svr4_r_map (struct svr4_info *info)
 
   try
     {
-      addr = read_memory_typed_address (info->debug_base + lmo->r_map_offset,
+      addr = read_memory_typed_address (debug_base + lmo->r_map_offset,
 					ptr_type);
     }
   catch (const gdb_exception_error &ex)
@@ -792,6 +827,35 @@ solib_svr4_r_ldsomap (struct svr4_info *info)
 				    ptr_type);
 }
 
+/* Find the next namespace from the r_next field.  */
+
+static CORE_ADDR
+solib_svr4_r_next (CORE_ADDR debug_base)
+{
+  link_map_offsets *lmo = svr4_fetch_link_map_offsets ();
+  type *ptr_type = builtin_type (target_gdbarch ())->builtin_data_ptr;
+  bfd_endian byte_order = type_byte_order (ptr_type);
+  ULONGEST version = 0;
+
+  try
+    {
+      version
+	= read_memory_unsigned_integer (debug_base + lmo->r_version_offset,
+					lmo->r_version_size, byte_order);
+    }
+  catch (const gdb_exception_error &ex)
+    {
+      exception_print (gdb_stderr, ex);
+    }
+
+  /* The r_next field is added with r_version == 2.  */
+  if (version < 2 || lmo->r_next_offset == -1)
+    return 0;
+
+  return read_memory_typed_address (debug_base + lmo->r_next_offset,
+				    ptr_type);
+}
+
 /* On Solaris systems with some versions of the dynamic linker,
    ld.so's l_name pointer points to the SONAME in the string table
    rather than into writable memory.  So that GDB can find shared
@@ -848,7 +912,7 @@ open_symbol_file_object (int from_tty)
     return 0;	/* failed somehow...  */
 
   /* First link map member should be the executable.  */
-  lm = solib_svr4_r_map (info);
+  lm = solib_svr4_r_map (info->debug_base);
   if (lm == 0)
     return 0;	/* failed somehow...  */
 
@@ -882,11 +946,19 @@ open_symbol_file_object (int from_tty)
 
 struct svr4_library_list
 {
-  struct so_list *head, **tailp;
+  /* The tail pointer of the current namespace.  This is internal to XML
+     parsing.  */
+  so_list **tailp;
 
   /* Inferior address of struct link_map used for the main executable.  It is
      NULL if not known.  */
   CORE_ADDR main_lm;
+
+  /* List of objects loaded into the inferior per namespace.  This does
+     not include any default sos.
+
+     See comment on struct svr4_info.solib_lists.  */
+  std::map<CORE_ADDR, so_list *> solib_lists;
 };
 
 /* This module's 'free_objfile' observer.  */
@@ -918,13 +990,11 @@ svr4_clear_so (struct so_list *so)
     li->l_addr_p = 0;
 }
 
-/* Free so_list built so far (called via cleanup).  */
+/* Free so_list built so far.  */
 
 static void
-svr4_free_library_list (void *p_list)
+svr4_free_library_list (so_list *list)
 {
-  struct so_list *list = *(struct so_list **) p_list;
-
   while (list != NULL)
     {
       struct so_list *next = list->next;
@@ -1021,6 +1091,12 @@ svr4_library_list_start_list (struct gdb_xml_parser *parser,
 
   if (main_lm)
     list->main_lm = *(ULONGEST *) main_lm->value.get ();
+
+  /* Older gdbserver do not support namespaces.  We use the special
+     namespace zero for a linear list of libraries.  */
+  so_list **solist = &list->solib_lists[0];
+  *solist = nullptr;
+  list->tailp = solist;
 }
 
 /* The allowed elements and attributes for an XML library list.
@@ -1068,13 +1144,16 @@ static const struct gdb_xml_element svr4_library_list_elements[] =
 static int
 svr4_parse_libraries (const char *document, struct svr4_library_list *list)
 {
-  auto cleanup = make_scope_exit ([&] ()
+  auto cleanup = make_scope_exit ([list] ()
     {
-      svr4_free_library_list (&list->head);
+      for (const std::pair<CORE_ADDR, so_list *> tuple
+	     : list->solib_lists)
+	svr4_free_library_list (tuple.second);
     });
 
-  memset (list, 0, sizeof (*list));
-  list->tailp = &list->head;
+  list->tailp = nullptr;
+  list->main_lm = 0;
+  list->solib_lists.clear ();
   if (gdb_xml_parse_quick (_("target library list"), "library-list-svr4.dtd",
 			   svr4_library_list_elements, document, list) == 0)
     {
@@ -1140,7 +1219,7 @@ svr4_default_sos (svr4_info *info)
   newobj->lm_info = li;
 
   /* Nothing will ever check the other fields if we set l_addr_p.  */
-  li->l_addr = info->debug_loader_offset;
+  li->l_addr = li->l_addr_inferior = info->debug_loader_offset;
   li->l_addr_p = 1;
 
   strncpy (newobj->so_name, info->debug_loader_name, SO_NAME_MAX_PATH_SIZE - 1);
@@ -1231,17 +1310,18 @@ svr4_read_so_list (svr4_info *info, CORE_ADDR lm, CORE_ADDR prev_lm,
 /* Read the full list of currently loaded shared objects directly
    from the inferior, without referring to any libraries read and
    stored by the probes interface.  Handle special cases relating
-   to the first elements of the list.  */
+   to the first elements of the list in default namespace.  */
 
-static struct so_list *
+static void
 svr4_current_sos_direct (struct svr4_info *info)
 {
   CORE_ADDR lm;
-  struct so_list *head = NULL;
-  struct so_list **link_ptr = &head;
-  int ignore_first;
+  bool ignore_first;
   struct svr4_library_list library_list;
 
+  /* Remove any old libraries.  We're going to read them back in again.  */
+  free_solib_lists (info);
+
   /* Fall back to manual examination of the target if the packet is not
      supported or gdbserver failed to find DT_DEBUG.  gdb.server/solib-list.exp
      tests a case where gdbserver cannot find the shared libraries list while
@@ -1257,49 +1337,114 @@ svr4_current_sos_direct (struct svr4_info *info)
       if (library_list.main_lm)
 	info->main_lm_addr = library_list.main_lm;
 
-      return library_list.head ? library_list.head : svr4_default_sos (info);
+      /* Remove an empty special zero namespace so we know that when there
+	 is one, it is actually used, and we have a flat list without
+	 namespace information.  */
+      if ((library_list.solib_lists.find (0)
+	   != library_list.solib_lists.end ())
+	  && (library_list.solib_lists[0] == nullptr))
+	library_list.solib_lists.erase (0);
+
+      /* Replace the (empty) solib_lists in INFO with the one generated
+	 from the target.  We don't want to copy it on assignment and then
+	 delete the original afterwards, so let's just swap the
+	 internals.  */
+      std::swap (info->solib_lists, library_list.solib_lists);
+      return;
     }
 
   /* If we can't find the dynamic linker's base structure, this
      must not be a dynamically linked executable.  Hmm.  */
   info->debug_base = elf_locate_base ();
   if (info->debug_base == 0)
-    return svr4_default_sos (info);
+    return;
 
   /* Assume that everything is a library if the dynamic loader was loaded
      late by a static executable.  */
   if (current_program_space->exec_bfd ()
       && bfd_get_section_by_name (current_program_space->exec_bfd (),
 				  ".dynamic") == NULL)
-    ignore_first = 0;
+    ignore_first = false;
   else
-    ignore_first = 1;
+    ignore_first = true;
 
-  auto cleanup = make_scope_exit ([&] ()
+  auto cleanup = make_scope_exit ([info] ()
     {
-      svr4_free_library_list (&head);
+      free_solib_lists (info);
     });
 
-  /* Walk the inferior's link map list, and build our list of
-     `struct so_list' nodes.  */
-  lm = solib_svr4_r_map (info);
-  if (lm)
-    svr4_read_so_list (info, lm, 0, &link_ptr, ignore_first);
+  /* Collect the sos in each namespace.  */
+  CORE_ADDR debug_base = info->debug_base;
+  for (; debug_base != 0;
+       ignore_first = false, debug_base = solib_svr4_r_next (debug_base))
+    {
+      /* Walk the inferior's link map list, and build our so_list list.  */
+      lm = solib_svr4_r_map (debug_base);
+      if (lm != 0)
+	{
+	  so_list **sos = &info->solib_lists[debug_base];
+	  *sos = nullptr;
+
+	  svr4_read_so_list (info, lm, 0, &sos, ignore_first);
+	}
+    }
 
   /* On Solaris, the dynamic linker is not in the normal list of
      shared objects, so make sure we pick it up too.  Having
      symbol information for the dynamic linker is quite crucial
-     for skipping dynamic linker resolver code.  */
-  lm = solib_svr4_r_ldsomap (info);
-  if (lm)
-    svr4_read_so_list (info, lm, 0, &link_ptr, 0);
+     for skipping dynamic linker resolver code.
+
+     Note that we interpret the ldsomap load map address as 'virtual'
+     r_debug object.  If we added it to the default namespace (as it was),
+     we would probably run into inconsistencies with the load map's
+     prev/next links (I wonder if we did).  */
+  debug_base = solib_svr4_r_ldsomap (info);
+  if (debug_base != 0)
+    {
+      /* Add the dynamic linker's namespace unless we already did.  */
+      if (info->solib_lists.find (debug_base) == info->solib_lists.end ())
+	{
+	  so_list **sos = &info->solib_lists[debug_base];
+	  *sos = nullptr;
+	  svr4_read_so_list (info, debug_base, 0, &sos, 0);
+	}
+    }
 
   cleanup.release ();
+}
+
+/* Collect sos read and stored by the probes interface.  */
+
+static so_list *
+svr4_collect_probes_sos (svr4_info *info)
+{
+  so_list *sos = nullptr;
+  so_list **pnext = &sos;
+
+  for (const std::pair<CORE_ADDR, so_list *> tuple
+	 : info->solib_lists)
+    {
+      so_list *solist = tuple.second;
+
+      /* Allow the linker to report empty namespaces.  */
+      if (solist == nullptr)
+	continue;
+
+      *pnext = svr4_copy_library_list (solist);
+
+      /* Update PNEXT to point to the next member of the last element.  */
+      gdb_assert (*pnext != nullptr);
+      for (;;)
+	{
+	  so_list *next = *pnext;
+	  if (next == nullptr)
+	    break;
 
-  if (head == NULL)
-    return svr4_default_sos (info);
+	  pnext = &next->next;
+	}
+    }
 
-  return head;
+  return sos;
 }
 
 /* Implement the main part of the "current_sos" target_so_ops
@@ -1308,13 +1453,26 @@ svr4_current_sos_direct (struct svr4_info *info)
 static struct so_list *
 svr4_current_sos_1 (svr4_info *info)
 {
-  /* If the solib list has been read and stored by the probes
-     interface then we return a copy of the stored list.  */
-  if (info->solib_list != NULL)
-    return svr4_copy_library_list (info->solib_list);
+  so_list *sos = nullptr;
+
+  /* If we're using the probes interface, we can use the cache as it will
+     be maintained by probe update/reload actions.  */
+  if (info->probes_table != nullptr)
+    sos = svr4_collect_probes_sos (info);
 
-  /* Otherwise obtain the solib list directly from the inferior.  */
-  return svr4_current_sos_direct (info);
+  /* If we're not using the probes interface or if we didn't cache
+     anything, read the sos to fill the cache, then collect them from the
+     cache.  */
+  if (sos == nullptr)
+    {
+      svr4_current_sos_direct (info);
+
+      sos = svr4_collect_probes_sos (info);
+      if (sos == nullptr)
+	sos = svr4_default_sos (info);
+    }
+
+  return sos;
 }
 
 /* Implement the "current_sos" target_so_ops method.  */
@@ -1648,8 +1806,7 @@ solib_event_probe_action (struct probe_and_action *pa)
 static int
 solist_update_full (struct svr4_info *info)
 {
-  free_solib_list (info);
-  info->solib_list = svr4_current_sos_direct (info);
+  svr4_current_sos_direct (info);
 
   return 1;
 }
@@ -1660,29 +1817,51 @@ solist_update_full (struct svr4_info *info)
    failure.  */
 
 static int
-solist_update_incremental (struct svr4_info *info, CORE_ADDR lm)
+solist_update_incremental (svr4_info *info, CORE_ADDR debug_base,
+			   CORE_ADDR lm)
 {
-  struct so_list *tail;
-  CORE_ADDR prev_lm;
-
-  /* svr4_current_sos_direct contains logic to handle a number of
-     special cases relating to the first elements of the list.  To
-     avoid duplicating this logic we defer to solist_update_full
-     if the list is empty.  */
-  if (info->solib_list == NULL)
-    return 0;
-
   /* Fall back to a full update if we are using a remote target
      that does not support incremental transfers.  */
   if (info->using_xfer && !target_augmented_libraries_svr4_read ())
     return 0;
 
-  /* Walk to the end of the list.  */
-  for (tail = info->solib_list; tail->next != NULL; tail = tail->next)
-    /* Nothing.  */;
+  /* Fall back to a full update if we used the special namespace zero.  We
+     wouldn't be able to find the last item in the DEBUG_BASE namespace
+     and hence get the prev link wrong.  */
+  if (info->solib_lists.find (0) != info->solib_lists.end ())
+    return 0;
+
+  /* Ensure that the element is actually initialized.  */
+  if (info->solib_lists.find (debug_base) == info->solib_lists.end ())
+    info->solib_lists[debug_base] = nullptr;
+
+  so_list **psolist = &info->solib_lists[debug_base];
+  so_list **pnext = nullptr;
+  so_list *solist = *psolist;
+  CORE_ADDR prev_lm;
+
+  if (solist == nullptr)
+    {
+      /* svr4_current_sos_direct contains logic to handle a number of
+	 special cases relating to the first elements of the list in
+	 default namespace.  To avoid duplicating this logic we defer to
+	 solist_update_full in this case.  */
+      if (svr4_is_default_namespace (info, debug_base))
+	return 0;
+
+      prev_lm = 0;
+      pnext = psolist;
+    }
+  else
+    {
+      /* Walk to the end of the list.  */
+      for (; solist->next != nullptr; solist = solist->next)
+	/* Nothing.  */;
 
-  lm_info_svr4 *li = (lm_info_svr4 *) tail->lm_info;
-  prev_lm = li->lm_addr;
+      lm_info_svr4 *li = (lm_info_svr4 *) solist->lm_info;
+      prev_lm = li->lm_addr;
+      pnext = &solist->next;
+    }
 
   /* Read the new objects.  */
   if (info->using_xfer)
@@ -1696,17 +1875,38 @@ solist_update_incremental (struct svr4_info *info, CORE_ADDR lm)
       if (!svr4_current_sos_via_xfer_libraries (&library_list, annex))
 	return 0;
 
-      tail->next = library_list.head;
+      /* Get the so list from the target.  We replace the list in the
+         target response so we can easily check that the response only
+         covers one namespace.
+
+	 We expect gdbserver to provide updates for the namespace that
+	 contains LM, which whould be this namespace...  */
+      so_list *sos = nullptr;
+      if (library_list.solib_lists.find (debug_base)
+	  != library_list.solib_lists.end ())
+	std::swap (sos, library_list.solib_lists[debug_base]);
+      if (sos == nullptr)
+	{
+	  /* ...or for the special zero namespace for earlier versions...  */
+	  if (library_list.solib_lists.find (0)
+	      != library_list.solib_lists.end ())
+	    std::swap (sos, library_list.solib_lists[0]);
+	}
+
+      /* ...but nothing else.  */
+      for (const std::pair<CORE_ADDR, so_list *> tuple
+	     : library_list.solib_lists)
+	gdb_assert (tuple.second == nullptr);
+
+      *pnext = sos;
     }
   else
     {
-      struct so_list **link = &tail->next;
-
       /* IGNORE_FIRST may safely be set to zero here because the
 	 above check and deferral to solist_update_full ensures
 	 that this call to svr4_read_so_list will never see the
 	 first element.  */
-      if (!svr4_read_so_list (info, lm, prev_lm, &link, 0))
+      if (!svr4_read_so_list (info, lm, prev_lm, &pnext, 0))
 	return 0;
     }
 
@@ -1724,7 +1924,7 @@ disable_probes_interface (svr4_info *info)
 	     "Reverting to original interface."));
 
   free_probes_table (info);
-  free_solib_list (info);
+  free_solib_lists (info);
 }
 
 /* Update the solib list as appropriate when using the
@@ -1798,8 +1998,16 @@ svr4_handle_solib_event (void)
     if (debug_base == 0)
       return;
 
-    /* Always locate the debug struct, in case it moved.  */
-    info->debug_base = elf_locate_base ();
+    /* If the global _r_debug object moved, we need to reload everything
+       since we cannot identify namespaces (by the location of their
+       r_debug_ext object) anymore.  */
+    CORE_ADDR global_debug_base = elf_locate_base ();
+    if (global_debug_base != info->debug_base)
+      {
+	info->debug_base = global_debug_base;
+	action = FULL_RELOAD;
+      }
+
     if (info->debug_base == 0)
       {
 	/* It's possible for the reloc_complete probe to be triggered before
@@ -1823,13 +2031,6 @@ svr4_handle_solib_event (void)
 	  return;
       }
 
-    /* GDB does not currently support libraries loaded via dlmopen
-       into namespaces other than the initial one.  We must ignore
-       any namespace other than the initial namespace here until
-       support for this is added to GDB.  */
-    if (debug_base != info->debug_base)
-      action = DO_NOTHING;
-
     if (action == UPDATE_OR_RELOAD)
       {
 	try
@@ -1855,7 +2056,7 @@ svr4_handle_solib_event (void)
 
   if (action == UPDATE_OR_RELOAD)
     {
-      if (!solist_update_incremental (info, lm))
+      if (!solist_update_incremental (info, debug_base, lm))
 	action = FULL_RELOAD;
     }
 
@@ -2090,7 +2291,7 @@ enable_break (struct svr4_info *info, int from_tty)
 
   solib_add (NULL, from_tty, auto_solib_add);
   sym_addr = 0;
-  if (info->debug_base && solib_svr4_r_map (info) != 0)
+  if (info->debug_base && solib_svr4_r_map (info->debug_base) != 0)
     sym_addr = solib_svr4_r_brk (info);
 
   if (sym_addr != 0)
@@ -2884,7 +3085,7 @@ svr4_solib_create_inferior_hook (int from_tty)
 
   /* Clear the probes-based interface's state.  */
   free_probes_table (info);
-  free_solib_list (info);
+  free_solib_lists (info);
 
   /* Relocate the main executable if necessary.  */
   svr4_relocate_main_executable ();
@@ -3033,6 +3234,7 @@ svr4_ilp32_fetch_link_map_offsets (void)
       lmo.r_map_offset = 4;
       lmo.r_brk_offset = 8;
       lmo.r_ldsomap_offset = 20;
+      lmo.r_next_offset = -1;
 
       /* Everything we need is in the first 20 bytes.  */
       lmo.link_map_size = 20;
@@ -3064,6 +3266,7 @@ svr4_lp64_fetch_link_map_offsets (void)
       lmo.r_map_offset = 8;
       lmo.r_brk_offset = 16;
       lmo.r_ldsomap_offset = 40;
+      lmo.r_next_offset = -1;
 
       /* Everything we need is in the first 40 bytes.  */
       lmo.link_map_size = 40;
diff --git a/gdb/solib-svr4.h b/gdb/solib-svr4.h
index 4a6880225e2..49f3d048dd9 100644
--- a/gdb/solib-svr4.h
+++ b/gdb/solib-svr4.h
@@ -66,6 +66,9 @@ struct link_map_offsets
     /* Offset of r_debug.r_ldsomap.  */
     int r_ldsomap_offset;
 
+    /* Offset of r_debug_extended.r_next.  */
+    int r_next_offset;
+
     /* Size of struct link_map (or equivalent), or at least enough of it
        to be able to obtain the fields below.  */
     int link_map_size;
diff --git a/gdb/testsuite/gdb.base/dlmopen-lib.c b/gdb/testsuite/gdb.base/dlmopen-lib.c
new file mode 100644
index 00000000000..616bf97b4f5
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen-lib.c
@@ -0,0 +1,25 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2022 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+__attribute__((visibility ("default")))
+int
+inc (int n)
+{
+  return n + 1;  /* bp.inc.  */
+}
diff --git a/gdb/testsuite/gdb.base/dlmopen.c b/gdb/testsuite/gdb.base/dlmopen.c
new file mode 100644
index 00000000000..2dc2f2e6d03
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen.c
@@ -0,0 +1,65 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <stddef.h>
+#include <assert.h>
+#include <unistd.h>
+
+volatile int wait_for_gdb = 1;
+
+int
+main (void)
+{
+  void *handle[4];
+  int (*fun) (int);
+  Lmid_t lmid;
+  int dl;
+
+  handle[0] = dlmopen (LM_ID_NEWLM, DSO1_NAME, RTLD_LAZY | RTLD_LOCAL);
+  assert (handle[0] != NULL);
+
+  dlinfo (handle[0], RTLD_DI_LMID, &lmid);
+
+  handle[1] = dlopen (DSO1_NAME, RTLD_LAZY | RTLD_LOCAL);
+  assert (handle[1] != NULL);
+
+  handle[2] = dlmopen (LM_ID_NEWLM, DSO1_NAME, RTLD_LAZY | RTLD_LOCAL);
+  assert (handle[2] != NULL);
+
+  handle[3] = dlmopen (lmid, DSO2_NAME, RTLD_LAZY | RTLD_LOCAL);
+  assert (handle[3] != NULL);
+
+  alarm (20);
+  while (wait_for_gdb != 0)
+    usleep (1);
+
+  for (dl = 0; dl < 4; ++dl)
+    {
+      fun = dlsym (handle[dl], "inc");
+      assert (fun != NULL);
+
+      fun (42);
+
+      dlclose (handle[dl]);
+    }
+
+  return 0;  /* bp.main  */
+}
diff --git a/gdb/testsuite/gdb.base/dlmopen.exp b/gdb/testsuite/gdb.base/dlmopen.exp
new file mode 100644
index 00000000000..8e86d5d5ccc
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen.exp
@@ -0,0 +1,150 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 2021-2022 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# Test shared libraries loaded into different namespaces with dlmopen().
+#
+# We test that GDB shows the correct number of instances of the libraries
+# the test loaded while unloading them one-by-one.
+
+if { [skip_dlmopen_tests] } {
+    unsupported "target does not support dlmopen debugging"
+    return -1
+}
+
+standard_testfile
+
+set basename_lib dlmopen-lib
+set srcfile_lib $srcdir/$subdir/$basename_lib.c
+set binfile_lib1 [standard_output_file $basename_lib.1.so]
+set binfile_lib2 [standard_output_file $basename_lib.2.so]
+
+if { [gdb_compile_shlib $srcfile_lib $binfile_lib1 {debug}] != "" } {
+    untested "failed to prepare shlib"
+    return -1
+}
+
+if { [gdb_compile_shlib $srcfile_lib $binfile_lib2 {debug}] != "" } {
+    untested "failed to prepare shlib"
+    return -1
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  [list additional_flags=-DDSO1_NAME=\"$binfile_lib1\" \
+	       additional_flags=-DDSO2_NAME=\"$binfile_lib2\" \
+	       libs=-ldl debug]] } {
+    return -1
+}
+
+if { ![runto_main] } {
+    return -1
+}
+
+# Check that 'info shared' show NUM occurrences of DSO.
+proc check_dso_count { dso num } {
+    global gdb_prompt hex
+
+    set count 0
+    gdb_test_multiple "info shared" "info shared" {
+	-re "$hex  $hex  Yes \[^\r\n\]*$dso\r\n" {
+	    # use longer form so debug remote does not interfere
+	    set count [expr $count + 1]
+	    exp_continue
+	}
+	-re "$gdb_prompt " {
+	    verbose -log "library: $dso, expected: $num, found: $count"
+	    gdb_assert {$count == $num} "$gdb_test_name"
+	}
+    }
+}
+
+# The DSO part of the test.  We run it once per DSO call.
+proc test_dlmopen_one { ndso1 ndso2 } {
+    global srcfile_lib srcfile_lib basename_lib bp_inc
+
+    # Try to reach the breakpoint in the dynamically loaded library.
+    gdb_continue_to_breakpoint "cont to bp.inc" \
+	".*$srcfile_lib:$bp_inc\r\n.*"
+
+    # We opened all DSOs initially and close them one by one.
+    with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so $ndso1 }
+    with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so $ndso2 }
+
+    # This might help debugging.
+    gdb_test "info breakpoints" ".*"
+    gdb_test "print \$pc" ".*"
+}
+
+# The actual test.  We run it twice.
+proc test_dlmopen {} {
+    global srcfile basename_lib bp_main
+
+    with_test_prefix "dlmopen 1" { test_dlmopen_one 3 1 }
+    with_test_prefix "dlmopen 2" { test_dlmopen_one 2 1 }
+    with_test_prefix "dlmopen 3" { test_dlmopen_one 1 1 }
+    with_test_prefix "dlmopen 4" { test_dlmopen_one 0 1 }
+
+    with_test_prefix "main" {
+	# Try to reach the breakpoint in the dynamically loaded library.
+	gdb_continue_to_breakpoint "cont to bp.main" \
+	    ".*$srcfile:$bp_main\r\n.*"
+
+	# The library should not be listed.
+	with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so 0 }
+	with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so 0 }
+    }
+}
+
+# Remove the pause.  We only need it for the attach test.
+gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
+
+# Break in the to-be-loaded library and at the end of main.
+set bp_inc [gdb_get_line_number "bp.inc" $srcfile_lib]
+set bp_main [gdb_get_line_number "bp.main" $srcfile]
+
+delete_breakpoints
+gdb_breakpoint $srcfile_lib:$bp_inc allow-pending
+gdb_breakpoint $srcfile:$bp_main
+
+test_dlmopen
+
+# Try the same again when attaching after dlmopen().
+if { ![can_spawn_for_attach] } {
+    unsupported "target does not support attach"
+    return -1
+}
+
+clean_restart $binfile
+
+# Start the test program.
+set test_spawn_id [spawn_wait_for_attach $binfile]
+set testpid [spawn_id_get_pid $test_spawn_id]
+
+# Attach.
+gdb_test "attach $testpid" "Attaching to program.*, process $testpid.*"
+
+with_test_prefix "attach" {
+    # Remove the pause.  We no longer need it.
+    gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
+
+    # Set the same breakpoints again.  This time, however, we do not allow the
+    # breakpoint to be pending since the library has already been loaded.
+    gdb_breakpoint $srcfile_lib:$bp_inc
+    gdb_breakpoint $srcfile:$bp_main
+
+    test_dlmopen
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 36bcfacfdd0..c510ab25365 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -2512,6 +2512,102 @@ proc skip_shlib_tests {} {
     return 1
 }
 
+# Return 1 if we should skip dlmopen tests, 0 if we should not.
+
+gdb_caching_proc skip_dlmopen_tests {
+    global srcdir subdir gdb_prompt inferior_exited_re
+
+    # We need shared library support.
+    if { [skip_shlib_tests] } {
+	return 1
+    }
+
+    set me "skip_dlmopen_tests"
+    set lib {
+	int foo (void) {
+	    return 42;
+	}
+    }
+    set src {
+	#define _GNU_SOURCE
+	#include <dlfcn.h>
+	#include <link.h>
+	#include <stdio.h>
+	#include <errno.h>
+
+	int  main (void) {
+	    struct r_debug *r_debug;
+	    ElfW(Dyn) *dyn;
+	    void *handle;
+
+	    /* The version is kept at 1 until we create a new namespace.  */
+	    handle = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
+	    if (!handle) {
+		printf ("dlmopen failed: %s.\n", dlerror ());
+		return 1;
+	    }
+
+	    r_debug = 0;
+	    /* Taken from /usr/include/link.h.  */
+	    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;
+
+	    if (!r_debug) {
+	        printf ("r_debug not found.\n");
+		return 1;
+	    }
+	    if (r_debug->r_version < 2) {
+	        printf ("dlmopen debug not supported.\n");
+		return 1;
+	    }
+	    printf ("dlmopen debug supported.\n");
+	    return 0;
+	}
+    }
+
+    set libsrc [standard_temp_file "libfoo.c"]
+    set libout [standard_temp_file "libfoo.so"]
+    gdb_produce_source $libsrc $lib
+
+    if { [gdb_compile_shlib $libsrc $libout {debug}] != "" } {
+	verbose -log "failed to build library"
+	return 1
+    }
+    if { ![gdb_simple_compile $me $src executable \
+	       [list shlib_load debug \
+		    additional_flags=-DDSO_NAME=\"$libout\"]] } {
+	verbose -log "failed to build executable"
+        return 1
+    }
+
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+    gdb_load $obj
+
+    if { [gdb_run_cmd] != 0 } {
+	verbose -log "failed to start skip test"
+	return 1
+    }
+    gdb_expect {
+        -re "$inferior_exited_re normally.*${gdb_prompt} $" {
+            set skip_dlmopen_tests 0
+        }
+        -re "$inferior_exited_re with code.*${gdb_prompt} $" {
+            set skip_dlmopen_tests 1
+        }
+        default {
+	    warning "\n$me: default case taken"
+            set skip_dlmopen_tests 1
+        }
+    }
+    gdb_exit
+
+    verbose "$me:  returning $skip_dlmopen_tests" 2
+    return $skip_dlmopen_tests
+}
+
 # Return 1 if we should skip tui related tests.
 
 proc skip_tui_tests {} {
diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 2f71360d3bd..af3c4b35cd9 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -6443,6 +6443,9 @@ struct link_map_offsets
     /* Offset and size of r_debug.r_map.  */
     int r_map_offset;
 
+    /* Offset of r_debug_extended.r_next.  */
+    int r_next_offset;
+
     /* Offset to l_addr field in struct link_map.  */
     int l_addr_offset;
 
@@ -6459,6 +6462,98 @@ struct link_map_offsets
     int l_prev_offset;
   };
 
+static const link_map_offsets lmo_32bit_offsets =
+  {
+    0,     /* r_version offset.  */
+    4,     /* r_debug.r_map offset.  */
+    20,    /* r_debug_extended.r_next.  */
+    0,     /* l_addr offset in link_map.  */
+    4,     /* l_name offset in link_map.  */
+    8,     /* l_ld offset in link_map.  */
+    12,    /* l_next offset in link_map.  */
+    16     /* l_prev offset in link_map.  */
+  };
+
+static const link_map_offsets lmo_64bit_offsets =
+  {
+    0,     /* r_version offset.  */
+    8,     /* r_debug.r_map offset.  */
+    40,    /* r_debug_extended.r_next.  */
+    0,     /* l_addr offset in link_map.  */
+    8,     /* l_name offset in link_map.  */
+    16,    /* l_ld offset in link_map.  */
+    24,    /* l_next offset in link_map.  */
+    32     /* l_prev offset in link_map.  */
+  };
+
+/* Get the loaded shared libraries from one namespace.  */
+
+static void
+read_link_map (std::string &document, CORE_ADDR lm_addr, CORE_ADDR lm_prev,
+	       int ptr_size, const link_map_offsets *lmo, bool ignore_first,
+	       int &header_done)
+{
+  CORE_ADDR l_name, l_addr, l_ld, l_next, l_prev;
+
+  while (lm_addr
+	 && read_one_ptr (lm_addr + lmo->l_name_offset,
+			  &l_name, ptr_size) == 0
+	 && read_one_ptr (lm_addr + lmo->l_addr_offset,
+			  &l_addr, ptr_size) == 0
+	 && read_one_ptr (lm_addr + lmo->l_ld_offset,
+			  &l_ld, ptr_size) == 0
+	 && read_one_ptr (lm_addr + lmo->l_prev_offset,
+			  &l_prev, ptr_size) == 0
+	 && read_one_ptr (lm_addr + lmo->l_next_offset,
+			  &l_next, ptr_size) == 0)
+    {
+      unsigned char libname[PATH_MAX];
+
+      if (lm_prev != l_prev)
+	{
+	  warning ("Corrupted shared library list: 0x%s != 0x%s",
+		   paddress (lm_prev), paddress (l_prev));
+	  break;
+	}
+
+      /* Ignore the first entry even if it has valid name as the first entry
+	 corresponds to the main executable.  The first entry should not be
+	 skipped if the dynamic loader was loaded late by a static executable
+	 (see solib-svr4.c parameter ignore_first).  But in such case the main
+	 executable does not have PT_DYNAMIC present and this function already
+	 exited above due to failed get_r_debug.  */
+      if (ignore_first && lm_prev == 0)
+	string_appendf (document, " main-lm=\"0x%s\"", paddress (lm_addr));
+      else
+	{
+	  /* Not checking for error because reading may stop before
+	     we've got PATH_MAX worth of characters.  */
+	  libname[0] = '\0';
+	  linux_read_memory (l_name, libname, sizeof (libname) - 1);
+	  libname[sizeof (libname) - 1] = '\0';
+	  if (libname[0] != '\0')
+	    {
+	      if (!header_done)
+		{
+		  /* Terminate `<library-list-svr4'.  */
+		  document += '>';
+		  header_done = 1;
+		}
+
+	      string_appendf (document, "<library name=\"");
+	      xml_escape_text_append (&document, (char *) libname);
+	      string_appendf (document, "\" lm=\"0x%s\" l_addr=\"0x%s\" "
+			      "l_ld=\"0x%s\"/>",
+			      paddress (lm_addr), paddress (l_addr),
+			      paddress (l_ld));
+	    }
+	}
+
+      lm_prev = lm_addr;
+      lm_addr = l_next;
+    }
+}
+
 /* Construct qXfer:libraries-svr4:read reply.  */
 
 int
@@ -6470,33 +6565,8 @@ linux_process_target::qxfer_libraries_svr4 (const char *annex,
   struct process_info_private *const priv = current_process ()->priv;
   char filename[PATH_MAX];
   int pid, is_elf64;
-
-  static const struct link_map_offsets lmo_32bit_offsets =
-    {
-      0,     /* r_version offset. */
-      4,     /* r_debug.r_map offset.  */
-      0,     /* l_addr offset in link_map.  */
-      4,     /* l_name offset in link_map.  */
-      8,     /* l_ld offset in link_map.  */
-      12,    /* l_next offset in link_map.  */
-      16     /* l_prev offset in link_map.  */
-    };
-
-  static const struct link_map_offsets lmo_64bit_offsets =
-    {
-      0,     /* r_version offset. */
-      8,     /* r_debug.r_map offset.  */
-      0,     /* l_addr offset in link_map.  */
-      8,     /* l_name offset in link_map.  */
-      16,    /* l_ld offset in link_map.  */
-      24,    /* l_next offset in link_map.  */
-      32     /* l_prev offset in link_map.  */
-    };
-  const struct link_map_offsets *lmo;
   unsigned int machine;
-  int ptr_size;
   CORE_ADDR lm_addr = 0, lm_prev = 0;
-  CORE_ADDR l_name, l_addr, l_ld, l_next, l_prev;
   int header_done = 0;
 
   if (writebuf != NULL)
@@ -6507,8 +6577,18 @@ linux_process_target::qxfer_libraries_svr4 (const char *annex,
   pid = lwpid_of (current_thread);
   xsnprintf (filename, sizeof filename, "/proc/%d/exe", pid);
   is_elf64 = elf_64_file_p (filename, &machine);
-  lmo = is_elf64 ? &lmo_64bit_offsets : &lmo_32bit_offsets;
-  ptr_size = is_elf64 ? 8 : 4;
+  const link_map_offsets *lmo;
+  int ptr_size;
+  if (is_elf64)
+    {
+      lmo = &lmo_64bit_offsets;
+      ptr_size = 8;
+    }
+  else
+    {
+      lmo = &lmo_32bit_offsets;
+      ptr_size = 4;
+    }
 
   while (annex[0] != '\0')
     {
@@ -6537,95 +6617,74 @@ linux_process_target::qxfer_libraries_svr4 (const char *annex,
       annex = decode_address_to_semicolon (addrp, sep + 1);
     }
 
-  if (lm_addr == 0)
+  std::string document = "<library-list-svr4 version=\"1.0\"";
+
+  /* When the starting LM_ADDR is passed in the annex, only traverse that
+     namespace.
+
+     Otherwise, start with R_DEBUG and traverse all namespaces we find.  */
+  if (lm_addr != 0)
+    read_link_map (document, lm_addr, lm_prev, ptr_size, lmo, false,
+		   header_done);
+  else
     {
-      int r_version = 0;
+      if (lm_prev != 0)
+	warning ("ignoring prev=0x%s without start", paddress (lm_prev));
 
-      if (priv->r_debug == 0)
-	priv->r_debug = get_r_debug (pid, is_elf64);
+      CORE_ADDR r_debug = priv->r_debug;
+      if (r_debug == 0)
+	r_debug = priv->r_debug = get_r_debug (pid, is_elf64);
 
       /* We failed to find DT_DEBUG.  Such situation will not change
 	 for this inferior - do not retry it.  Report it to GDB as
 	 E01, see for the reasons at the GDB solib-svr4.c side.  */
-      if (priv->r_debug == (CORE_ADDR) -1)
+      if (r_debug == (CORE_ADDR) -1)
 	return -1;
 
-      if (priv->r_debug != 0)
+      bool ignore_first = true;
+      while (r_debug != 0)
 	{
-	  if (linux_read_memory (priv->r_debug + lmo->r_version_offset,
+	  int r_version = 0;
+	  if (linux_read_memory (r_debug + lmo->r_version_offset,
 				 (unsigned char *) &r_version,
-				 sizeof (r_version)) != 0
-	      || r_version < 1)
+				 sizeof (r_version)) != 0)
+	    {
+	      warning ("unable to read r_version from 0x%s",
+		       paddress (r_debug + lmo->r_version_offset));
+	      break;
+	    }
+
+	  if (r_version < 1)
 	    {
 	      warning ("unexpected r_debug version %d", r_version);
+	      break;
 	    }
-	  else if (read_one_ptr (priv->r_debug + lmo->r_map_offset,
-				 &lm_addr, ptr_size) != 0)
+
+	  if (read_one_ptr (r_debug + lmo->r_map_offset, &lm_addr,
+			    ptr_size) != 0)
 	    {
-	      warning ("unable to read r_map from 0x%lx",
-		       (long) priv->r_debug + lmo->r_map_offset);
+	      warning ("unable to read r_map from 0x%s",
+		       paddress (r_debug + lmo->r_map_offset));
+	      break;
 	    }
-	}
-    }
 
-  std::string document = "<library-list-svr4 version=\"1.0\"";
+	  read_link_map (document, lm_addr, 0, ptr_size, lmo,
+			 ignore_first, header_done);
 
-  while (lm_addr
-	 && read_one_ptr (lm_addr + lmo->l_name_offset,
-			  &l_name, ptr_size) == 0
-	 && read_one_ptr (lm_addr + lmo->l_addr_offset,
-			  &l_addr, ptr_size) == 0
-	 && read_one_ptr (lm_addr + lmo->l_ld_offset,
-			  &l_ld, ptr_size) == 0
-	 && read_one_ptr (lm_addr + lmo->l_prev_offset,
-			  &l_prev, ptr_size) == 0
-	 && read_one_ptr (lm_addr + lmo->l_next_offset,
-			  &l_next, ptr_size) == 0)
-    {
-      unsigned char libname[PATH_MAX];
+	  if (r_version < 2)
+	    break;
 
-      if (lm_prev != l_prev)
-	{
-	  warning ("Corrupted shared library list: 0x%lx != 0x%lx",
-		   (long) lm_prev, (long) l_prev);
-	  break;
-	}
+	  /* Only applies to the default namespace.  */
+	  ignore_first = false;
 
-      /* Ignore the first entry even if it has valid name as the first entry
-	 corresponds to the main executable.  The first entry should not be
-	 skipped if the dynamic loader was loaded late by a static executable
-	 (see solib-svr4.c parameter ignore_first).  But in such case the main
-	 executable does not have PT_DYNAMIC present and this function already
-	 exited above due to failed get_r_debug.  */
-      if (lm_prev == 0)
-	string_appendf (document, " main-lm=\"0x%lx\"", (unsigned long) lm_addr);
-      else
-	{
-	  /* Not checking for error because reading may stop before
-	     we've got PATH_MAX worth of characters.  */
-	  libname[0] = '\0';
-	  linux_read_memory (l_name, libname, sizeof (libname) - 1);
-	  libname[sizeof (libname) - 1] = '\0';
-	  if (libname[0] != '\0')
+	  if (read_one_ptr (r_debug + lmo->r_next_offset, &r_debug,
+			    ptr_size) != 0)
 	    {
-	      if (!header_done)
-		{
-		  /* Terminate `<library-list-svr4'.  */
-		  document += '>';
-		  header_done = 1;
-		}
-
-	      string_appendf (document, "<library name=\"");
-	      xml_escape_text_append (&document, (char *) libname);
-	      string_appendf (document, "\" lm=\"0x%lx\" "
-			      "l_addr=\"0x%lx\" l_ld=\"0x%lx\"/>",
-			      (unsigned long) lm_addr, (unsigned long) l_addr,
-			      (unsigned long) l_ld);
+	      warning ("unable to read r_next from 0x%s",
+		       paddress (r_debug + lmo->r_next_offset));
+	      break;
 	    }
 	}
-
-      lm_prev = lm_addr;
-      lm_addr = l_next;
     }
 
   if (!header_done)

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2022-10-18 13:24 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-18 13:24 [binutils-gdb] gdb, gdbserver: support dlmopen() Markus Metzger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).