gdb, gdbserver: support dlmopen()
authorMarkus Metzger <markus.t.metzger@intel.com>
Mon, 4 Oct 2021 08:24:35 +0000 (10:24 +0200)
committerMarkus Metzger <markus.t.metzger@intel.com>
Tue, 18 Oct 2022 12:16:08 +0000 (14:16 +0200)
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>
gdb/linux-tdep.c
gdb/mips-fbsd-tdep.c
gdb/mips-netbsd-tdep.c
gdb/solib-svr4.c
gdb/solib-svr4.h
gdb/testsuite/gdb.base/dlmopen-lib.c [new file with mode: 0644]
gdb/testsuite/gdb.base/dlmopen.c [new file with mode: 0644]
gdb/testsuite/gdb.base/dlmopen.exp [new file with mode: 0644]
gdb/testsuite/lib/gdb.exp
gdbserver/linux-low.cc

index dccb45d73a8d9c4dea99b1ac4f6f15525eaf06e9..0a2fced7804baead6050143dc1885c44680785b5 100644 (file)
@@ -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;
index eae53108c038fa67ccd07d23b51e86b51818632e..0de1007d74fca69a755be8c30dc976ecf94372d9 100644 (file)
@@ -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;
index c13e1fd0802901056d86a47423ebcf93367627f9..344ec1fcfd490539a908917d16aae1afbaffe616 100644 (file)
@@ -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;
index 222b9df3c47949e120f97203082c335de583510c..a80e7e30561af7313e6054917cbc0a0e0023d349 100644 (file)
 #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;
index 4a6880225e25401ce6b789d6ca6a3ba6a52ef4be..49f3d048dd9c06cd2a4210e0d280570f8d3d28b5 100644 (file)
@@ -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 (file)
index 0000000..616bf97
--- /dev/null
@@ -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 (file)
index 0000000..2dc2f2e
--- /dev/null
@@ -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 (file)
index 0000000..8e86d5d
--- /dev/null
@@ -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
+}
index 36bcfacfdd022b4cb65334250a07f36ed3180bbb..c510ab253654ae9a5dadcacad994f2222c86a8e9 100644 (file)
@@ -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 {} {
index 2f71360d3bdeebe44b37e5c786e9b53eaadabfd4..af3c4b35cd98fdaa98ad81b5dec5ff602e00b8fa 100644 (file)
@@ -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)