diff --git a/gdb/mi/mi-cmd-file.c b/gdb/mi/mi-cmd-file.c
index 5f63ce4494cb..adc324e64fc8 100644
--- a/gdb/mi/mi-cmd-file.c
+++ b/gdb/mi/mi-cmd-file.c
@@ -163,14 +163,14 @@ mi_cmd_file_list_shared_libraries (const char *command,
   /* Print the table header.  */
   ui_out_emit_list list_emitter (uiout, "shared-libraries");
 
-  for (struct so_list *so : current_program_space->solibs ())
+  for (const so_list &so : current_program_space->solibs ())
     {
-      if (so->so_name[0] == '\0')
+      if (so.so_name[0] == '\0')
 	continue;
-      if (pattern != NULL && !re_exec (so->so_name.c_str ()))
+      if (pattern != NULL && !re_exec (so.so_name.c_str ()))
 	continue;
 
       ui_out_emit_tuple tuple_emitter (uiout, NULL);
-      mi_output_solib_attribs (uiout, *so);
+      mi_output_solib_attribs (uiout, so);
     }
 }
diff --git a/gdb/progspace.c b/gdb/progspace.c
index 555fa79ccce8..4b816b4dac8b 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -132,8 +132,8 @@ void
 program_space::free_all_objfiles ()
 {
   /* Any objfile reference would become stale.  */
-  for (struct so_list *so : current_program_space->solibs ())
-    gdb_assert (so->objfile == NULL);
+  for (const struct so_list &so : current_program_space->solibs ())
+    gdb_assert (so.objfile == NULL);
 
   while (!objfiles_list.empty ())
     objfiles_list.front ()->unlink ();
diff --git a/gdb/progspace.h b/gdb/progspace.h
index 20dfc2e92e83..0a762e6ad5c8 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -28,6 +28,7 @@
 #include "solist.h"
 #include "gdbsupport/next-iterator.h"
 #include "gdbsupport/safe-iterator.h"
+#include "gdbsupport/intrusive_list.h"
 #include <list>
 #include <vector>
 
@@ -253,12 +254,9 @@ struct program_space
      is outside all objfiles in this progspace.  */
   struct objfile *objfile_for_address (CORE_ADDR address);
 
-  /* Return a range adapter for iterating over all the solibs in this
-     program space.  Use it like:
-
-     for (so_list *so : pspace->solibs ()) { ... }  */
-  so_list_range solibs () const
-  { return so_list_range (this->so_list); }
+  /* Return the list of  all the solibs in this program space.  */
+  intrusive_list<struct so_list> &solibs ()
+  { return so_list; }
 
   /* Close and clear exec_bfd.  If we end up with no target sections
      to read memory from, this unpushes the exec_ops target.  */
@@ -361,7 +359,7 @@ struct program_space
 
   /* List of shared objects mapped into this space.  Managed by
      solib.c.  */
-  struct so_list *so_list = NULL;
+  intrusive_list<struct so_list> so_list;
 
   /* Number of calls to solib_add.  */
   unsigned int solib_add_generation = 0;
diff --git a/gdb/solib-aix.c b/gdb/solib-aix.c
index e5e86c49e712..68798e8735ac 100644
--- a/gdb/solib-aix.c
+++ b/gdb/solib-aix.c
@@ -445,21 +445,20 @@ solib_aix_solib_create_inferior_hook (int from_tty)
 
 /* Implement the "current_sos" target_so_ops method.  */
 
-static struct so_list *
-solib_aix_current_sos (void)
+static intrusive_list<so_list>
+solib_aix_current_sos ()
 {
-  struct so_list *start = NULL, *last = NULL;
-  int ix;
-
   gdb::optional<std::vector<lm_info_aix>> &library_list
     = solib_aix_get_library_list (current_inferior (), NULL);
   if (!library_list.has_value ())
-    return NULL;
+    return {};
+
+  intrusive_list<so_list> sos;
 
   /* Build a struct so_list for each entry on the list.
      We skip the first entry, since this is the entry corresponding
      to the main executable, not a shared library.  */
-  for (ix = 1; ix < library_list->size (); ix++)
+  for (int ix = 1; ix < library_list->size (); ix++)
     {
       so_list *new_solib = new so_list;
       std::string so_name;
@@ -488,16 +487,10 @@ solib_aix_current_sos (void)
       new_solib->lm_info = gdb::make_unique<lm_info_aix> (info);
 
       /* Add it to the list.  */
-      if (!start)
-	last = start = new_solib;
-      else
-	{
-	  last->next = new_solib;
-	  last = new_solib;
-	}
+      sos.push_back (*new_solib);
     }
 
-  return start;
+  return sos;
 }
 
 /* Implement the "open_symbol_file_object" target_so_ops method.  */
diff --git a/gdb/solib-darwin.c b/gdb/solib-darwin.c
index 2c62e16d386a..5e3aabbfb831 100644
--- a/gdb/solib-darwin.c
+++ b/gdb/solib-darwin.c
@@ -212,32 +212,31 @@ open_symbol_file_object (int from_tty)
 
 /* Build a list of currently loaded shared objects.  See solib-svr4.c.  */
 
-static struct so_list *
-darwin_current_sos (void)
+static intrusive_list<so_list>
+darwin_current_sos ()
 {
   type *ptr_type
     = builtin_type (current_inferior ()->arch ())->builtin_data_ptr;
   enum bfd_endian byte_order = type_byte_order (ptr_type);
   int ptr_len = ptr_type->length ();
   unsigned int image_info_size;
-  struct so_list *head = NULL;
-  struct so_list *tail = NULL;
-  int i;
   darwin_info *info = get_darwin_info (current_program_space);
 
   /* Be sure image infos are loaded.  */
   darwin_load_image_infos (info);
 
   if (!darwin_dyld_version_ok (info))
-    return NULL;
+    return {};
 
   image_info_size = ptr_len * 3;
 
+  intrusive_list<so_list> sos;
+
   /* Read infos for each solib.
      The first entry was rumored to be the executable itself, but this is not
      true when a large number of shared libraries are used (table expanded ?).
      We now check all entries, but discard executable images.  */
-  for (i = 0; i < info->all_image.count; i++)
+  for (int i = 0; i < info->all_image.count; i++)
     {
       CORE_ADDR iinfo = info->all_image.info + i * image_info_size;
       gdb_byte buf[image_info_size];
@@ -282,16 +281,10 @@ darwin_current_sos (void)
       li->lm_addr = load_addr;
 
       newobj->lm_info = std::move (li);
-
-      if (head == NULL)
-	head = newobj;
-      else
-	tail->next = newobj;
-
-      tail = newobj;
+      sos.push_back (*newobj);
     }
 
-  return head;
+  return sos;
 }
 
 /* Check LOAD_ADDR points to a Mach-O executable header.  Return LOAD_ADDR
diff --git a/gdb/solib-dsbt.c b/gdb/solib-dsbt.c
index d3566b9590db..0c60ad69753d 100644
--- a/gdb/solib-dsbt.c
+++ b/gdb/solib-dsbt.c
@@ -512,14 +512,13 @@ lm_base (void)
    themselves.  The declaration of `struct so_list' says which fields
    we provide values for.  */
 
-static struct so_list *
+static intrusive_list<so_list>
 dsbt_current_sos (void)
 {
   bfd_endian byte_order = gdbarch_byte_order (current_inferior ()->arch ());
   CORE_ADDR lm_addr;
-  struct so_list *sos_head = NULL;
-  struct so_list **sos_next_ptr = &sos_head;
   dsbt_info *info = get_dsbt_info (current_program_space);
+  intrusive_list<so_list> sos;
 
   /* Make sure that the main executable has been relocated.  This is
      required in order to find the address of the global offset table,
@@ -616,10 +615,7 @@ dsbt_current_sos (void)
 	      sop->so_original_name = sop->so_name;
 	    }
 
-	  sop->lm_info = std::move (li);
-
-	  *sos_next_ptr = sop;
-	  sos_next_ptr = &sop->next;
+	  sos.push_back (*sop);
 	}
       else
 	{
@@ -630,7 +626,7 @@ dsbt_current_sos (void)
 					  sizeof (lm_buf.l_next), byte_order);
     }
 
-  return sos_head;
+  return sos;
 }
 
 /* Return 1 if PC lies in the dynamic symbol resolution code of the
diff --git a/gdb/solib-frv.c b/gdb/solib-frv.c
index d92c35339523..c391462ad9af 100644
--- a/gdb/solib-frv.c
+++ b/gdb/solib-frv.c
@@ -306,13 +306,12 @@ lm_base (void)
 
 /* Implement the "current_sos" target_so_ops method.  */
 
-static struct so_list *
-frv_current_sos (void)
+static intrusive_list<so_list>
+frv_current_sos ()
 {
   bfd_endian byte_order = gdbarch_byte_order (current_inferior ()->arch ());
   CORE_ADDR lm_addr, mgot;
-  struct so_list *sos_head = NULL;
-  struct so_list **sos_next_ptr = &sos_head;
+  intrusive_list<so_list> sos;
 
   /* Make sure that the main executable has been relocated.  This is
      required in order to find the address of the global offset table,
@@ -399,10 +398,7 @@ frv_current_sos (void)
 	      sop->so_original_name = sop->so_name;
 	    }
 
-	  sop->lm_info = std::move (li);
-
-	  *sos_next_ptr = sop;
-	  sos_next_ptr = &sop->next;
+	  sos.push_back (*sop);
 	}
       else
 	{
@@ -415,7 +411,7 @@ frv_current_sos (void)
 
   enable_break2 ();
 
-  return sos_head;
+  return sos;
 }
 
 
@@ -856,10 +852,10 @@ main_got (void)
 CORE_ADDR
 frv_fdpic_find_global_pointer (CORE_ADDR addr)
 {
-  for (struct so_list *so : current_program_space->solibs ())
+  for (const so_list &so : current_program_space->solibs ())
     {
       int seg;
-      auto *li = gdb::checked_static_cast<lm_info_frv *> (so->lm_info.get ());
+      auto *li = gdb::checked_static_cast<lm_info_frv *> (so.lm_info.get ());
       int_elf32_fdpic_loadmap *map = li->map;
 
       for (seg = 0; seg < map->nsegs; seg++)
@@ -913,12 +909,12 @@ frv_fdpic_find_canonical_descriptor (CORE_ADDR entry_point)
      in list of shared objects.  */
   if (addr == 0)
     {
-      for (struct so_list *so : current_program_space->solibs ())
+      for (const so_list &so : current_program_space->solibs ())
 	{
-	  auto *li = gdb::checked_static_cast<lm_info_frv *> (so->lm_info.get ());
+	  auto *li = gdb::checked_static_cast<lm_info_frv *> (so.lm_info.get ());
 
 	  addr = find_canonical_descriptor_in_load_object
-		   (entry_point, got_value, name, so->abfd.get (), li);
+		   (entry_point, got_value, name, so.abfd.get(), li);
 
 	  if (addr != 0)
 	    break;
@@ -1065,11 +1061,11 @@ frv_fetch_objfile_link_map (struct objfile *objfile)
 
   /* The other link map addresses may be found by examining the list
      of shared libraries.  */
-  for (struct so_list *so : current_program_space->solibs ())
+  for (const so_list &so : current_program_space->solibs ())
     {
-      auto *li = gdb::checked_static_cast<lm_info_frv *> (so->lm_info.get ());
+      auto *li = gdb::checked_static_cast<lm_info_frv *> (so.lm_info.get ());
 
-      if (so->objfile == objfile)
+      if (so.objfile == objfile)
 	return li->lm_addr;
     }
 
diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index bdd458995c21..0dc303b5a651 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -204,11 +204,10 @@ rocm_solib_handle_event ()
 
 /* Create so_list objects from rocm_so objects in SOS.  */
 
-static so_list *
+static intrusive_list<so_list>
 so_list_from_rocm_sos (const std::vector<rocm_so> &sos)
 {
-  struct so_list *dst = nullptr;
-  struct so_list **link = &dst;
+  intrusive_list<so_list> dst;
 
   for (const rocm_so &so : sos)
     {
@@ -218,9 +217,7 @@ so_list_from_rocm_sos (const std::vector<rocm_so> &sos)
       newobj->so_name = so.name;
       newobj->so_original_name = so.unique_name;
 
-      newobj->next = nullptr;
-      *link = newobj;
-      link = &newobj->next;
+      dst.push_back (*newobj);
     }
 
   return dst;
@@ -229,30 +226,27 @@ so_list_from_rocm_sos (const std::vector<rocm_so> &sos)
 /* Build a list of `struct so_list' objects describing the shared
    objects currently loaded in the inferior.  */
 
-static struct so_list *
+static intrusive_list<so_list>
 rocm_solib_current_sos ()
 {
   /* First, retrieve the host-side shared library list.  */
-  so_list *head = svr4_so_ops.current_sos ();
+  intrusive_list<so_list> sos = svr4_so_ops.current_sos ();
 
   /* Then, the device-side shared library list.  */
   std::vector<rocm_so> &dev_sos = get_solib_info (current_inferior ())->solib_list;
 
   if (dev_sos.empty ())
-    return head;
+    return sos;
 
-  so_list *dev_so_list = so_list_from_rocm_sos (dev_sos);
+  intrusive_list<so_list> dev_so_list = so_list_from_rocm_sos (dev_sos);
 
-  if (head == nullptr)
+  if (sos.empty ())
     return dev_so_list;
 
   /* Append our libraries to the end of the list.  */
-  so_list *tail;
-  for (tail = head; tail->next; tail = tail->next)
-    /* Nothing.  */;
-  tail->next = dev_so_list;
+  sos.splice (std::move (dev_so_list));
 
-  return head;
+  return sos;
 }
 
 namespace {
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index b90b4ca2f680..6311599ff709 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -954,7 +954,7 @@ open_symbol_file_object (int from_tty)
 
 struct svr4_library_list
 {
-  /* The tail pointer of the current namespace.  This is internal to XML
+  /* The so list for the current namespace.  This is internal to XML
      parsing.  */
   std::vector<svr4_so> *cur_list;
 
@@ -990,11 +990,10 @@ svr4_clear_so (const so_list &so)
 
 /* Create the so_list objects equivalent to the svr4_sos in SOS.  */
 
-static so_list *
+static intrusive_list<so_list>
 so_list_from_svr4_sos (const std::vector<svr4_so> &sos)
 {
-  struct so_list *dst = NULL;
-  struct so_list **link = &dst;
+  intrusive_list<so_list> dst;
 
   for (const svr4_so &so : sos)
     {
@@ -1004,9 +1003,7 @@ so_list_from_svr4_sos (const std::vector<svr4_so> &sos)
       newobj->so_original_name = so.name;
       newobj->lm_info = gdb::make_unique<lm_info_svr4> (*so.lm_info);
 
-      newobj->next = NULL;
-      *link = newobj;
-      link = &newobj->next;
+      dst.push_back (*newobj);
     }
 
   return dst;
@@ -1186,11 +1183,11 @@ svr4_current_sos_via_xfer_libraries (struct svr4_library_list *list,
 /* If no shared library information is available from the dynamic
    linker, build a fallback list from other sources.  */
 
-static struct so_list *
+static intrusive_list<so_list>
 svr4_default_sos (svr4_info *info)
 {
   if (!info->debug_loader_offset_p)
-    return NULL;
+    return {};
 
   so_list *newobj = new so_list;
   auto li = gdb::make_unique<lm_info_svr4> ();
@@ -1203,7 +1200,10 @@ svr4_default_sos (svr4_info *info)
   newobj->so_name = info->debug_loader_name;
   newobj->so_original_name = newobj->so_name;
 
-  return newobj;
+  intrusive_list<so_list> sos;
+  sos.push_back (*newobj);
+
+  return sos;
 }
 
 /* Read the whole inferior libraries chain starting at address LM.
@@ -1372,32 +1372,15 @@ svr4_current_sos_direct (struct svr4_info *info)
 
 /* Collect sos read and stored by the probes interface.  */
 
-static so_list *
+static intrusive_list<so_list>
 svr4_collect_probes_sos (svr4_info *info)
 {
-  so_list *res = nullptr;
-  so_list **pnext = &res;
+  intrusive_list<so_list> res;
 
   for (const auto &tuple : info->solib_lists)
     {
       const std::vector<svr4_so> &sos = tuple.second;
-
-      /* Allow the linker to report empty namespaces.  */
-      if (sos.empty ())
-	continue;
-
-      *pnext = so_list_from_svr4_sos (sos);
-
-      /* 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;
-
-	  pnext = &next->next;
-	}
+      res.splice (so_list_from_svr4_sos (sos));
     }
 
   return res;
@@ -1406,10 +1389,10 @@ svr4_collect_probes_sos (svr4_info *info)
 /* Implement the main part of the "current_sos" target_so_ops
    method.  */
 
-static struct so_list *
+static intrusive_list<so_list>
 svr4_current_sos_1 (svr4_info *info)
 {
-  so_list *sos = nullptr;
+  intrusive_list<so_list> sos;
 
   /* If we're using the probes interface, we can use the cache as it will
      be maintained by probe update/reload actions.  */
@@ -1419,12 +1402,12 @@ svr4_current_sos_1 (svr4_info *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)
+  if (sos.empty ())
     {
       svr4_current_sos_direct (info);
 
       sos = svr4_collect_probes_sos (info);
-      if (sos == nullptr)
+      if (sos.empty ())
 	sos = svr4_default_sos (info);
     }
 
@@ -1433,11 +1416,11 @@ svr4_current_sos_1 (svr4_info *info)
 
 /* Implement the "current_sos" target_so_ops method.  */
 
-static struct so_list *
-svr4_current_sos (void)
+static intrusive_list<so_list>
+svr4_current_sos ()
 {
   svr4_info *info = get_svr4_info (current_program_space);
-  struct so_list *so_head = svr4_current_sos_1 (info);
+  intrusive_list<so_list> sos = svr4_current_sos_1 (info);
   struct mem_range vsyscall_range;
 
   /* Filter out the vDSO module, if present.  Its symbol file would
@@ -1446,13 +1429,8 @@ svr4_current_sos (void)
   if (gdbarch_vsyscall_range (current_inferior ()->arch (), &vsyscall_range)
       && vsyscall_range.length != 0)
     {
-      struct so_list **sop;
-
-      sop = &so_head;
-      while (*sop != NULL)
+      for (auto so = sos.begin (); so != sos.end (); )
 	{
-	  struct so_list *so = *sop;
-
 	  /* We can't simply match the vDSO by starting address alone,
 	     because lm_info->l_addr_inferior (and also l_addr) do not
 	     necessarily represent the real starting address of the
@@ -1499,16 +1477,17 @@ svr4_current_sos (void)
 
 	  if (address_in_mem_range (li->l_ld, &vsyscall_range))
 	    {
-	      *sop = so->next;
+	      auto next = sos.erase (so);
 	      free_so (*so);
+	      so = next;
 	      break;
 	    }
 
-	  sop = &so->next;
+	  ++so;
 	}
     }
 
-  return so_head;
+  return sos;
 }
 
 /* Get the address of the link_map for a given OBJFILE.  */
@@ -1528,11 +1507,11 @@ svr4_fetch_objfile_link_map (struct objfile *objfile)
 
   /* The other link map addresses may be found by examining the list
      of shared libraries.  */
-  for (struct so_list *so : current_program_space->solibs ())
-    if (so->objfile == objfile)
+  for (const so_list &so : current_program_space->solibs ())
+    if (so.objfile == objfile)
       {
 	auto *li
-	  = gdb::checked_static_cast<lm_info_svr4 *> (so->lm_info.get ());
+	  = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
 
 	return li->lm_addr;
       }
@@ -2369,13 +2348,13 @@ enable_break (struct svr4_info *info, int from_tty)
 
       /* On a running target, we can get the dynamic linker's base
 	 address from the shared library table.  */
-      for (struct so_list *so : current_program_space->solibs ())
+      for (const so_list &so : current_program_space->solibs ())
 	{
-	  if (svr4_same_1 (interp_name, so->so_original_name.c_str ()))
+	  if (svr4_same_1 (interp_name, so.so_original_name.c_str ()))
 	    {
 	      load_addr_found = 1;
 	      loader_found_in_list = 1;
-	      load_addr = lm_addr_check (*so, tmp_bfd.get ());
+	      load_addr = lm_addr_check (so, tmp_bfd.get ());
 	      break;
 	    }
 	}
@@ -3259,7 +3238,7 @@ svr4_lp64_fetch_link_map_offsets (void)
 
 /* Return the DSO matching OBJFILE or nullptr if none can be found.  */
 
-static so_list *
+static const so_list *
 find_solib_for_objfile (struct objfile *objfile)
 {
   if (objfile == nullptr)
@@ -3270,9 +3249,9 @@ find_solib_for_objfile (struct objfile *objfile)
   if (objfile->separate_debug_objfile_backlink != nullptr)
     objfile = objfile->separate_debug_objfile_backlink;
 
-  for (so_list *so : current_program_space->solibs ())
-    if (so->objfile == objfile)
-      return so;
+  for (const so_list &so : current_program_space->solibs ())
+    if (so.objfile == objfile)
+      return &so;
 
   return nullptr;
 }
@@ -3285,7 +3264,7 @@ find_solib_for_objfile (struct objfile *objfile)
    right thing for the main executable.  */
 
 static CORE_ADDR
-find_debug_base_for_solib (so_list *solib)
+find_debug_base_for_solib (const so_list *solib)
 {
   if (solib == nullptr)
     return 0;
@@ -3346,7 +3325,7 @@ svr4_iterate_over_objfiles_in_search_order
   /* The linker namespace to iterate identified by the address of its
      r_debug object, defaulting to the initial namespace.  */
   CORE_ADDR initial = elf_locate_base ();
-  so_list *curr_solib = find_solib_for_objfile (current_objfile);
+  const so_list *curr_solib = find_solib_for_objfile (current_objfile);
   CORE_ADDR debug_base = find_debug_base_for_solib (curr_solib);
   if (debug_base == 0)
     debug_base = initial;
@@ -3361,7 +3340,7 @@ svr4_iterate_over_objfiles_in_search_order
 	 If we fail, e.g. for manually added symbol files or for the main
 	 executable, we assume that they were added to the initial
 	 namespace.  */
-      so_list *solib = find_solib_for_objfile (objfile);
+      const so_list *solib = find_solib_for_objfile (objfile);
       CORE_ADDR solib_base = find_debug_base_for_solib (solib);
       if (solib_base == 0)
 	solib_base = initial;
diff --git a/gdb/solib-target.c b/gdb/solib-target.c
index 0881d7eda430..2e1c74f97e70 100644
--- a/gdb/solib-target.c
+++ b/gdb/solib-target.c
@@ -227,25 +227,22 @@ solib_target_parse_libraries (const char *library)
 }
 #endif
 
-static struct so_list *
+static intrusive_list<so_list>
 solib_target_current_sos (void)
 {
-  so_list *start = NULL, *last = NULL;
+  intrusive_list<so_list> sos;
 
   /* Fetch the list of shared libraries.  */
   gdb::optional<gdb::char_vector> library_document
     = target_read_stralloc (current_inferior ()->top_target (),
 			    TARGET_OBJECT_LIBRARIES, NULL);
   if (!library_document)
-    return NULL;
+    return {};
 
   /* Parse the list.  */
   std::vector<lm_info_target_up> library_list
     = solib_target_parse_libraries (library_document->data ());
 
-  if (library_list.empty ())
-    return NULL;
-
   /* Build a struct so_list for each entry on the list.  */
   for (lm_info_target_up &info : library_list)
     {
@@ -257,16 +254,10 @@ solib_target_current_sos (void)
       new_solib->lm_info = std::move (info);
 
       /* Add it to the list.  */
-      if (!start)
-	last = start = new_solib;
-      else
-	{
-	  last->next = new_solib;
-	  last = new_solib;
-	}
+      sos.push_back (*new_solib);
     }
 
-  return start;
+  return sos;
 }
 
 static void
diff --git a/gdb/solib.c b/gdb/solib.c
index 1815b0910208..9c10340ff583 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -734,10 +734,10 @@ solib_read_symbols (so_list &so, symfile_add_flags flags)
    in the list of shared libraries.  Return false otherwise.  */
 
 static bool
-solib_used (const struct so_list *const known)
+solib_used (const so_list &known)
 {
-  for (const struct so_list *pivot : current_program_space->solibs ())
-    if (pivot != known && pivot->objfile == known->objfile)
+  for (const so_list &pivot : current_program_space->solibs ())
+    if (&pivot != &known && pivot.objfile == known.objfile)
       return true;
   return false;
 }
@@ -766,8 +766,6 @@ void
 update_solib_list (int from_tty)
 {
   const target_so_ops *ops = gdbarch_so_ops (current_inferior ()->arch ());
-  struct so_list *inferior = ops->current_sos();
-  struct so_list *gdb, **gdb_link;
 
   /* We can reach here due to changing solib-search-path or the
      sysroot, before having any inferior.  */
@@ -818,42 +816,38 @@ update_solib_list (int from_tty)
      the time we're done walking GDB's list, the inferior's list
      contains only the new shared objects, which we then add.  */
 
-  gdb = current_program_space->so_list;
-  gdb_link = &current_program_space->so_list;
-  while (gdb)
+  intrusive_list<so_list> inferior = ops->current_sos ();
+  intrusive_list<so_list>::iterator gdb_iter
+    = current_program_space->so_list.begin ();
+  while (gdb_iter != current_program_space->so_list.end ())
     {
-      struct so_list *i = inferior;
-      struct so_list **i_link = &inferior;
+      intrusive_list<so_list>::iterator inferior_iter = inferior.begin ();
 
       /* Check to see whether the shared object *gdb also appears in
 	 the inferior's current list.  */
-      while (i)
+      for (; inferior_iter != inferior.end (); ++inferior_iter)
 	{
 	  if (ops->same)
 	    {
-	      if (ops->same (*gdb, *i))
+	      if (ops->same (*gdb_iter, *inferior_iter))
 		break;
 	    }
 	  else
 	    {
-	      if (filename_cmp (gdb->so_original_name.c_str (),
-				i->so_original_name.c_str ()) == 0)
-		break;	      
+	      if (!filename_cmp (gdb_iter->so_original_name.c_str (),
+				 inferior_iter->so_original_name.c_str ()))
+		break;
 	    }
-
-	  i_link = &i->next;
-	  i = *i_link;
 	}
 
       /* If the shared object appears on the inferior's list too, then
 	 it's still loaded, so we don't need to do anything.  Delete
 	 it from the inferior's list, and leave it on GDB's list.  */
-      if (i)
+      if (inferior_iter != inferior.end ())
 	{
-	  *i_link = i->next;
-	  free_so (*i);
-	  gdb_link = &gdb->next;
-	  gdb = *gdb_link;
+	  inferior.erase (inferior_iter);
+	  free_so (*inferior_iter);
+	  ++gdb_iter;
 	}
 
       /* If it's not on the inferior's list, remove it from GDB's tables.  */
@@ -861,52 +855,49 @@ update_solib_list (int from_tty)
 	{
 	  /* Notify any observer that the shared object has been
 	     unloaded before we remove it from GDB's tables.  */
-	  notify_solib_unloaded (current_program_space, *gdb);
+	  notify_solib_unloaded (current_program_space, *gdb_iter);
 
-	  current_program_space->deleted_solibs.push_back (gdb->so_name);
+	  current_program_space->deleted_solibs.push_back (gdb_iter->so_name);
 
-	  *gdb_link = gdb->next;
+	  intrusive_list<so_list>::iterator gdb_iter_next
+	    = current_program_space->so_list.erase (gdb_iter);
 
 	  /* Unless the user loaded it explicitly, free SO's objfile.  */
-	  if (gdb->objfile && ! (gdb->objfile->flags & OBJF_USERLOADED)
-	      && !solib_used (gdb))
-	    gdb->objfile->unlink ();
+	  if (gdb_iter->objfile != nullptr
+	      && !(gdb_iter->objfile->flags & OBJF_USERLOADED)
+	      && !solib_used (*gdb_iter))
+	    gdb_iter->objfile->unlink ();
 
 	  /* Some targets' section tables might be referring to
 	     sections from so.abfd; remove them.  */
-	  current_program_space->remove_target_sections (gdb);
+	  current_program_space->remove_target_sections (&*gdb_iter);
 
-	  free_so (*gdb);
-	  gdb = *gdb_link;
+	  free_so (*gdb_iter);
+	  gdb_iter = gdb_iter_next;
 	}
     }
 
   /* Now the inferior's list contains only shared objects that don't
      appear in GDB's list --- those that are newly loaded.  Add them
      to GDB's shared object list.  */
-  if (inferior)
+  if (!inferior.empty ())
     {
       int not_found = 0;
       const char *not_found_filename = NULL;
 
-      struct so_list *i;
-
-      /* Add the new shared objects to GDB's list.  */
-      *gdb_link = inferior;
-
       /* Fill in the rest of each of the `struct so_list' nodes.  */
-      for (i = inferior; i; i = i->next)
+      for (so_list &new_so : inferior)
 	{
-	  current_program_space->added_solibs.push_back (i);
+	  current_program_space->added_solibs.push_back (&new_so);
 
 	  try
 	    {
 	      /* Fill in the rest of the `struct so_list' node.  */
-	      if (!solib_map_sections (*i))
+	      if (!solib_map_sections (new_so))
 		{
 		  not_found++;
 		  if (not_found_filename == NULL)
-		    not_found_filename = i->so_original_name.c_str ();
+		    not_found_filename = new_so.so_original_name.c_str ();
 		}
 	    }
 
@@ -919,9 +910,12 @@ update_solib_list (int from_tty)
 
 	  /* Notify any observer that the shared object has been
 	     loaded now that we've added it to GDB's tables.  */
-	  notify_solib_loaded (*i);
+	  notify_solib_loaded (new_so);
 	}
 
+      /* Add the new shared objects to GDB's list.  */
+      current_program_space->so_list.splice (std::move (inferior));
+
       /* If a library was not found, issue an appropriate warning
 	 message.  We have to use a single call to warning in case the
 	 front end does something special with warnings, e.g., pop up
@@ -1015,8 +1009,8 @@ solib_add (const char *pattern, int from_tty, int readsyms)
     if (from_tty)
 	add_flags |= SYMFILE_VERBOSE;
 
-    for (struct so_list *gdb : current_program_space->solibs ())
-      if (! pattern || re_exec (gdb->so_name.c_str ()))
+    for (so_list &gdb : current_program_space->solibs ())
+      if (! pattern || re_exec (gdb.so_name.c_str ()))
 	{
 	  /* Normally, we would read the symbols from that library
 	     only if READSYMS is set.  However, we're making a small
@@ -1024,20 +1018,20 @@ solib_add (const char *pattern, int from_tty, int readsyms)
 	     need the library symbols to be loaded in order to provide
 	     thread support (x86-linux for instance).  */
 	  const int add_this_solib =
-	    (readsyms || libpthread_solib_p (*gdb));
+	    (readsyms || libpthread_solib_p (gdb));
 
 	  any_matches = true;
 	  if (add_this_solib)
 	    {
-	      if (gdb->symbols_loaded)
+	      if (gdb.symbols_loaded)
 		{
 		  /* If no pattern was given, be quiet for shared
 		     libraries we have already loaded.  */
 		  if (pattern && (from_tty || info_verbose))
 		    gdb_printf (_("Symbols already loaded for %s\n"),
-				gdb->so_name.c_str ());
+				gdb.so_name.c_str ());
 		}
-	      else if (solib_read_symbols (*gdb, add_flags))
+	      else if (solib_read_symbols (gdb, add_flags))
 		loaded_any_symbols = true;
 	    }
 	}
@@ -1089,11 +1083,11 @@ info_sharedlibrary_command (const char *pattern, int from_tty)
      so we need to make two passes over the libs.  */
 
   nr_libs = 0;
-  for (struct so_list *so : current_program_space->solibs ())
+  for (const so_list &so : current_program_space->solibs ())
     {
-      if (so->so_name[0])
+      if (so.so_name[0])
 	{
-	  if (pattern && ! re_exec (so->so_name.c_str ()))
+	  if (pattern && ! re_exec (so.so_name.c_str ()))
 	    continue;
 	  ++nr_libs;
 	}
@@ -1110,19 +1104,19 @@ info_sharedlibrary_command (const char *pattern, int from_tty)
 
     uiout->table_body ();
 
-    for (struct so_list *so : current_program_space->solibs ())
+    for (const so_list &so : current_program_space->solibs ())
       {
-	if (! so->so_name[0])
+	if (! so.so_name[0])
 	  continue;
-	if (pattern && ! re_exec (so->so_name.c_str ()))
+	if (pattern && ! re_exec (so.so_name.c_str ()))
 	  continue;
 
 	ui_out_emit_tuple tuple_emitter (uiout, "lib");
 
-	if (so->addr_high != 0)
+	if (so.addr_high != 0)
 	  {
-	    uiout->field_core_addr ("from", gdbarch, so->addr_low);
-	    uiout->field_core_addr ("to", gdbarch, so->addr_high);
+	    uiout->field_core_addr ("from", gdbarch, so.addr_low);
+	    uiout->field_core_addr ("to", gdbarch, so.addr_high);
 	  }
 	else
 	  {
@@ -1131,16 +1125,16 @@ info_sharedlibrary_command (const char *pattern, int from_tty)
 	  }
 
 	if (! top_level_interpreter ()->interp_ui_out ()->is_mi_like_p ()
-	    && so->symbols_loaded
-	    && !objfile_has_symbols (so->objfile))
+	    && so.symbols_loaded
+	    && !objfile_has_symbols (so.objfile))
 	  {
 	    so_missing_debug_info = true;
 	    uiout->field_string ("syms-read", "Yes (*)");
 	  }
 	else
-	  uiout->field_string ("syms-read", so->symbols_loaded ? "Yes" : "No");
+	  uiout->field_string ("syms-read", so.symbols_loaded ? "Yes" : "No");
 
-	uiout->field_string ("name", so->so_name, file_name_style.style ());
+	uiout->field_string ("name", so.so_name, file_name_style.style ());
 
 	uiout->text ("\n");
       }
@@ -1188,13 +1182,11 @@ solib_contains_address_p (const so_list &solib,
 const char *
 solib_name_from_address (struct program_space *pspace, CORE_ADDR address)
 {
-  struct so_list *so = NULL;
-
-  for (so = pspace->so_list; so; so = so->next)
-    if (solib_contains_address_p (*so, address))
-      return so->so_name.c_str ();
+  for (const so_list &so : pspace->so_list)
+    if (solib_contains_address_p (so, address))
+      return so.so_name.c_str ();
 
-  return (0);
+  return nullptr;
 }
 
 /* See solib.h.  */
@@ -1219,15 +1211,13 @@ clear_solib (void)
 
   disable_breakpoints_in_shlibs ();
 
-  while (current_program_space->so_list)
+  current_program_space->so_list.clear_and_dispose ([] (so_list &so)
     {
-      struct so_list *so = current_program_space->so_list;
+      notify_solib_unloaded (current_program_space, so);
+      current_program_space->remove_target_sections (&so);
+      free_so (so);
+    });
 
-      current_program_space->so_list = so->next;
-      notify_solib_unloaded (current_program_space, *so);
-      current_program_space->remove_target_sections (so);
-      free_so (*so);
-    }
 
   if (ops->clear_solib != nullptr)
     ops->clear_solib (current_program_space);
@@ -1322,17 +1312,17 @@ reload_shared_libraries_1 (int from_tty)
   if (print_symbol_loading_p (from_tty, 0, 0))
     gdb_printf (_("Loading symbols for shared libraries.\n"));
 
-  for (struct so_list *so : current_program_space->solibs ())
+  for (so_list &so : current_program_space->solibs ())
     {
       const char *found_pathname = NULL;
-      bool was_loaded = so->symbols_loaded != 0;
+      bool was_loaded = so.symbols_loaded != 0;
       symfile_add_flags add_flags = SYMFILE_DEFER_BP_RESET;
 
       if (from_tty)
 	add_flags |= SYMFILE_VERBOSE;
 
       gdb::unique_xmalloc_ptr<char> filename
-	(tilde_expand (so->so_original_name.c_str ()));
+	(tilde_expand (so.so_original_name.c_str ()));
       gdb_bfd_ref_ptr abfd (solib_bfd_open (filename.get ()));
       if (abfd != NULL)
 	found_pathname = bfd_get_filename (abfd.get ());
@@ -1341,26 +1331,26 @@ reload_shared_libraries_1 (int from_tty)
 	 symbol file, close that.  */
       if ((found_pathname == NULL && was_loaded)
 	  || (found_pathname != NULL
-	      && filename_cmp (found_pathname, so->so_name.c_str ()) != 0))
+	      && filename_cmp (found_pathname, so.so_name.c_str ()) != 0))
 	{
-	  if (so->objfile && ! (so->objfile->flags & OBJF_USERLOADED)
+	  if (so.objfile && ! (so.objfile->flags & OBJF_USERLOADED)
 	      && !solib_used (so))
-	    so->objfile->unlink ();
-	  current_program_space->remove_target_sections (so);
-	  so->clear ();
+	    so.objfile->unlink ();
+	  current_program_space->remove_target_sections (&so);
+	  so.clear ();
 	}
 
       /* If this shared library is now associated with a new symbol
 	 file, open it.  */
       if (found_pathname != NULL
 	  && (!was_loaded
-	      || filename_cmp (found_pathname, so->so_name.c_str ()) != 0))
+	      || filename_cmp (found_pathname, so.so_name.c_str ()) != 0))
 	{
 	  bool got_error = false;
 
 	  try
 	    {
-	      solib_map_sections (*so);
+	      solib_map_sections (so);
 	    }
 
 	  catch (const gdb_exception_error &e)
@@ -1372,8 +1362,8 @@ reload_shared_libraries_1 (int from_tty)
 	    }
 
 	    if (!got_error
-		&& (auto_solib_add || was_loaded || libpthread_solib_p (*so)))
-	      solib_read_symbols (*so, add_flags);
+		&& (auto_solib_add || was_loaded || libpthread_solib_p (so)))
+	      solib_read_symbols (so, add_flags);
 	}
     }
 }
@@ -1729,9 +1719,9 @@ remove_user_added_objfile (struct objfile *objfile)
 {
   if (objfile != 0 && objfile->flags & OBJF_USERLOADED)
     {
-      for (struct so_list *so : objfile->pspace->solibs ())
-	if (so->objfile == objfile)
-	  so->objfile = NULL;
+      for (so_list &so : objfile->pspace->solibs ())
+	if (so.objfile == objfile)
+	  so.objfile = nullptr;
     }
 }
 
diff --git a/gdb/solist.h b/gdb/solist.h
index 9320acb7fd3e..31b823a3a482 100644
--- a/gdb/solist.h
+++ b/gdb/solist.h
@@ -36,7 +36,7 @@ struct lm_info
 
 using lm_info_up = std::unique_ptr<lm_info>;
 
-struct so_list
+struct so_list : intrusive_list_node<so_list>
 {
   /* Free symbol-file related contents of SO and reset for possible reloading
      of SO.  If we have opened a BFD for SO, close it.  If we have placed SO's
@@ -121,7 +121,7 @@ struct target_so_ops
      inferior --- we don't examine any of the shared library files
      themselves.  The declaration of `struct so_list' says which fields
      we provide values for.  */
-  struct so_list *(*current_sos) (void);
+  intrusive_list<so_list> (*current_sos) ();
 
   /* Find, open, and read the symbols for the main executable.  If
      FROM_TTY is non-zero, allow messages to be printed.  */
@@ -171,8 +171,6 @@ struct target_so_ops
   void (*handle_event) (void);
 };
 
-using so_list_range = next_range<so_list>;
-
 /* Free the memory associated with a (so_list *).  */
 void free_so (so_list &so);
 
