Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 00/11] Multiple solib_ops in a program_space
@ 2025-12-09 19:32 Simon Marchi
  2025-12-09 19:32 ` [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops Simon Marchi
                   ` (11 more replies)
  0 siblings, 12 replies; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

This series adds support for having multiple solib_ops instances
providing solibs for a given program_space.  The main use case to
justify this change is to make rocm_solib_ops less clunky.

When debugging ROCm programs, we need to have an instance of
svr4_solib_ops providing solibs for host shared libraries, and
an instance of rocm_solib_ops providing solibs for device code objects.
Since there can only be one solib_ops in a program, rocm_solib_ops has
to wrap and replce the svr4_solib_ops instance previously installed.

By allowing multiple solib_ops in the same program_space, the two
solib_ops instances can co-exist without knowing each other.

The main change is contained in the last patch.  The patches before that
are cleanup or preparatory patches.

Simon Marchi (11):
  gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  gdb/solib: use early return in solib_read_symbols
  gdb/solib-rocm: pass reference to cache to
    rocm_code_object_stream_file
  gdb/solib-rocm: add cached_fd to manage cached fd lifetime
  gdb: de-constify some methods of solib_ops
  gdb/solib-rocm: move per-inferior data to rocm_solib_ops
  gdb/solib-rocm: save inferior in rocm_solib_ops
  gdb/solib: add remove_solib function
  gdb: add objfile -> solib backlink
  gdb: change default objfile iteration order to start with current
    objfile
  gdb: multiple solib_ops per program space

 gdb/amd-dbgapi-target.c                  |   3 +-
 gdb/infcmd.c                             |  16 +-
 gdb/inferior.h                           |   6 +-
 gdb/infrun.c                             |   7 +-
 gdb/objfiles.h                           |  23 ++
 gdb/observable.h                         |   8 +-
 gdb/progspace.c                          |  91 +++++-
 gdb/progspace.h                          |  57 +++-
 gdb/solib-aix.c                          |  14 +-
 gdb/solib-darwin.c                       |  14 +-
 gdb/solib-dsbt.c                         |  10 +-
 gdb/solib-frv.c                          |  10 +-
 gdb/solib-rocm.c                         | 348 +++++++++-----------
 gdb/solib-svr4.c                         |  60 ++--
 gdb/solib-svr4.h                         |  20 +-
 gdb/solib-target.c                       |   4 +-
 gdb/solib-target.h                       |   2 +-
 gdb/solib.c                              | 393 +++++++++++++----------
 gdb/solib.h                              |  50 ++-
 gdb/target.c                             |   2 +-
 gdb/testsuite/gdb.rocm/symbol-lookup.cpp | 320 ++++++++++++++++++
 gdb/testsuite/gdb.rocm/symbol-lookup.exp | 243 ++++++++++++++
 gdb/windows-tdep.c                       |  46 +--
 23 files changed, 1221 insertions(+), 526 deletions(-)
 create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.exp


base-commit: bfc430c99b66ab12643a65388c4ace925ebd68b2
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-05-07 17:56   ` [PATCH 1/11] " Lancelot SIX
  2025-12-09 19:32 ` [PATCH 02/11] gdb/solib: use early return in solib_read_symbols Simon Marchi
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

In some fork cases, the rocm_solib_target_inferior_created observer gets
called while the child inferior (passed as a parameter) already has a
rocm_solib_ops installed.  Since we unconditionally wrap the existing
solib_ops with a new rocm_solib_ops, we can end up with a chain of
multiple rocm_solib_ops, like:

  rocm_solib_ops -> rocm_solib_ops -> svr4_solib_ops

I don't think it is technically harmful as of now (unless the process
does a ton of forks and the rocm_solib_ops accumulate), but it is for
sure useless.  Add an assert for this in the rocm_solib_ops constructor,
which reveals the cases where this happens.

When a fork happens with follow-fork-mode == child and detach-on-fork
on, infrun's follow_fork_inferior function directly moves the program
space from the parent the child inferior, as an optimization.  Coming
into rocm_solib_target_inferior_created, the inferior's pspace
unexpectedly already has a rocm_solib_ops pushed.

Fix this locally by using an inferior_forked observer.  In the scenario
described above, remove the rocm_solib_ops and restore the host
solib_ops as the program space's solib_ops, to make it look as if infrun
didn't do this trick.  This requires adding two parameters to the
inferior_forked observer (detach_on_fork and follow_child).

This should probably be done by infrun directly at some point.  The
logic being that it's fine to do an optimization, but it should look as
if it didn't occur.  If infrun created a brand new pspace for the child,
there wouldn't be a rocm_solib_ops there already.  But I prefer a local
fix for now.

Finally, there are also the vfork cases, where the child inferior shares
the program space with its parent, and therefore the child's program
space already has a rocm_solib_ops installed.  Address this case by
returning early (the `inf->vfork_parent != nullptr` check), because
there is nothing we want to do for a vfork child anyway.

Change-Id: I2e76d111e96f1e01b6799b04da9cdd6f6e2984c9
---
 gdb/amd-dbgapi-target.c |  3 ++-
 gdb/infrun.c            |  3 ++-
 gdb/observable.h        |  8 ++++++--
 gdb/solib-rocm.c        | 31 +++++++++++++++++++++++++++++++
 4 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
index d296f2830063..87b834739e2d 100644
--- a/gdb/amd-dbgapi-target.c
+++ b/gdb/amd-dbgapi-target.c
@@ -2102,7 +2102,8 @@ amd_dbgapi_inferior_execd (inferior *exec_inf, inferior *follow_inf)
 
 static void
 amd_dbgapi_inferior_forked (inferior *parent_inf, inferior *child_inf,
-			    target_waitkind fork_kind)
+			    target_waitkind fork_kind, bool detach_on_fork,
+			    bool follow_child)
 {
   if (child_inf != nullptr)
     {
diff --git a/gdb/infrun.c b/gdb/infrun.c
index bd114e16b80e..e0ffe95e4e45 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -645,7 +645,8 @@ holding the child stopped.  Try \"set %ps\" or \"%ps\".\n"),
   target_follow_fork (child_inf, child_ptid, fork_kind, follow_child,
 		      detach_fork);
 
-  gdb::observers::inferior_forked.notify (parent_inf, child_inf, fork_kind);
+  gdb::observers::inferior_forked.notify (parent_inf, child_inf, fork_kind,
+					  detach_fork, follow_child);
 
   /* target_follow_fork must leave the parent as the current inferior.  If we
      want to follow the child, we make it the current one below.  */
diff --git a/gdb/observable.h b/gdb/observable.h
index 5f064cf1fc8c..c8cbdbad97d1 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -92,9 +92,13 @@ extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
    the child (because we follow only the child or we follow both), CHILD_INF
    is the child inferior.  Otherwise, CHILD_INF is nullptr.
 
-   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.  */
+   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.
+
+   detach_on_fork and follow_child represent the "detach-on-fork" and
+   "follow-fork-mode" settings.  */
 extern observable<inferior */* parent_inf */, inferior */* child_inf */,
-		  target_waitkind /* fork_kind */> inferior_forked;
+		  target_waitkind /* fork_kind */, bool /* detach_on_fork */,
+		  bool /* follow_child */> inferior_forked;
 
 /* The shared library specified by SOLIB has been loaded.  Note that
    when gdb calls this observer, the library's symbols probably
diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index d97ab25df5d0..a9573f8eefde 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -164,8 +164,14 @@ struct rocm_solib_ops : public solib_ops
   explicit rocm_solib_ops (program_space *pspace, solib_ops_up host_ops)
     : solib_ops (pspace), m_host_ops (std::move (host_ops))
   {
+    gdb_assert (m_host_ops != nullptr);
+    gdb_assert (dynamic_cast<rocm_solib_ops *> (m_host_ops.get ()) == nullptr);
   }
 
+  /* Release the host solib_ops.  */
+  solib_ops_up release_host_ops ()
+  { return std::move (m_host_ops); }
+
   /* The methods implemented by rocm_solib_ops.  */
   owning_intrusive_list<solib> current_sos () const override;
   void create_inferior_hook (int from_tty) const override;
@@ -808,6 +814,10 @@ rocm_update_solib_list ()
 static void
 rocm_solib_target_inferior_created (inferior *inf)
 {
+  /* A vfork child shares its pspace with its parent, do not touch anything.  */
+  if (inf->vfork_parent != nullptr)
+    return;
+
   get_solib_info (inf)->solib_list.clear ();
 
   auto prev_ops = inf->pspace->release_solib_ops ();
@@ -839,6 +849,24 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
   get_solib_info (exec_inf)->solib_list.clear ();
 }
 
+static void
+rocm_solib_target_inferior_forked (inferior *parent_inf, inferior *child_inf,
+				   target_waitkind fork_kind,
+				   bool detach_on_fork, bool follow_child)
+{
+  if (detach_on_fork && follow_child && fork_kind == TARGET_WAITKIND_FORKED)
+    {
+      /* In this particular configuration, infrun's follow_fork_inferior
+	 function moves the parent pspace to the child directly.  Remove the
+	 existing rocm_solib_ops from the child and restore the host solib_ops,
+	 to make it look like a brand new pspace.  */
+      auto rocm_ops_holder = child_inf->pspace->release_solib_ops ();
+      auto rocm_ops
+	= gdb::checked_static_cast<rocm_solib_ops *> (rocm_ops_holder.get ());
+      child_inf->pspace->set_solib_ops (rocm_ops->release_host_ops ());
+    }
+}
+
 INIT_GDB_FILE (rocm_solib)
 {
   /* The dependency on the amd-dbgapi exists because solib-rocm's
@@ -852,4 +880,7 @@ INIT_GDB_FILE (rocm_solib)
   gdb::observers::inferior_execd.attach
     (rocm_solib_target_inferior_execd, "solib-rocm",
      { &get_amd_dbgapi_target_inferior_execd_observer_token () });
+
+  gdb::observers::inferior_forked.attach
+    (rocm_solib_target_inferior_forked, "solib-rocm");
 }
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 02/11] gdb/solib: use early return in solib_read_symbols
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
  2025-12-09 19:32 ` [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 16:16   ` Tom Tromey
  2025-12-09 19:32 ` [PATCH 03/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file Simon Marchi
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

This reduces the indent a bit, and makes it clearer where the
"interesting" part of the function is.

At the same time, move the comment to solib.h.

Change-Id: I8004895757ce296742bcec929799a0ce6041938b
---
 gdb/solib.c | 73 +++++++++++++++++++++++++----------------------------
 gdb/solib.h |  6 ++++-
 2 files changed, 40 insertions(+), 39 deletions(-)

diff --git a/gdb/solib.c b/gdb/solib.c
index dade2a76a48c..ac674f4e9720 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -621,8 +621,7 @@ solib::clear ()
 
 lm_info::~lm_info () = default;
 
-/* Read in symbols for shared object SO.  If SYMFILE_VERBOSE is set in FLAGS,
-   be chatty about it.  Return true if any symbols were actually loaded.  */
+/* See solib.h.  */
 
 bool
 solib_read_symbols (solib &so, symfile_add_flags flags)
@@ -630,52 +629,50 @@ solib_read_symbols (solib &so, symfile_add_flags flags)
   if (so.symbols_loaded)
     {
       /* If needed, we've already warned in our caller.  */
+      return false;
     }
-  else if (so.abfd == NULL)
+
+  if (so.abfd == nullptr)
     {
-      /* We've already warned about this library, when trying to open
-	 it.  */
+      /* We've already warned about this library, when trying to open it.  */
+      return false;
     }
-  else
+
+  flags |= current_inferior ()->symfile_flags;
+
+  try
     {
-      flags |= current_inferior ()->symfile_flags;
+      /* Have we already loaded this shared object?  */
+      so.objfile = nullptr;
+      for (objfile &objfile : current_program_space->objfiles ())
+	if (objfile.addr_low == so.addr_low)
+	  {
+	    so.objfile = &objfile;
+	    break;
+	  }
 
-      try
+      if (so.objfile == nullptr)
 	{
-	  /* Have we already loaded this shared object?  */
-	  so.objfile = nullptr;
-	  for (objfile &objfile : current_program_space->objfiles ())
-	    if (objfile.addr_low == so.addr_low)
-	      {
-		so.objfile = &objfile;
-		break;
-	      }
-
-	  if (so.objfile == NULL)
-	    {
-	      section_addr_info sap
-		= build_section_addr_info_from_section_table (so.sections);
-	      gdb_bfd_ref_ptr tmp_bfd = so.abfd;
-	      so.objfile
-		= symbol_file_add_from_bfd (tmp_bfd, so.name.c_str (),
-					    flags, &sap, OBJF_SHARED, nullptr);
-	      so.objfile->addr_low = so.addr_low;
-	    }
-
-	  so.symbols_loaded = true;
-	}
-      catch (const gdb_exception_error &e)
-	{
-	  exception_fprintf (gdb_stderr, e,
-			     _("Error while reading shared"
-			       " library symbols for %s:\n"),
-			     so.name.c_str ());
+	  section_addr_info sap
+	    = build_section_addr_info_from_section_table (so.sections);
+	  gdb_bfd_ref_ptr tmp_bfd = so.abfd;
+	  so.objfile = symbol_file_add_from_bfd (tmp_bfd, so.name.c_str (),
+						 flags, &sap, OBJF_SHARED,
+						 nullptr);
+	  so.objfile->addr_low = so.addr_low;
 	}
 
-      return true;
+      so.symbols_loaded = true;
+    }
+  catch (const gdb_exception_error &e)
+    {
+      exception_fprintf (gdb_stderr, e,
+			 _("Error while reading shared"
+			   " library symbols for %s:\n"),
+			 so.name.c_str ());
     }
 
-  return false;
+  return true;
 }
 
 /* Return true if KNOWN->objfile is used by any other solib object
diff --git a/gdb/solib.h b/gdb/solib.h
index 85ea6675b799..92a485dbca4c 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -318,7 +318,11 @@ extern void clear_solib (program_space *pspace);
 /* Called to add symbols from a shared library to gdb's symbol table.  */
 
 extern void solib_add (const char *, int, int);
-extern bool solib_read_symbols (solib &, symfile_add_flags);
+
+/* Read in symbols for shared object SO.  If SYMFILE_VERBOSE is set in FLAGS,
+   be chatty about it.  Return true if any symbols were actually loaded.  */
+
+extern bool solib_read_symbols (solib &so, symfile_add_flags flags);
 
 /* Function to be called when the inferior starts up, to discover the
    names of shared libraries that are dynamically linked, the base
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 03/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
  2025-12-09 19:32 ` [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops Simon Marchi
  2025-12-09 19:32 ` [PATCH 02/11] gdb/solib: use early return in solib_read_symbols Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-05-07 18:20   ` [PATCH 3/11] " Lancelot SIX
  2025-12-09 19:32 ` [PATCH 04/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime Simon Marchi
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

~rocm_code_object_stream_file obtains the fd cache to call "close" on
using its m_inf field, obtaining the per-inferior info using
get_solib_info.  A patch later in this series moves the fd cache from
the per-inferior registry to the rocm_solib_ops directly.  This implies
that we will need a new way to get a reference to the fd_cache owning
the fd, as it won't be easy to get it from the inferior anymore.

To achieve this, update rocm_code_object_stream_file to keep a reference
to the fd cache directly (because the rocm_code_object_stream is not
meant to be copied nor moved, make the field a reference).  The inferior
parameter and field are not needed anymore.

Change-Id: Ia10f8f125840274e51e188cafcb7384fdff92240
---
 gdb/solib-rocm.c | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index a9573f8eefde..bde34bafd8b1 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -360,8 +360,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
 {
   DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file);
 
-  rocm_code_object_stream_file (inferior *inf, int fd, ULONGEST offset,
-				ULONGEST size);
+  rocm_code_object_stream_file (rocm_solib_fd_cache &fd_cache, int fd,
+				ULONGEST offset, ULONGEST size);
 
   file_ptr read (bfd *abfd, void *buf, file_ptr size,
 		 file_ptr offset) override;
@@ -371,9 +371,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
   ~rocm_code_object_stream_file () override;
 
 protected:
-
-  /* The inferior owning this code object stream.  */
-  inferior *m_inf;
+  /* The fd cache owning this code object stream.  */
+  rocm_solib_fd_cache &m_fd_cache;
 
   /* The target file descriptor for this stream.  */
   int m_fd;
@@ -387,8 +386,9 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
 };
 
 rocm_code_object_stream_file::rocm_code_object_stream_file
-  (inferior *inf, int fd, ULONGEST offset, ULONGEST size)
-  : m_inf (inf), m_fd (fd), m_offset (offset), m_size (size)
+  (rocm_solib_fd_cache &fd_cache, int fd, ULONGEST offset,
+   ULONGEST size)
+  : m_fd_cache (fd_cache), m_fd (fd), m_offset (offset), m_size (size)
 {
 }
 
@@ -453,9 +453,8 @@ rocm_code_object_stream_file::size ()
 
 rocm_code_object_stream_file::~rocm_code_object_stream_file ()
 {
-  auto info = get_solib_info (m_inf);
   fileio_error target_errno;
-  if (info->fd_cache.close (m_fd, &target_errno) != 0)
+  if (m_fd_cache.close (m_fd, &target_errno) != 0)
     warning (_("Failed to close solib: %s"),
 	     strerror (fileio_error_to_host (target_errno)));
 }
@@ -608,7 +607,7 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
 	      return nullptr;
 	    }
 
-	  return new rocm_code_object_stream_file (inferior, fd, offset,
+	  return new rocm_code_object_stream_file (info->fd_cache, fd, offset,
 						   size);
 	}
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 04/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (2 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 03/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-05-07 20:17   ` [PATCH 4/11] " Lancelot SIX
  2025-12-09 19:32 ` [PATCH 05/11] gdb: de-constify some methods of solib_ops Simon Marchi
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

Make the management of the cached fds automatic.

Change rocm_solib_fd_cache::open to return a cached_fd, an RAII type
that calls rocm_solib_fd_cache::close on destruction.  cached_fd holds
the actual fd and a reference to the cache it comes from.

Change rocm_code_object_stream_file to hold a cached_fd and remove the
explicit destructor.

This is not essential, I just thought it would be nice.

Change-Id: I2529ac1caebbe6b7e6e60d228a403064f6f38d06
---
 gdb/solib-rocm.c | 91 ++++++++++++++++++++++++++++++------------------
 1 file changed, 58 insertions(+), 33 deletions(-)

diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index bde34bafd8b1..5560a6035184 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -39,22 +39,58 @@ struct rocm_solib_fd_cache
   explicit rocm_solib_fd_cache (inferior *inf) : m_inferior (inf) {}
   DISABLE_COPY_AND_ASSIGN (rocm_solib_fd_cache);
 
+  /* The open method returns an object of this type.
+
+     On destruction, automatically call close to indicate the caller no longer
+     uses the fd.  */
+  struct cached_fd
+  {
+    cached_fd (int fd, rocm_solib_fd_cache &cache)
+      : fd (fd),
+	cache (cache)
+    {
+    }
+
+    cached_fd (cached_fd &&other)
+      : fd (other.fd),
+	cache (other.cache)
+    {
+      other.fd = -1;
+    }
+
+    ~cached_fd ()
+    {
+      if (fd == -1)
+	return;
+
+      fileio_error target_errno;
+      if (cache.close (fd, &target_errno) != 0)
+	warning (_("Failed to close solib: %s"),
+		 strerror (fileio_error_to_host (target_errno)));
+    }
+
+    DISABLE_COPY_AND_ASSIGN (cached_fd);
+
+    int fd;
+    rocm_solib_fd_cache &cache;
+  };
+
   /* Return a read-only file descriptor to FILENAME and increment the
      associated reference count.
 
      Open the file FILENAME if it is not already opened, reuse the existing file
      descriptor otherwise.
 
-     On error -1 is returned, and TARGET_ERRNO is set.  */
-  int open (const std::string &filename, fileio_error *target_errno);
+     On error, return a cached_fd with .fd == -1 and set *TARGET_ERRNO.  */
+  cached_fd open (const std::string &filename, fileio_error *target_errno);
 
+private:
   /* Decrement the reference count to FD and close FD if the reference count
      reaches 0.
 
      On success, return 0.  On error, return -1 and set TARGET_ERRNO.  */
   int close (int fd, fileio_error *target_errno);
 
-private:
   struct refcnt_fd
   {
     refcnt_fd (int fd, int refcnt) : fd (fd), refcnt (refcnt) {}
@@ -72,7 +108,7 @@ struct rocm_solib_fd_cache
   gdb::unordered_map<std::string, refcnt_fd> m_cache;
 };
 
-int
+rocm_solib_fd_cache::cached_fd
 rocm_solib_fd_cache::open (const std::string &filename,
 			   fileio_error *target_errno)
 {
@@ -83,11 +119,14 @@ rocm_solib_fd_cache::open (const std::string &filename,
       int fd
 	= target_fileio_open (m_inferior, filename.c_str (), FILEIO_O_RDONLY,
 			      false, 0, target_errno);
-      if (fd != -1)
-	m_cache.emplace (std::piecewise_construct,
-			 std::forward_as_tuple (filename),
-			 std::forward_as_tuple (fd, 1));
-      return fd;
+      if (fd == -1)
+	return cached_fd { -1, *this };
+
+      m_cache.emplace (std::piecewise_construct,
+		       std::forward_as_tuple (filename),
+		       std::forward_as_tuple (fd, 1));
+
+      return cached_fd { fd, *this };
     }
   else
     {
@@ -95,7 +134,7 @@ rocm_solib_fd_cache::open (const std::string &filename,
 	 already opened FD.  */
       it->second.refcnt++;
       gdb_assert (it->second.fd != -1);
-      return it->second.fd;
+      return cached_fd { it->second.fd, *this };
     }
 }
 
@@ -360,7 +399,7 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
 {
   DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file);
 
-  rocm_code_object_stream_file (rocm_solib_fd_cache &fd_cache, int fd,
+  rocm_code_object_stream_file (rocm_solib_fd_cache::cached_fd fd,
 				ULONGEST offset, ULONGEST size);
 
   file_ptr read (bfd *abfd, void *buf, file_ptr size,
@@ -368,14 +407,9 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
 
   LONGEST size () override;
 
-  ~rocm_code_object_stream_file () override;
-
 protected:
-  /* The fd cache owning this code object stream.  */
-  rocm_solib_fd_cache &m_fd_cache;
-
   /* The target file descriptor for this stream.  */
-  int m_fd;
+  rocm_solib_fd_cache::cached_fd m_fd;
 
   /* The offset of the ELF file image in the target file.  */
   ULONGEST m_offset;
@@ -386,9 +420,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
 };
 
 rocm_code_object_stream_file::rocm_code_object_stream_file
-  (rocm_solib_fd_cache &fd_cache, int fd, ULONGEST offset,
-   ULONGEST size)
-  : m_fd_cache (fd_cache), m_fd (fd), m_offset (offset), m_size (size)
+  (rocm_solib_fd_cache::cached_fd fd, ULONGEST offset, ULONGEST size)
+  : m_fd (std::move (fd)), m_offset (offset), m_size (size)
 {
 }
 
@@ -403,7 +436,7 @@ rocm_code_object_stream_file::read (bfd *, void *buf, file_ptr size,
       QUIT;
 
       file_ptr bytes_read
-	= target_fileio_pread (m_fd, static_cast<gdb_byte *> (buf) + nbytes,
+	= target_fileio_pread (m_fd.fd, static_cast<gdb_byte *> (buf) + nbytes,
 			       size, m_offset + offset + nbytes,
 			       &target_errno);
 
@@ -431,7 +464,7 @@ rocm_code_object_stream_file::size ()
     {
       fileio_error target_errno;
       struct stat stat;
-      if (target_fileio_fstat (m_fd, &stat, &target_errno) < 0)
+      if (target_fileio_fstat (m_fd.fd, &stat, &target_errno) < 0)
 	{
 	  errno = fileio_error_to_host (target_errno);
 	  bfd_set_error (bfd_error_system_call);
@@ -451,14 +484,6 @@ rocm_code_object_stream_file::size ()
   return m_size;
 }
 
-rocm_code_object_stream_file::~rocm_code_object_stream_file ()
-{
-  fileio_error target_errno;
-  if (m_fd_cache.close (m_fd, &target_errno) != 0)
-    warning (_("Failed to close solib: %s"),
-	     strerror (fileio_error_to_host (target_errno)));
-}
-
 /* Interface to a code object which lives in the inferior's memory.  */
 
 struct rocm_code_object_stream_memory final : public rocm_code_object_stream
@@ -598,16 +623,16 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
 	{
 	  auto info = get_solib_info (inferior);
 	  fileio_error target_errno;
-	  int fd = info->fd_cache.open (decoded_path, &target_errno);
+	  auto fd = info->fd_cache.open (decoded_path, &target_errno);
 
-	  if (fd == -1)
+	  if (fd.fd == -1)
 	    {
 	      errno = fileio_error_to_host (target_errno);
 	      bfd_set_error (bfd_error_system_call);
 	      return nullptr;
 	    }
 
-	  return new rocm_code_object_stream_file (info->fd_cache, fd, offset,
+	  return new rocm_code_object_stream_file (std::move (fd), offset,
 						   size);
 	}
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 05/11] gdb: de-constify some methods of solib_ops
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (3 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 04/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2025-12-09 19:32 ` [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops Simon Marchi
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

The following commit moves the ROCm solib data from a registry to the
rocm_solib_ops object itself.  This requires making some methods
non-const, and it then cascades to a bunch of things.  This will have to
be done one day or another, when (I suspect) we'll move more solib_ops
data out of registries and into the solib_ops objects themselves.  Do it
in this separate patch to avoid polluting that patch.

No behavior change expected.

Change-Id: Icb5e921a82e235817f8817a3aa3c20b2c9dc959f
---
 gdb/progspace.h    |  2 +-
 gdb/solib-aix.c    | 12 ++++++------
 gdb/solib-darwin.c | 12 ++++++------
 gdb/solib-dsbt.c   |  8 ++++----
 gdb/solib-frv.c    |  8 ++++----
 gdb/solib-rocm.c   | 20 ++++++++++----------
 gdb/solib-svr4.c   | 14 +++++++-------
 gdb/solib-svr4.h   | 14 +++++++-------
 gdb/solib-target.c |  2 +-
 gdb/solib-target.h |  2 +-
 gdb/solib.c        |  8 ++++----
 gdb/solib.h        | 14 +++++++-------
 gdb/windows-tdep.c |  4 ++--
 13 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/gdb/progspace.h b/gdb/progspace.h
index 498a5b460f12..bb4d6c79d16c 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -269,7 +269,7 @@ struct program_space
   { return std::move (m_solib_ops); }
 
   /* Get this program space's solib provider.  */
-  const struct solib_ops *solib_ops () const
+  struct solib_ops *solib_ops () const
   { return m_solib_ops.get (); }
 
   /* Return the list of all the solibs in this program space.  */
diff --git a/gdb/solib-aix.c b/gdb/solib-aix.c
index 5076d7e2f063..a29e8639b3cd 100644
--- a/gdb/solib-aix.c
+++ b/gdb/solib-aix.c
@@ -31,9 +31,9 @@ struct aix_solib_ops : public solib_ops
   using solib_ops::solib_ops;
 
   void relocate_section_addresses (solib &so, target_section *) const override;
-  void create_inferior_hook (int from_tty) const override;
-  owning_intrusive_list<solib> current_sos () const override;
-  gdb_bfd_ref_ptr bfd_open (const char *pathname) const override;
+  void create_inferior_hook (int from_tty) override;
+  owning_intrusive_list<solib> current_sos () override;
+  gdb_bfd_ref_ptr bfd_open (const char *pathname) override;
 };
 
 /* See solib-aix.h.  */
@@ -430,7 +430,7 @@ solib_aix_get_section_offsets (struct objfile *objfile,
 }
 
 void
-aix_solib_ops::create_inferior_hook (int from_tty) const
+aix_solib_ops::create_inferior_hook (int from_tty)
 {
   const char *warning_msg = "unable to relocate main executable";
 
@@ -459,7 +459,7 @@ aix_solib_ops::create_inferior_hook (int from_tty) const
 }
 
 owning_intrusive_list<solib>
-aix_solib_ops::current_sos () const
+aix_solib_ops::current_sos ()
 {
   std::optional<std::vector<lm_info_aix>> &library_list
     = solib_aix_get_library_list (current_inferior (), NULL);
@@ -503,7 +503,7 @@ aix_solib_ops::current_sos () const
 }
 
 gdb_bfd_ref_ptr
-aix_solib_ops::bfd_open (const char *pathname) const
+aix_solib_ops::bfd_open (const char *pathname)
 {
   /* The pathname is actually a synthetic filename with the following
      form: "/path/to/sharedlib(member.o)" (double-quotes excluded).
diff --git a/gdb/solib-darwin.c b/gdb/solib-darwin.c
index a6f550464f10..961b1099f45e 100644
--- a/gdb/solib-darwin.c
+++ b/gdb/solib-darwin.c
@@ -41,9 +41,9 @@ struct darwin_solib_ops : public solib_ops
 
   void relocate_section_addresses (solib &so, target_section *) const override;
   void clear_solib (program_space *pspace) const override;
-  void create_inferior_hook (int from_tty) const override;
-  owning_intrusive_list<solib> current_sos () const override;
-  gdb_bfd_ref_ptr bfd_open (const char *pathname) const override;
+  void create_inferior_hook (int from_tty) override;
+  owning_intrusive_list<solib> current_sos () override;
+  gdb_bfd_ref_ptr bfd_open (const char *pathname) override;
 };
 
 /* See solib-darwin.h.  */
@@ -214,7 +214,7 @@ find_program_interpreter (void)
 }
 
 owning_intrusive_list<solib>
-darwin_solib_ops::current_sos () const
+darwin_solib_ops::current_sos ()
 {
   type *ptr_type
     = builtin_type (current_inferior ()->arch ())->builtin_data_ptr;
@@ -474,7 +474,7 @@ darwin_solib_read_all_image_info_addr (struct darwin_info *info)
 }
 
 void
-darwin_solib_ops::create_inferior_hook (int from_tty) const
+darwin_solib_ops::create_inferior_hook (int from_tty)
 {
   /* Everything below only makes sense if we have a running inferior.  */
   if (!target_has_execution ())
@@ -609,7 +609,7 @@ darwin_solib_ops::relocate_section_addresses (solib &so,
 }
 
 gdb_bfd_ref_ptr
-darwin_solib_ops::bfd_open (const char *pathname) const
+darwin_solib_ops::bfd_open (const char *pathname)
 {
   int found_file;
 
diff --git a/gdb/solib-dsbt.c b/gdb/solib-dsbt.c
index 23042b4158e9..de18aef6ecdd 100644
--- a/gdb/solib-dsbt.c
+++ b/gdb/solib-dsbt.c
@@ -128,8 +128,8 @@ struct dsbt_solib_ops : public solib_ops
 
   void relocate_section_addresses (solib &so, target_section *) const override;
   void clear_solib (program_space *pspace) const override;
-  void create_inferior_hook (int from_tty) const override;
-  owning_intrusive_list<solib> current_sos () const override;
+  void create_inferior_hook (int from_tty) override;
+  owning_intrusive_list<solib> current_sos () override;
   bool in_dynsym_resolve_code (CORE_ADDR pc) const override;
 };
 
@@ -530,7 +530,7 @@ lm_base (void)
    we provide values for.  */
 
 owning_intrusive_list<solib>
-dsbt_solib_ops::current_sos () const
+dsbt_solib_ops::current_sos ()
 {
   bfd_endian byte_order = gdbarch_byte_order (current_inferior ()->arch ());
   CORE_ADDR lm_addr;
@@ -864,7 +864,7 @@ dsbt_relocate_main_executable (void)
    The shared library breakpoints also need to be enabled.  */
 
 void
-dsbt_solib_ops::create_inferior_hook (int from_tty) const
+dsbt_solib_ops::create_inferior_hook (int from_tty)
 {
   /* Relocate main executable.  */
   dsbt_relocate_main_executable ();
diff --git a/gdb/solib-frv.c b/gdb/solib-frv.c
index 9d36b0fd4edf..39ed0e8b731a 100644
--- a/gdb/solib-frv.c
+++ b/gdb/solib-frv.c
@@ -36,8 +36,8 @@ struct frv_solib_ops : public solib_ops
 
   void relocate_section_addresses (solib &so, target_section *) const override;
   void clear_solib (program_space *pspace) const override;
-  void create_inferior_hook (int from_tty) const override;
-  owning_intrusive_list<solib> current_sos () const override;
+  void create_inferior_hook (int from_tty) override;
+  owning_intrusive_list<solib> current_sos () override;
   bool in_dynsym_resolve_code (CORE_ADDR pc) const override;
 };
 
@@ -323,7 +323,7 @@ lm_base (void)
 }
 
 owning_intrusive_list<solib>
-frv_solib_ops::current_sos () const
+frv_solib_ops::current_sos ()
 {
   bfd_endian byte_order = gdbarch_byte_order (current_inferior ()->arch ());
   CORE_ADDR lm_addr, mgot;
@@ -796,7 +796,7 @@ frv_relocate_main_executable (void)
    enabled.  */
 
 void
-frv_solib_ops::create_inferior_hook (int from_tty) const
+frv_solib_ops::create_inferior_hook (int from_tty)
 {
   /* Relocate main executable.  */
   frv_relocate_main_executable ();
diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index 5560a6035184..953d31d6c77b 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -212,11 +212,11 @@ struct rocm_solib_ops : public solib_ops
   { return std::move (m_host_ops); }
 
   /* The methods implemented by rocm_solib_ops.  */
-  owning_intrusive_list<solib> current_sos () const override;
-  void create_inferior_hook (int from_tty) const override;
-  gdb_bfd_ref_ptr bfd_open (const char *pathname) const override;
+  owning_intrusive_list<solib> current_sos () override;
+  void create_inferior_hook (int from_tty) override;
+  gdb_bfd_ref_ptr bfd_open (const char *pathname) override;
   void relocate_section_addresses (solib &so, target_section *) const override;
-  void handle_event () const override;
+  void handle_event () override;
 
   /* Implement the following methods just to forward the calls to the host
      solib_ops.  We currently need to implement all the methods that
@@ -267,7 +267,7 @@ struct rocm_solib_ops : public solib_ops
 
 private:
   owning_intrusive_list<solib>
-  solibs_from_rocm_sos (const std::vector<rocm_so> &sos) const;
+  solibs_from_rocm_sos (const std::vector<rocm_so> &sos);
 
   solib_ops_up m_host_ops;
 };
@@ -305,7 +305,7 @@ rocm_solib_ops::relocate_section_addresses (solib &so,
 static void rocm_update_solib_list ();
 
 void
-rocm_solib_ops::handle_event () const
+rocm_solib_ops::handle_event ()
 {
   /* Since we sit on top of a host solib_ops, we might get called following an
      event concerning host libraries.  We must therefore forward the call.  If
@@ -321,7 +321,7 @@ rocm_solib_ops::handle_event () const
 /* Create solib objects from rocm_so objects in SOS.  */
 
 owning_intrusive_list<solib>
-rocm_solib_ops::solibs_from_rocm_sos (const std::vector<rocm_so> &sos) const
+rocm_solib_ops::solibs_from_rocm_sos (const std::vector<rocm_so> &sos)
 {
   owning_intrusive_list<solib> dst;
 
@@ -336,7 +336,7 @@ rocm_solib_ops::solibs_from_rocm_sos (const std::vector<rocm_so> &sos) const
    objects currently loaded in the inferior.  */
 
 owning_intrusive_list<solib>
-rocm_solib_ops::current_sos () const
+rocm_solib_ops::current_sos ()
 {
   /* First, retrieve the host-side shared library list.  */
   owning_intrusive_list<solib> sos = m_host_ops->current_sos ();
@@ -677,7 +677,7 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
 }
 
 gdb_bfd_ref_ptr
-rocm_solib_ops::bfd_open (const char *pathname) const
+rocm_solib_ops::bfd_open (const char *pathname)
 {
   /* Handle regular files with SVR4 open.  */
   if (strstr (pathname, "://") == nullptr)
@@ -765,7 +765,7 @@ rocm_solib_ops::bfd_open (const char *pathname) const
 }
 
 void
-rocm_solib_ops::create_inferior_hook (int from_tty) const
+rocm_solib_ops::create_inferior_hook (int from_tty)
 {
   get_solib_info (current_inferior ())->solib_list.clear ();
 
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index b21987544a78..7fd067ff38ab 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -1064,7 +1064,7 @@ svr4_solib_ops::clear_so (const solib &so) const
 /* Create the solib objects equivalent to the svr4_sos in SOS.  */
 
 owning_intrusive_list<solib>
-svr4_solib_ops::solibs_from_svr4_sos (const std::vector<svr4_so> &sos) const
+svr4_solib_ops::solibs_from_svr4_sos (const std::vector<svr4_so> &sos)
 {
   owning_intrusive_list<solib> dst;
 
@@ -1258,7 +1258,7 @@ svr4_current_sos_via_xfer_libraries (struct svr4_library_list *list,
    linker, build a fallback list from other sources.  */
 
 owning_intrusive_list<solib>
-svr4_solib_ops::default_sos (svr4_info *info) const
+svr4_solib_ops::default_sos (svr4_info *info)
 {
   if (!info->debug_loader_offset_p)
     return {};
@@ -1457,7 +1457,7 @@ svr4_solib_ops::current_sos_direct (svr4_info *info) const
 /* Collect sos read and stored by the probes interface.  */
 
 owning_intrusive_list<solib>
-svr4_solib_ops::collect_probes_sos (svr4_info *info) const
+svr4_solib_ops::collect_probes_sos (svr4_info *info)
 {
   owning_intrusive_list<solib> res;
 
@@ -1474,7 +1474,7 @@ svr4_solib_ops::collect_probes_sos (svr4_info *info) const
    method.  */
 
 owning_intrusive_list<solib>
-svr4_solib_ops::current_sos_1 (svr4_info *info) const
+svr4_solib_ops::current_sos_1 (svr4_info *info)
 {
   owning_intrusive_list<solib> sos;
 
@@ -1501,7 +1501,7 @@ svr4_solib_ops::current_sos_1 (svr4_info *info) const
 /* Implement the "current_sos" solib_ops method.  */
 
 owning_intrusive_list<solib>
-svr4_solib_ops::current_sos () const
+svr4_solib_ops::current_sos ()
 {
   svr4_info *info = get_svr4_info (current_program_space);
 
@@ -2149,7 +2149,7 @@ svr4_solib_ops::disable_probes_interface (svr4_info *info) const
    standard interface.  */
 
 void
-svr4_solib_ops::handle_event () const
+svr4_solib_ops::handle_event ()
 {
   struct svr4_info *info = get_svr4_info (current_program_space);
   struct probe_and_action *pa;
@@ -3326,7 +3326,7 @@ svr4_relocate_main_executable (void)
    their symbols to be read at a later time.  */
 
 void
-svr4_solib_ops::create_inferior_hook (int from_tty) const
+svr4_solib_ops::create_inferior_hook (int from_tty)
 {
   struct svr4_info *info;
 
diff --git a/gdb/solib-svr4.h b/gdb/solib-svr4.h
index 7b38ff4483a5..28b27675f529 100644
--- a/gdb/solib-svr4.h
+++ b/gdb/solib-svr4.h
@@ -99,14 +99,14 @@ struct svr4_solib_ops : public solib_ops
   void relocate_section_addresses (solib &so, target_section *) const override;
   void clear_so (const solib &so) const override;
   void clear_solib (program_space *pspace) const override;
-  void create_inferior_hook (int from_tty) const override;
-  owning_intrusive_list<solib> current_sos () const override;
+  void create_inferior_hook (int from_tty) override;
+  owning_intrusive_list<solib> current_sos () override;
   bool open_symbol_file_object (int from_tty) const override;
   bool in_dynsym_resolve_code (CORE_ADDR pc) const override;
   bool same (const solib &gdb, const solib &inferior) const override;
   bool keep_data_in_core (CORE_ADDR vaddr, unsigned long size) const override;
   void update_breakpoints () const override;
-  void handle_event () const override;
+  void handle_event () override;
   std::optional<CORE_ADDR> find_solib_addr (solib &so) const override;
   bool supports_namespaces () const override { return true; }
   int find_solib_ns (const solib &so) const override;
@@ -135,7 +135,7 @@ struct svr4_solib_ops : public solib_ops
   void free_probes_table (svr4_info *info) const;
   CORE_ADDR find_r_brk (svr4_info *info) const;
   CORE_ADDR find_r_ldsomap (svr4_info *info) const;
-  owning_intrusive_list<solib> default_sos (svr4_info *info) const;
+  owning_intrusive_list<solib> default_sos (svr4_info *info);
   int read_so_list (svr4_info *info, CORE_ADDR lm, CORE_ADDR prev_lm,
 		    CORE_ADDR debug_base, std::vector<svr4_so> &sos,
 		    int ignore_first) const;
@@ -144,10 +144,10 @@ struct svr4_solib_ops : public solib_ops
   CORE_ADDR lm_addr_check (const solib &so, bfd *abfd) const;
   CORE_ADDR read_r_next (CORE_ADDR debug_base) const;
   CORE_ADDR read_r_map (CORE_ADDR debug_base) const;
-  owning_intrusive_list<solib> collect_probes_sos (svr4_info *info) const;
-  owning_intrusive_list<solib> current_sos_1 (svr4_info *info) const;
+  owning_intrusive_list<solib> collect_probes_sos (svr4_info *info);
+  owning_intrusive_list<solib> current_sos_1 (svr4_info *info);
   owning_intrusive_list<solib> solibs_from_svr4_sos
-    (const std::vector<svr4_so> &sos) const;
+    (const std::vector<svr4_so> &sos);
   void disable_probes_interface (svr4_info *info) const;
   void update_full (svr4_info *info) const;
   int update_incremental (svr4_info *info, CORE_ADDR debug_base,
diff --git a/gdb/solib-target.c b/gdb/solib-target.c
index 36654a76fc3e..9707596725d4 100644
--- a/gdb/solib-target.c
+++ b/gdb/solib-target.c
@@ -232,7 +232,7 @@ solib_target_parse_libraries (const char *library)
 #endif
 
 owning_intrusive_list<solib>
-target_solib_ops::current_sos () const
+target_solib_ops::current_sos ()
 {
   owning_intrusive_list<solib> sos;
 
diff --git a/gdb/solib-target.h b/gdb/solib-target.h
index 4b8f2d6ad5cb..e3c1d87684f6 100644
--- a/gdb/solib-target.h
+++ b/gdb/solib-target.h
@@ -29,7 +29,7 @@ struct target_solib_ops : solib_ops
   using solib_ops::solib_ops;
 
   void relocate_section_addresses (solib &so, target_section *) const override;
-  owning_intrusive_list<solib> current_sos () const override;
+  owning_intrusive_list<solib> current_sos () override;
   bool in_dynsym_resolve_code (CORE_ADDR pc) const override;
 };
 
diff --git a/gdb/solib.c b/gdb/solib.c
index ac674f4e9720..cc587aa8158b 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -467,7 +467,7 @@ solib_bfd_open (const char *pathname)
 }
 
 gdb_bfd_ref_ptr
-solib_ops::bfd_open (const char *pathname) const
+solib_ops::bfd_open (const char *pathname)
 {
   return solib_bfd_open (pathname);
 }
@@ -718,7 +718,7 @@ notify_solib_unloaded (program_space *pspace, const solib &so,
 void
 update_solib_list (int from_tty)
 {
-  const solib_ops *ops = current_program_space->solib_ops ();
+  solib_ops *ops = current_program_space->solib_ops ();
 
   if (ops == nullptr)
     return;
@@ -1318,7 +1318,7 @@ clear_solib (program_space *pspace)
 void
 solib_create_inferior_hook (int from_tty)
 {
-  if (const solib_ops *ops = current_program_space->solib_ops ();
+  if (solib_ops *ops = current_program_space->solib_ops ();
       ops != nullptr)
     ops->create_inferior_hook (from_tty);
 }
@@ -1383,7 +1383,7 @@ update_solib_breakpoints (void)
 void
 handle_solib_event (void)
 {
-  if (const solib_ops *ops = current_program_space->solib_ops ();
+  if (solib_ops *ops = current_program_space->solib_ops ();
       ops != nullptr)
     ops->handle_event ();
 
diff --git a/gdb/solib.h b/gdb/solib.h
index 92a485dbca4c..512fdf7bce96 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -62,7 +62,7 @@ struct solib : intrusive_list_node<solib>
 
      OPS is the solib_ops implementation providing this solib.  */
   explicit solib (lm_info_up lm_info, std::string original_name,
-		  std::string name, const solib_ops &ops)
+		  std::string name, solib_ops &ops)
     : lm_info (std::move (lm_info)),
       original_name (std::move (original_name)),
       name (std::move (name)),
@@ -70,7 +70,7 @@ struct solib : intrusive_list_node<solib>
   {}
 
   /* Return the solib_ops implementation providing this solib.  */
-  const solib_ops &ops () const
+  solib_ops &ops () const
   { return *m_ops; }
 
   /* Free symbol-file related contents of SO and reset for possible reloading
@@ -131,7 +131,7 @@ struct solib : intrusive_list_node<solib>
 
 private:
   /* The solib_ops responsible for this solib.  */
-  const solib_ops *m_ops;
+  solib_ops *m_ops;
 };
 
 /* A unique pointer to an solib.  */
@@ -173,7 +173,7 @@ struct solib_ops
   /* Target dependent code to run after child process fork.
 
      Defaults to no-op.  */
-  virtual void create_inferior_hook (int from_tty) const {};
+  virtual void create_inferior_hook (int from_tty) {};
 
   /* Construct a list of the currently loaded shared objects.  This
      list does not include an entry for the main executable file.
@@ -182,7 +182,7 @@ struct solib_ops
      inferior --- we don't examine any of the shared library files
      themselves.  The declaration of `struct solib' says which fields
      we provide values for.  */
-  virtual owning_intrusive_list<solib> current_sos () const = 0;
+  virtual owning_intrusive_list<solib> current_sos () = 0;
 
   /* Find, open, and read the symbols for the main executable.  If
      FROM_TTY is non-zero, allow messages to be printed.
@@ -198,7 +198,7 @@ struct solib_ops
   { return false; };
 
   /* Find and open shared library binary file.  */
-  virtual gdb_bfd_ref_ptr bfd_open (const char *pathname) const;
+  virtual gdb_bfd_ref_ptr bfd_open (const char *pathname);
 
   /* Given two solib objects, GDB from the GDB thread list and INFERIOR from the
      list returned by current_sos, return true if they represent the same library.
@@ -226,7 +226,7 @@ struct solib_ops
      solib_add is called.
 
      Defaults to no-op.  */
-  virtual void handle_event () const {};
+  virtual void handle_event () {};
 
   /* Return an address within the inferior's address space which is known
      to be part of SO.  If there is no such address, or GDB doesn't know
diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c
index 68d40b8373af..e2dfe4a8c93e 100644
--- a/gdb/windows-tdep.c
+++ b/gdb/windows-tdep.c
@@ -831,7 +831,7 @@ struct windows_solib_ops : target_solib_ops
 {
   using target_solib_ops::target_solib_ops;
 
-  void create_inferior_hook (int from_tty) const override;
+  void create_inferior_hook (int from_tty) override;
   void iterate_over_objfiles_in_search_order
     (iterate_over_objfiles_in_search_order_cb_ftype cb,
      objfile *current_objfile) const override;
@@ -848,7 +848,7 @@ make_windows_solib_ops (program_space *pspace)
 /* Implement the "solib_create_inferior_hook" solib_ops method.  */
 
 void
-windows_solib_ops::create_inferior_hook (int from_tty) const
+windows_solib_ops::create_inferior_hook (int from_tty)
 {
   CORE_ADDR exec_base = 0;
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (4 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 05/11] gdb: de-constify some methods of solib_ops Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 16:19   ` Tom Tromey
  2025-12-09 19:32 ` [PATCH 07/11] gdb/solib-rocm: save inferior in rocm_solib_ops Simon Marchi
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

Move the data currently stored in an inferior registry directly into
rocm_solib_ops.  This changes the storage of this data from "per
inferior" to "per program-space", but it should be equivalent, since
there is usually a 1:1 mapping between those two.  The only time there
isn't a 1:1 mapping is during a vfork, before the exec/exit, but no ROCm
activity happens during that slice of time.

Function rocm_update_solib_list becomes
rocm_solib_ops::update_solib_list.

Function rocm_bfd_iovec_open becomes rocm_solib_ops::bfd_iovec_open.

Most of the changes consist of accessing the fields directly, instead of
going through the solib_info object.

The rocm_solib_ops constructor has to take an inferior as a parameter,
rather than a program_space, because of the fd cache.

Change-Id: I386139008b6f0eca1d897fc35be18faa9d5a104e
---
 gdb/solib-rocm.c | 95 +++++++++++++++++-------------------------------
 1 file changed, 33 insertions(+), 62 deletions(-)

diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index 953d31d6c77b..81a1c0ab64c1 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -178,30 +178,15 @@ struct rocm_so
   lm_info_svr4_up lm_info;
 };
 
-struct solib_info
-{
-  explicit solib_info (inferior *inf)
-    : fd_cache (inf)
-  {};
-
-  /* List of code objects loaded into the inferior.  */
-  std::vector<rocm_so> solib_list;
-
-  /* Cache of opened FD in the inferior.  */
-  rocm_solib_fd_cache fd_cache;
-};
-
-/* Per-inferior data key.  */
-static const registry<inferior>::key<solib_info> rocm_solib_data;
-
 /* solib_ops for ROCm systems.  */
 
 struct rocm_solib_ops : public solib_ops
 {
   /* HOST_OPS is the host solib_ops that rocm_solib_ops hijacks / wraps,
      in order to provide support for ROCm code objects.  */
-  explicit rocm_solib_ops (program_space *pspace, solib_ops_up host_ops)
-    : solib_ops (pspace), m_host_ops (std::move (host_ops))
+  explicit rocm_solib_ops (inferior *inf, solib_ops_up host_ops)
+    : solib_ops (inf->pspace), m_host_ops (std::move (host_ops)),
+      m_fd_cache (inf)
   {
     gdb_assert (m_host_ops != nullptr);
     gdb_assert (dynamic_cast<rocm_solib_ops *> (m_host_ops.get ()) == nullptr);
@@ -218,6 +203,10 @@ struct rocm_solib_ops : public solib_ops
   void relocate_section_addresses (solib &so, target_section *) const override;
   void handle_event () override;
 
+  /* This needs to be public because it is called from
+     rocm_solib_target_inferior_created.  */
+  void update_solib_list ();
+
   /* Implement the following methods just to forward the calls to the host
      solib_ops.  We currently need to implement all the methods that
      svr4_solib_ops implements.  */
@@ -268,23 +257,17 @@ struct rocm_solib_ops : public solib_ops
 private:
   owning_intrusive_list<solib>
   solibs_from_rocm_sos (const std::vector<rocm_so> &sos);
+  gdb_bfd_iovec_base *bfd_iovec_open (bfd *abfd, inferior *inferior);
 
   solib_ops_up m_host_ops;
+
+  /* List of code objects loaded into the inferior.  */
+  std::vector<rocm_so> m_solib_list;
+
+  /* Cache of opened FD in the inferior.  */
+  rocm_solib_fd_cache m_fd_cache;
 };
 
-/* Fetch the solib_info data for INF.  */
-
-static struct solib_info *
-get_solib_info (inferior *inf)
-{
-  solib_info *info = rocm_solib_data.get (inf);
-
-  if (info == nullptr)
-    info = rocm_solib_data.emplace (inf, inf);
-
-  return info;
-}
-
 /* Relocate section addresses.  */
 
 void
@@ -302,8 +285,6 @@ rocm_solib_ops::relocate_section_addresses (solib &so,
   sec->endaddr = sec->endaddr + li->l_addr;
 }
 
-static void rocm_update_solib_list ();
-
 void
 rocm_solib_ops::handle_event ()
 {
@@ -315,7 +296,7 @@ rocm_solib_ops::handle_event ()
      previously loaded).  */
   m_host_ops->handle_event ();
 
-  rocm_update_solib_list ();
+  this->update_solib_list ();
 }
 
 /* Create solib objects from rocm_so objects in SOS.  */
@@ -342,12 +323,10 @@ rocm_solib_ops::current_sos ()
   owning_intrusive_list<solib> sos = m_host_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 ())
+  if (m_solib_list.empty ())
     return sos;
 
-  owning_intrusive_list<solib> dev_solibs = solibs_from_rocm_sos (dev_sos);
+  owning_intrusive_list<solib> dev_solibs = solibs_from_rocm_sos (m_solib_list);
 
   if (sos.empty ())
     return dev_solibs;
@@ -527,8 +506,8 @@ rocm_code_object_stream_memory::read (bfd *, void *buf, file_ptr size,
 
 } /* anonymous namespace */
 
-static gdb_bfd_iovec_base *
-rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
+gdb_bfd_iovec_base *
+rocm_solib_ops::bfd_iovec_open (bfd *abfd, inferior *inferior)
 {
   std::string_view uri (bfd_get_filename (abfd));
   std::string_view protocol_delim = "://";
@@ -621,9 +600,8 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
 
       if (protocol == "file")
 	{
-	  auto info = get_solib_info (inferior);
 	  fileio_error target_errno;
-	  auto fd = info->fd_cache.open (decoded_path, &target_errno);
+	  auto fd = m_fd_cache.open (decoded_path, &target_errno);
 
 	  if (fd.fd == -1)
 	    {
@@ -683,9 +661,9 @@ rocm_solib_ops::bfd_open (const char *pathname)
   if (strstr (pathname, "://") == nullptr)
     return m_host_ops->bfd_open (pathname);
 
-  auto open = [] (bfd *nbfd) -> gdb_bfd_iovec_base *
+  auto open = [this] (bfd *nbfd)
   {
-    return rocm_bfd_iovec_open (nbfd, current_inferior ());
+    return this->bfd_iovec_open (nbfd, current_inferior ());
   };
 
   gdb_bfd_ref_ptr abfd = gdb_bfd_openr_iovec (pathname, "elf64-amdgcn", open);
@@ -767,13 +745,12 @@ rocm_solib_ops::bfd_open (const char *pathname)
 void
 rocm_solib_ops::create_inferior_hook (int from_tty)
 {
-  get_solib_info (current_inferior ())->solib_list.clear ();
-
+  m_solib_list.clear ();
   m_host_ops->create_inferior_hook (from_tty);
 }
 
-static void
-rocm_update_solib_list ()
+void
+rocm_solib_ops::update_solib_list ()
 {
   inferior *inf = current_inferior ();
 
@@ -781,10 +758,7 @@ rocm_update_solib_list ()
   if (process_id.handle == AMD_DBGAPI_PROCESS_NONE.handle)
     return;
 
-  solib_info *info = get_solib_info (inf);
-
-  info->solib_list.clear ();
-  std::vector<rocm_so> &sos = info->solib_list;
+  m_solib_list.clear ();
 
   amd_dbgapi_code_object_id_t *code_object_list;
   size_t count;
@@ -831,7 +805,8 @@ rocm_update_solib_list ()
       std::string unique_name
 	= string_printf ("code_object_%ld", code_object_list[i].handle);
 
-      sos.emplace_back (uri_bytes, std::move (unique_name), std::move (li));
+      m_solib_list.emplace_back (uri_bytes, std::move (unique_name),
+				 std::move (li));
     }
 }
 
@@ -842,17 +817,15 @@ rocm_solib_target_inferior_created (inferior *inf)
   if (inf->vfork_parent != nullptr)
     return;
 
-  get_solib_info (inf)->solib_list.clear ();
-
   auto prev_ops = inf->pspace->release_solib_ops ();
-  auto rocm_ops
-    = std::make_unique<rocm_solib_ops> (inf->pspace, std::move (prev_ops));
+  auto rocm_ops = std::make_unique<rocm_solib_ops> (inf, std::move (prev_ops));
   inf->pspace->set_solib_ops (std::move (rocm_ops));
 
-  rocm_update_solib_list ();
+  gdb::checked_static_cast<rocm_solib_ops *>
+    (inf->pspace->solib_ops ())->update_solib_list ();
 
   /* Force GDB to reload the solibs.  */
-  current_inferior ()->pspace->clear_solib_cache ();
+  inf->pspace->clear_solib_cache ();
   solib_add (nullptr, 0, auto_solib_add);
 }
 
@@ -867,10 +840,8 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
   auto pspace = follow_inf->pspace;
   auto prev_ops = pspace->release_solib_ops ();
   auto rocm_ops
-    = std::make_unique<rocm_solib_ops> (pspace, std::move (prev_ops));
+    = std::make_unique<rocm_solib_ops> (follow_inf, std::move (prev_ops));
   pspace->set_solib_ops (std::move (rocm_ops));
-
-  get_solib_info (exec_inf)->solib_list.clear ();
 }
 
 static void
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 07/11] gdb/solib-rocm: save inferior in rocm_solib_ops
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (5 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
       [not found]   ` <87a4unm59w.fsf@tromey.com>
  2025-12-09 19:32 ` [PATCH 08/11] gdb/solib: add remove_solib function Simon Marchi
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

Saving the inferior the rocm_solib_ops is for can avoid using
`current_inferior ()` at a couple of places.

Change-Id: Icf36bbef031eda7b776cd735a91a2ff918697dd3
---
 gdb/solib-rocm.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index 81a1c0ab64c1..a1bbb1a54f42 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -186,7 +186,7 @@ struct rocm_solib_ops : public solib_ops
      in order to provide support for ROCm code objects.  */
   explicit rocm_solib_ops (inferior *inf, solib_ops_up host_ops)
     : solib_ops (inf->pspace), m_host_ops (std::move (host_ops)),
-      m_fd_cache (inf)
+      m_inf (inf), m_fd_cache (inf)
   {
     gdb_assert (m_host_ops != nullptr);
     gdb_assert (dynamic_cast<rocm_solib_ops *> (m_host_ops.get ()) == nullptr);
@@ -257,10 +257,13 @@ struct rocm_solib_ops : public solib_ops
 private:
   owning_intrusive_list<solib>
   solibs_from_rocm_sos (const std::vector<rocm_so> &sos);
-  gdb_bfd_iovec_base *bfd_iovec_open (bfd *abfd, inferior *inferior);
+  gdb_bfd_iovec_base *bfd_iovec_open (bfd *abfd);
 
   solib_ops_up m_host_ops;
 
+  /* Inferior this rocm_solib_ops is for.  */
+  inferior *m_inf;
+
   /* List of code objects loaded into the inferior.  */
   std::vector<rocm_so> m_solib_list;
 
@@ -507,7 +510,7 @@ rocm_code_object_stream_memory::read (bfd *, void *buf, file_ptr size,
 } /* anonymous namespace */
 
 gdb_bfd_iovec_base *
-rocm_solib_ops::bfd_iovec_open (bfd *abfd, inferior *inferior)
+rocm_solib_ops::bfd_iovec_open (bfd *abfd)
 {
   std::string_view uri (bfd_get_filename (abfd));
   std::string_view protocol_delim = "://";
@@ -617,7 +620,7 @@ rocm_solib_ops::bfd_iovec_open (bfd *abfd, inferior *inferior)
       if (protocol == "memory")
 	{
 	  ULONGEST pid = try_strtoulst (path);
-	  if (pid != inferior->pid)
+	  if (pid != m_inf->pid)
 	    {
 	      warning (_("`%s': code object is from another inferior"),
 		       std::string (uri).c_str ());
@@ -663,7 +666,7 @@ rocm_solib_ops::bfd_open (const char *pathname)
 
   auto open = [this] (bfd *nbfd)
   {
-    return this->bfd_iovec_open (nbfd, current_inferior ());
+    return this->bfd_iovec_open (nbfd);
   };
 
   gdb_bfd_ref_ptr abfd = gdb_bfd_openr_iovec (pathname, "elf64-amdgcn", open);
@@ -752,9 +755,7 @@ rocm_solib_ops::create_inferior_hook (int from_tty)
 void
 rocm_solib_ops::update_solib_list ()
 {
-  inferior *inf = current_inferior ();
-
-  amd_dbgapi_process_id_t process_id = get_amd_dbgapi_process_id (inf);
+  amd_dbgapi_process_id_t process_id = get_amd_dbgapi_process_id (m_inf);
   if (process_id.handle == AMD_DBGAPI_PROCESS_NONE.handle)
     return;
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 08/11] gdb/solib: add remove_solib function
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (6 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 07/11] gdb/solib-rocm: save inferior in rocm_solib_ops Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 16:23   ` Tom Tromey
  2025-12-09 19:32 ` [PATCH 09/11] gdb: add objfile -> solib backlink Simon Marchi
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

Factor out the code to remove an solib into a new remove_solib function.
This function is used by a patch later in this series.

This patch only moves code, so no behavior change expected.

Change-Id: I6bb901c954cdcf900a3f09a4c4d1ba44203f1ed0
---
 gdb/solib.c | 58 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/gdb/solib.c b/gdb/solib.c
index cc587aa8158b..9f6cc26b63b4 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -713,6 +713,39 @@ notify_solib_unloaded (program_space *pspace, const solib &so,
   gdb::observers::solib_unloaded.notify (pspace, so, still_in_use, silent);
 }
 
+/* Remove solib *SOLIB_IT from PSPACE.
+
+   Remove the corresponding objfiles and target sections.
+
+   Return an iterator to the next solib in PSPACE's solib list, helping to
+   continue iterating.  */
+
+static owning_intrusive_list<solib>::iterator
+remove_solib (program_space *pspace,
+	      owning_intrusive_list<solib>::iterator solib_it)
+{
+  bool still_in_use
+    = solib_it->objfile != nullptr && solib_used (pspace, *solib_it);
+
+  /* Notify any observer that the shared object has been unloaded before we
+     remove it from GDB's tables.  */
+  notify_solib_unloaded (pspace, *solib_it, still_in_use, false);
+
+  /* Unless the user loaded it explicitly, free SO's objfile.  */
+  if (solib_it->objfile != nullptr
+      && !(solib_it->objfile->flags & OBJF_USERLOADED)
+      && !still_in_use)
+    solib_it->objfile->unlink ();
+
+  pspace->deleted_solibs.push_back (solib_it->name);
+
+  /* Some targets' section tables might be referring to
+     sections from so.abfd; remove them.  */
+  pspace->remove_target_sections (&*solib_it);
+
+  return pspace->solibs ().erase (solib_it);
+}
+
 /* See solib.h.  */
 
 void
@@ -796,30 +829,7 @@ update_solib_list (int from_tty)
 
       /* If it's not on the inferior's list, remove it from GDB's tables.  */
       else
-	{
-	  bool still_in_use
-	    = (gdb_iter->objfile != nullptr
-	       && solib_used (current_program_space, *gdb_iter));
-
-	  /* 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_iter,
-				 still_in_use, false);
-
-	  /* Unless the user loaded it explicitly, free SO's objfile.  */
-	  if (gdb_iter->objfile != nullptr
-	      && !(gdb_iter->objfile->flags & OBJF_USERLOADED)
-	      && !still_in_use)
-	    gdb_iter->objfile->unlink ();
-
-	  current_program_space->deleted_solibs.push_back (gdb_iter->name);
-
-	  /* Some targets' section tables might be referring to
-	     sections from so.abfd; remove them.  */
-	  current_program_space->remove_target_sections (&*gdb_iter);
-
-	  gdb_iter = current_program_space->solibs ().erase (gdb_iter);
-	}
+	gdb_iter = remove_solib (current_program_space, gdb_iter);
     }
 
   /* Now the inferior's list contains only shared objects that don't
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 09/11] gdb: add objfile -> solib backlink
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (7 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 08/11] gdb/solib: add remove_solib function Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 16:28   ` Tom Tromey
  2025-12-09 19:32 ` [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile Simon Marchi
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

The following "multiple solib_ops" patch will require accessing the
solib(s) related to an objfile fairly often.  To make that easier, add
an objfile to solib backlink.

However, I learned that there may be more than one solib linked to one
objfile, so this backlink is actually a vector.

Rename find_solib_for_objfile to find_one_solib_for_objfile to reflect
that this function returns just one of possible multiple solibs for the
given objfile (if that ever matters).  To be clear, this doesn't change
its behavior, this is what it was doing before this patch.

Change-Id: I427d19407fdbc274b1790e0cae3004ee5fdbea72
---
 gdb/objfiles.h   | 23 +++++++++++++++++++++++
 gdb/solib-svr4.c | 16 ++++++++--------
 gdb/solib.c      | 13 +++++++++----
 3 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index f65f3bde930d..9e4a800dda9e 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -469,6 +469,25 @@ struct objfile : intrusive_list_node<objfile>
   /* Return the program space associated with this objfile.  */
   program_space *pspace () { return m_pspace; }
 
+  /* Return the list of struct solib associated to this objfile.  */
+  std::vector<solib *> &solibs () { return m_solibs; }
+
+  /* Add SOLIB to the list of solibs associated to this objfile.  */
+  void add_solib (solib &solib)
+  {
+    gdb_assert (std::find (m_solibs.begin (), m_solibs.end (), &solib)
+		== m_solibs.end ());
+    m_solibs.emplace_back (&solib);
+  }
+
+  /* Remove SOLIB from the list of solibs associated to this objfile.  */
+  void remove_solib (solib &solib)
+  {
+    auto it = std::find (m_solibs.begin (), m_solibs.end (), &solib);
+    gdb_assert (it != m_solibs.end ());
+    m_solibs.erase (it);
+  }
+
   using compunit_symtab_iterator
     = owning_intrusive_list<compunit_symtab>::iterator;
   using compunit_symtab_range = iterator_range<compunit_symtab_iterator>;
@@ -715,6 +734,10 @@ struct objfile : intrusive_list_node<objfile>
 
   program_space *m_pspace;
 
+  /* The solibs associated to this objfile.  This is a 1 objfile to N solibs
+     relationship (see solib.c for details).  */
+  std::vector<solib *> m_solibs;
+
 public:
   /* The object file's BFD.  Can be null if the objfile contains only
      minimal symbols (e.g. the run time common symbols for SunOS4) or
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 7fd067ff38ab..f0f13c16bb03 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -3595,10 +3595,10 @@ lp64_svr4_solib_ops::fetch_link_map_offsets () const
 }
 \f
 
-/* Return the DSO matching OBJFILE or nullptr if none can be found.  */
+/* Return one solib matching OBJFILE or nullptr if none can be found.  */
 
-static const solib *
-find_solib_for_objfile (struct objfile *objfile)
+static solib *
+find_one_solib_for_objfile (struct objfile *objfile)
 {
   if (objfile == nullptr)
     return nullptr;
@@ -3608,9 +3608,9 @@ find_solib_for_objfile (struct objfile *objfile)
   if (objfile->separate_debug_objfile_backlink != nullptr)
     objfile = objfile->separate_debug_objfile_backlink;
 
-  for (const solib &so : current_program_space->solibs ())
-    if (so.objfile == objfile)
-      return &so;
+  if (const auto &solibs = objfile->solibs ();
+      !solibs.empty ())
+    return solibs.front ();
 
   return nullptr;
 }
@@ -3679,7 +3679,7 @@ svr4_solib_ops::iterate_over_objfiles_in_search_order
      r_debug object, defaulting to the initial namespace.  */
   svr4_info *info = get_svr4_info (current_program_space);
   CORE_ADDR default_debug_base = this->default_debug_base (info);
-  const solib *curr_solib = find_solib_for_objfile (current_objfile);
+  const solib *curr_solib = find_one_solib_for_objfile (current_objfile);
   CORE_ADDR debug_base = find_debug_base_for_solib (curr_solib);
   if (debug_base == 0)
     debug_base = default_debug_base;
@@ -3694,7 +3694,7 @@ svr4_solib_ops::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.  */
-      const solib *solib = find_solib_for_objfile (&objfile);
+      const solib *solib = find_one_solib_for_objfile (&objfile);
       CORE_ADDR solib_base = find_debug_base_for_solib (solib);
       if (solib_base == 0)
 	solib_base = default_debug_base;
diff --git a/gdb/solib.c b/gdb/solib.c
index 9f6cc26b63b4..33dd4b2b1a32 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -662,6 +662,7 @@ solib_read_symbols (solib &so, symfile_add_flags flags)
 	  so.objfile->addr_low = so.addr_low;
 	}
 
+      so.objfile->add_solib (so);
       so.symbols_loaded = true;
     }
   catch (const gdb_exception_error &e)
@@ -732,10 +733,14 @@ remove_solib (program_space *pspace,
   notify_solib_unloaded (pspace, *solib_it, still_in_use, false);
 
   /* Unless the user loaded it explicitly, free SO's objfile.  */
-  if (solib_it->objfile != nullptr
-      && !(solib_it->objfile->flags & OBJF_USERLOADED)
-      && !still_in_use)
-    solib_it->objfile->unlink ();
+  if (solib_it->objfile != nullptr)
+    {
+      /* Remove the objfile -> solib backlink.  */
+      solib_it->objfile->remove_solib (*solib_it);
+
+      if (!(solib_it->objfile->flags & OBJF_USERLOADED) && !still_in_use)
+	solib_it->objfile->unlink ();
+    }
 
   pspace->deleted_solibs.push_back (solib_it->name);
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (8 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 09/11] gdb: add objfile -> solib backlink Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 16:42   ` Tom Tromey
  2025-12-09 19:32 ` [PATCH 11/11] gdb: multiple solib_ops per program space Simon Marchi
  2026-04-28 16:00 ` [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

The current default objfile search strategy, defined both in
solib_ops::iterate_over_objfiles_in_search_order (when the solib_ops
hasn't overridden it) and in
program_space::iterate_over_objfiles_in_search_order (when there is no
solib_ops in the program space, for some reason), is to scan the objfile
list linearly.

program_space::iterate_over_objfiles_in_search_order:

    for (auto &objfile : this->objfiles ())
      if (cb (&objfile))
        return;

solib_ops::iterate_over_objfiles_in_search_order:

    for (objfile &objfile : m_pspace->objfiles ())
      if (cb (&objfile))
        return;

The following patch adds support for multiple concurrent solib_ops in a
program space.  Everything that deals with an solib_ops needs to be
adapted to deal with multiple solib_ops, including
program_space::iterate_over_objfiles_in_search_order.  What I came up
with for that method introduces a change in behavior where the current
objfile will always be searched first.  Rather than hide the change in
behavior in that big patch, do it in this preparatory patch, in the hope
that any potential problems it causes can be more easily bisected and
analyzed.

Concretely, the change is to check the current objfile first, in the two
spots:

    if (current_objfile != nullptr && cb (current_objfile))
      return;

This happens to be what windows_solib_ops did, so remove that
specialization.

svr4_solib_ops already has its own iterate_over_objfiles_in_search_order
that conditionally checks the current objfile first, based on the
DT_SYMBOLIC dynamic tag.  So it is not affected by this change.

The solib_ops potentially affected by this change are (we know that
rocm_solib_ops doesn't care):

 - frv_solib_ops
 - target_solib_ops
 - darwin_solib_ops
 - dsbt_solib_ops
 - aix_solib_ops

I don't really know if searching objfiles in order (so, main objfile
first typically) is essential for any of these solib_ops.
Intuitively, searching the current objfile first makes sense, unless
there is some fancy symbol interposition happening, in which case that
solib_ops should probably implement a custom
iterate_over_objfiles_in_search_order.

Change-Id: I58195af6b53ca202e6061a5f36d5f74e1a0d5c17
---
 gdb/progspace.c    |  5 ++++-
 gdb/solib.c        |  5 ++++-
 gdb/windows-tdep.c | 40 ----------------------------------------
 3 files changed, 8 insertions(+), 42 deletions(-)

diff --git a/gdb/progspace.c b/gdb/progspace.c
index 4017d0f4f27b..e7d6e634c168 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -153,8 +153,11 @@ program_space::iterate_over_objfiles_in_search_order
     return m_solib_ops->iterate_over_objfiles_in_search_order
       (cb, current_objfile);
 
+  if (current_objfile != nullptr && cb (current_objfile))
+    return;
+
   for (auto &objfile : this->objfiles ())
-    if (cb (&objfile))
+    if (&objfile != current_objfile && cb (&objfile))
       return;
 }
 
diff --git a/gdb/solib.c b/gdb/solib.c
index 33dd4b2b1a32..a18f9b5bd0ec 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -479,8 +479,11 @@ solib_ops::iterate_over_objfiles_in_search_order
   (iterate_over_objfiles_in_search_order_cb_ftype cb,
    objfile *current_objfile) const
 {
+  if (current_objfile != nullptr && cb (current_objfile))
+    return;
+
   for (objfile &objfile : m_pspace->objfiles ())
-    if (cb (&objfile))
+    if (&objfile != current_objfile && cb (&objfile))
       return;
 }
 
diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c
index e2dfe4a8c93e..1df19a70adbf 100644
--- a/gdb/windows-tdep.c
+++ b/gdb/windows-tdep.c
@@ -832,9 +832,6 @@ struct windows_solib_ops : target_solib_ops
   using target_solib_ops::target_solib_ops;
 
   void create_inferior_hook (int from_tty) override;
-  void iterate_over_objfiles_in_search_order
-    (iterate_over_objfiles_in_search_order_cb_ftype cb,
-     objfile *current_objfile) const override;
 };
 
 /* Return a new solib_ops for Windows systems.  */
@@ -893,43 +890,6 @@ windows_solib_ops::create_inferior_hook (int from_tty)
     }
 }
 
-/* Implement the "iterate_over_objfiles_in_search_order" gdbarch
-   method.  It searches all objfiles, starting with CURRENT_OBJFILE
-   first (if not NULL).
-
-   On Windows, the system behaves a little differently when two
-   objfiles each define a global symbol using the same name, compared
-   to other platforms such as GNU/Linux for instance.  On GNU/Linux,
-   all instances of the symbol effectively get merged into a single
-   one, but on Windows, they remain distinct.
-
-   As a result, it usually makes sense to start global symbol searches
-   with the current objfile before expanding it to all other objfiles.
-   This helps for instance when a user debugs some code in a DLL that
-   refers to a global variable defined inside that DLL.  When trying
-   to print the value of that global variable, it would be unhelpful
-   to print the value of another global variable defined with the same
-   name, but in a different DLL.  */
-
-void
-windows_solib_ops::iterate_over_objfiles_in_search_order
-  (iterate_over_objfiles_in_search_order_cb_ftype cb,
-   objfile *current_objfile) const
-{
-  if (current_objfile)
-    {
-      if (cb (current_objfile))
-	return;
-    }
-
-  for (objfile &objfile : m_pspace->objfiles ())
-    if (&objfile != current_objfile)
-      {
-	if (cb (&objfile))
-	  return;
-      }
-}
-
 /* Common parts for gdbarch initialization for the Windows and Cygwin OS
    ABIs.  */
 
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 11/11] gdb: multiple solib_ops per program space
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (9 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile Simon Marchi
@ 2025-12-09 19:32 ` Simon Marchi
  2026-04-28 17:27   ` Tom Tromey
  2026-04-28 16:00 ` [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2025-12-09 19:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

From: Simon Marchi <simon.marchi@polymtl.ca>

This patch adds the possibility for a program space to have multiple
solib_ops.  The motivation for this is to support ROCm (GPU) debugging
more cleanly.

Currently, when debugging a ROCm program, in order to be able to list
device code objects (the equivalent of shared libraries but for the
GPU), we install an instance of rocm_solib_ops as the program space's
sole solib_ops.  But in order to still be able to list host shared
libraries, the rocm_solib_ops wraps the previously installed solib_ops
(currently always an svr4_solib_ops instance) and forwards method calls
to it.

By allowing program spaces to use multiple solib_ops, each solib_ops can
work side by side and rocm_solib_ops won't need to care about the host
solib_ops.

This change starts by making program_space::m_solib_ops a vector of
solib_ops_up instead of a single solib_ops_up, and it propagates from
there.

program_space::set_solib_ops becomes program_space::add_solib_ops.  I
put in the restriction that there can be only one instance of a given
kind of solib_ops in the vector (it wouldn't make sense to have two
svr4_solib_ops instances in there, it would likely be a bug).

Remove program_space::unset_solib_ops, but add
program_space::remove_solib_ops (to remove a single solib_ops) and
program_space::clear_solib_ops (to remove all solib_ops).  Removing an
solib_ops now automatically removes all the solibs produced by that
solib_ops, to avoid having solibs with stale solib_ops backlinks.

Add program_space::find_solib_ops, which can be used to find the
instance of a given kind of solib_ops.

Then, the code in solib.c gets adapted to deal with the fact that there
might be multiple solib_ops.  The logic is done on a case by case basis:

 - update_solib_list: where we call open_symbol_file_object, iterate
   over all solib_ops until we find one that knows how to find and open
   the main executable

 - also update_solib_list: where we fetch the list of current SOs from
   the inferior and compare it against the list of SOs know to GDB,
   iterate over all solib_ops and fetch current SOs from all of them.
   When comparing the SOs fetched from the solib_ops with the SOs
   known to GDB, also compare the solib_ops before considering it a
   match.

 - print_solib_list_table: to determine whether to print the namespace
   column, iterate over all solib_ops until we find one for which
   supports_namespaces returns true and num_active_namespaces returns
   greater than 0.  If the namespace column is printed and some solibs
   are from an solib_ops that doesn't support namespaces, skip the
   column.

 - info_linker_namespace_command: iterate over all solib_ops.  For each
   solib_ops, print all namespaces.

 - solib_keep_data_in_core: iterate over all solib_ops until one says
   that the given memory range should be kept.

 - clear_solib: call clear_solib on all solib_ops

 - solib_create_inferior_hook: call create_inferior_hook on all
   solib_ops

 - in_solib_dynsym_resolve_code: iterate over all solib_ops until one
   says that the given PC falls into dynsym resolve code

 - update_solib_breakpoints: call update_breakpoints on all solib_ops

 - handle_solib_event: call handle_event on all solib_ops.  The event
   normally concern only one of those solib_ops, but at the moment we
   have no way to tell which solib_ops that is.  Perhaps, in the future,
   we can link bp_shlib_event breakpoints to which solib_ops the event
   is for, and call handle_event just for that solib_ops.  In the mean
   time, it is expected that for solib_ops not concerned by the event,
   calling handle_event will have no effect.

 - reload_shared_libraries: call clear_solib on all solib_ops

 - solib_linker_namespace_count: sum the number of active linker
   namespace over all solib_ops.  I don't know if this makes sense, but
   there is no known case today of two solib_ops supporting namespaces
   working side-by-side, so it's a theoretical problem for now.

With multiple solib_ops, the concept of
iterate_over_objfiles_in_search_order becomes a bit more complex.  The
implementation is mostly based on suppositions on my part.  The idea
implemented in this patch is to give priority to the solib_ops
responsible for an objfile when searching for a symbol.  If you're
stopped debugging some GPU code and look up a variable, and there is a
variable by that name both on the host and GPU side, I believe it is
more likely that you are interested in the GPU one.

Since we're going to ask multiple solib_ops to search successively,
change solib_ops::iterate_over_objfiles_in_search_order to return a
bool, to indicate if the search was successful or if we should continue.

Then, change solib_ops::iterate_over_objfiles_in_search_order (the
implementation used by all except svr4_solib_ops) to only search the
objfiles from that solib_ops as well as (optionally) the main objfile
(program_space::symfile_object_file).  The idea regarding the main
objfile is that it falls under the "linking domain" of the host-side
solib_ops, even though it wasn't produced by that solib_ops.
svr4_solib_ops has its own implementation of
iterate_over_objfiles_in_search_order, which does check the main
objfile.

Checking the main objfile is predicated by the HANDE_MAIN_OBJFILE
solib_ops constructor parameter, for which rocm_solib_ops passes false.

Then in program_space::iterate_over_objfiles_in_search_order, the search
strategy becomes:

  - if there is a current objfile:
    - if that objfile was contributed by an solib_ops:
      - call that solib_ops' iterate_over_objfiles_in_search_order
    - else:
      - search the current objfile by itself

If we didn't find anything interesting, we continue the search by asking
other solib_ops to search:

  - for all solib_ops except the one searched before (if any):
    - call that solib_ops' iterate_over_objfiles_in_search_order

Finally, "orphan" objfiles (not mapped to any solib) haven't been
searched (except possibly the current objfile), so:

  - for all objfile without an solib_ops backlink, except the current one (if any):
    - search that objfile

Note that if the current objfile is the main one
(program_space::symfile_object_file), then we won't enter the host
solib_ops::iterate_over_objfiles_in_search_order in the first part,
because that objfile is not associated to any solib, and therefore to
any solib_ops.  Instead, we will enter it in the first iteration of the
second part.  This relies on the fact that the host solib_ops will
always be first in the solib_ops list, with complementary solib_ops
following.  This is always true today, given the order in which
solib_ops instances are pushed.  Otherwise, we could go look for the
solib_ops for which M_HANDLE_MAIN_OBJFILE is true, but it didn't seem
like it was worth the extra complexity.

I wrote the gdb.rocm/symbol-lookup.exp test to try to exercise this
symbol lookup order thing.  The test program loads some shared libraries
and some ROCm code objects, then does some symbol lookups while stopped
in different places.

Finally, the goal of all this, rocm_solib_ops gets changed to not wrap a
host solib_ops anymore!  rocm_solib_ops previously had to use
lm_info_svr4, to avoid confusing svr4_solib_ops.  It doesn't have to
anymore, so introduce a simpler lm_info_rocm that just holds an address.

rocm_solib_ops no longer needs to implement a bunch of methods just to
forward the call to the host solib_ops, so remove them.  In the methods
that rocm_solib_ops actually needs to implement, it no longer needs to
call the host ops, which simplifies things.

Things also get simpler at the points where we push a new
rocm_solib_ops.

Change-Id: I260cdc0dcb9b11f33040059f300719dacac63d69
---
 gdb/infcmd.c                             |  16 +-
 gdb/inferior.h                           |   6 +-
 gdb/infrun.c                             |   4 +-
 gdb/progspace.c                          |  92 ++++++-
 gdb/progspace.h                          |  57 ++--
 gdb/solib-aix.c                          |   2 +-
 gdb/solib-darwin.c                       |   2 +-
 gdb/solib-dsbt.c                         |   2 +-
 gdb/solib-frv.c                          |   2 +-
 gdb/solib-rocm.c                         | 165 +++---------
 gdb/solib-svr4.c                         |  32 ++-
 gdb/solib-svr4.h                         |   6 +-
 gdb/solib-target.c                       |   2 +-
 gdb/solib.c                              | 268 +++++++++++--------
 gdb/solib.h                              |  30 ++-
 gdb/target.c                             |   2 +-
 gdb/testsuite/gdb.rocm/symbol-lookup.cpp | 320 +++++++++++++++++++++++
 gdb/testsuite/gdb.rocm/symbol-lookup.exp | 243 +++++++++++++++++
 gdb/windows-tdep.c                       |   2 +-
 19 files changed, 955 insertions(+), 298 deletions(-)
 create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.exp

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 3ba286738a00..f5214f0a2259 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -219,7 +219,7 @@ strip_bg_char (const char *args, int *bg_char_p)
 /* See inferior.h.  */
 
 void
-post_create_inferior (int from_tty, bool set_pspace_solib_ops)
+post_create_inferior (int from_tty, bool push_arch_solib_ops)
 {
   /* Be sure we own the terminal in case write operations are performed.  */
   target_terminal::ours_for_output ();
@@ -250,10 +250,16 @@ post_create_inferior (int from_tty, bool set_pspace_solib_ops)
 	throw;
     }
 
-  if (set_pspace_solib_ops)
-    current_program_space->set_solib_ops
-      (gdbarch_make_solib_ops (current_inferior ()->arch (),
-			       current_program_space));
+  if (push_arch_solib_ops)
+    {
+      /* This is called when an inferior gains execution.  Any solib_ops
+	 from previous executions should have been cleared by
+	 target_pre_inferior.  */
+      gdb_assert (current_program_space->solib_ops ().empty ());
+      current_program_space->add_solib_ops
+	(gdbarch_make_solib_ops (current_inferior ()->arch (),
+				 current_program_space));
+    }
 
   if (current_program_space->exec_bfd ())
     {
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 5b499a207b43..860aadcb4038 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -215,10 +215,10 @@ extern void setup_inferior (int from_tty);
    means (running, attaching, connecting, et cetera).  The target
    should be stopped.
 
-   If SET_PSPACE_SOLIB_OPS is true, initialize the program space's solib
-   provider using the current inferior's architecture.  */
+   If PUSH_ARCH_SOLIB_OPS is true, add an solib_ops to the current inferior's
+   program space provider using the current inferior's architecture.  */
 
-extern void post_create_inferior (int from_tty, bool set_pspace_solib_ops);
+extern void post_create_inferior (int from_tty, bool push_arch_solib_ops);
 
 extern void attach_command (const char *, int);
 
diff --git a/gdb/infrun.c b/gdb/infrun.c
index e0ffe95e4e45..f595192a34dd 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1324,7 +1324,7 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
      we don't want those to be satisfied by the libraries of the
      previous incarnation of this process.  */
   no_shared_libraries (current_program_space);
-  current_program_space->unset_solib_ops ();
+  current_program_space->clear_solib_ops ();
 
   inferior *execing_inferior = current_inferior ();
   inferior *following_inferior;
@@ -1381,7 +1381,7 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
      registers.  */
   target_find_description ();
 
-  following_inferior->pspace->set_solib_ops
+  following_inferior->pspace->add_solib_ops
     (gdbarch_make_solib_ops (following_inferior->arch (),
 			     following_inferior->pspace));
   gdb::observers::inferior_execd.notify (execing_inferior, following_inferior);
diff --git a/gdb/progspace.c b/gdb/progspace.c
index e7d6e634c168..6fb22b533c02 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -123,6 +123,42 @@ program_space::~program_space ()
 
 /* See progspace.h.  */
 
+void
+program_space::clear_solib_ops ()
+{
+  while (!m_solib_ops.empty ())
+    this->remove_solib_ops (*m_solib_ops.front ());
+}
+
+/* See progspace.h.  */
+
+void
+program_space::remove_solib_ops (const struct solib_ops &ops)
+{
+  auto ops_it
+    = std::find_if (m_solib_ops.begin (), m_solib_ops.end (),
+		    [&ops] (const auto &up) { return up.get () == &ops; });
+
+  gdb_assert (ops_it != m_solib_ops.end ());
+
+  /* Remove all solibs provided by the solib_ops being removed.  */
+  for (auto solibs_it = m_solib_list.begin ();
+       solibs_it != m_solib_list.end ();)
+    {
+      if (&solibs_it->ops () != &ops)
+	{
+	  ++solibs_it;
+	  continue;
+	}
+
+      solibs_it = remove_solib (this, solibs_it);
+    }
+
+  m_solib_ops.erase (ops_it);
+}
+
+/* See progspace.h.  */
+
 bool
 program_space::multi_objfile_p () const
 {
@@ -149,16 +185,58 @@ void
 program_space::iterate_over_objfiles_in_search_order
   (iterate_over_objfiles_in_search_order_cb_ftype cb, objfile *current_objfile)
 {
-  if (m_solib_ops != nullptr)
-    return m_solib_ops->iterate_over_objfiles_in_search_order
-      (cb, current_objfile);
+  struct solib_ops *curr_ops = nullptr;
 
-  if (current_objfile != nullptr && cb (current_objfile))
-    return;
+  if (current_objfile != nullptr)
+    {
+      if (!current_objfile->solibs ().empty ())
+	{
+	  /* If that objfile was created by an solib_ops, give a chance to that
+	     solib_ops to iterate the relevant objfiles first.
 
+	     If the objfile is associated to multiple struct solib, assume that
+	     they are all from the same solib_ops (so, get the ops from the
+	     first solib).  */
+	  curr_ops = &current_objfile->solibs ().front ()->ops ();
+
+	  if (curr_ops->iterate_over_objfiles_in_search_order (cb,
+							       current_objfile))
+	    return;
+	}
+      else
+	{
+	  /* Otherwise, search this objfile first.  */
+	  if (cb (current_objfile))
+	    return;
+	}
+    }
+
+  /* We didn't find what we were looking for based on the current objfile's
+     context, so expand the search.  Ask the other solib_ops.  */
+  for (auto &solib_ops : this->solib_ops ())
+    {
+      if (solib_ops.get () == curr_ops)
+	continue;
+
+      if (solib_ops->iterate_over_objfiles_in_search_order (cb, nullptr))
+	return;
+    }
+
+  /* Finally, look in the orphan objfiles.  */
   for (auto &objfile : this->objfiles ())
-    if (&objfile != current_objfile && cb (&objfile))
-      return;
+    {
+      /* The current objfile (if any) was already handled above.  */
+      if (&objfile == current_objfile)
+	continue;
+
+      /* If the obfile has any associated solibs, then it is not orphan, it has
+	 been handled above.  */
+      if (!objfile.solibs ().empty ())
+	continue;
+
+      if (cb (&objfile))
+	return;
+    }
 }
 
 /* See progspace.h.  */
diff --git a/gdb/progspace.h b/gdb/progspace.h
index bb4d6c79d16c..abad7f675f34 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -251,26 +251,51 @@ struct program_space
      is outside all objfiles in this progspace.  */
   struct objfile *objfile_for_address (CORE_ADDR address);
 
-  /* Set this program space's solib provider.
+  /* Add OPS as an solib provider for this program space.
 
-     The solib provider must be unset prior to calling this method.  */
-  void set_solib_ops (solib_ops_up ops)
+     There must not be another instance of SOLIB_OPS_TYPE in the solib_ops list
+     of this program space.
+
+     Return a non-owning reference to the ops.  */
+  template <typename solib_ops_type>
+  solib_ops_type &add_solib_ops (std::unique_ptr<solib_ops_type> ops)
   {
-    gdb_assert (m_solib_ops == nullptr);
-    m_solib_ops = std::move (ops);
+    gdb_assert (this->find_solib_ops<solib_ops_type> () == nullptr);
+
+    auto &ret = *ops;
+    m_solib_ops.emplace_back (std::move (ops));
+    return ret;
   };
 
-  /* Unset and free this program space's solib provider.  */
-  void unset_solib_ops ()
-  { m_solib_ops = nullptr; }
+  /* Clear the list of solib providers for this program space.
 
-  /* Unset and return this program space's solib provider.  */
-  solib_ops_up release_solib_ops ()
-  { return std::move (m_solib_ops); }
+     Remove all solibs from the program space.  */
+  void clear_solib_ops ();
 
-  /* Get this program space's solib provider.  */
-  struct solib_ops *solib_ops () const
-  { return m_solib_ops.get (); }
+  /* Remove OPS from the list of solib_ops of this program space.
+
+     OPS must be present in the solib_ops list.
+
+     Remove all solibs created by OPS from the program space.  */
+  void remove_solib_ops (const solib_ops &ops);
+
+  /* Return a view of the solib providers for this program space.  */
+  std::vector<solib_ops_up> &solib_ops ()
+  { return m_solib_ops; }
+
+  /* Find an solib_ops instance of type SOLIB_OPS_TYPE.
+
+     Return nullptr if not found.  */
+  template <typename solib_ops_type>
+  solib_ops_type *find_solib_ops ()
+  {
+    for (auto &ops : this->solib_ops ())
+      if (auto ret = dynamic_cast<solib_ops_type *> (ops.get ());
+	  ret != nullptr)
+	return ret;
+
+    return nullptr;
+  }
 
   /* Return the list of all the solibs in this program space.  */
   owning_intrusive_list<solib> &solibs ()
@@ -390,8 +415,8 @@ struct program_space
   /* All known objfiles are kept in a linked list.  */
   owning_intrusive_list<objfile> m_objfiles_list;
 
-  /* solib_ops implementation used to provide solibs in this program space.  */
-  solib_ops_up m_solib_ops;
+  /* solib_ops implementations used to provide solibs in this program space.  */
+  std::vector<solib_ops_up> m_solib_ops;
 
   /* List of shared objects mapped into this space.  Managed by
      solib.c.  */
diff --git a/gdb/solib-aix.c b/gdb/solib-aix.c
index a29e8639b3cd..d98fcccd720b 100644
--- a/gdb/solib-aix.c
+++ b/gdb/solib-aix.c
@@ -41,7 +41,7 @@ struct aix_solib_ops : public solib_ops
 solib_ops_up
 make_aix_solib_ops (program_space *pspace)
 {
-  return std::make_unique<aix_solib_ops> (pspace);
+  return std::make_unique<aix_solib_ops> (pspace, true);
 }
 
 /* Our private data in struct solib.  */
diff --git a/gdb/solib-darwin.c b/gdb/solib-darwin.c
index 961b1099f45e..7a8d714a0a3c 100644
--- a/gdb/solib-darwin.c
+++ b/gdb/solib-darwin.c
@@ -51,7 +51,7 @@ struct darwin_solib_ops : public solib_ops
 solib_ops_up
 make_darwin_solib_ops (program_space *pspace)
 {
-  return std::make_unique<darwin_solib_ops> (pspace);
+  return std::make_unique<darwin_solib_ops> (pspace, true);
 }
 
 struct gdb_dyld_image_info
diff --git a/gdb/solib-dsbt.c b/gdb/solib-dsbt.c
index de18aef6ecdd..5836fb76f441 100644
--- a/gdb/solib-dsbt.c
+++ b/gdb/solib-dsbt.c
@@ -138,7 +138,7 @@ struct dsbt_solib_ops : public solib_ops
 solib_ops_up
 make_dsbt_solib_ops (program_space *pspace)
 {
-  return std::make_unique<dsbt_solib_ops> (pspace);
+  return std::make_unique<dsbt_solib_ops> (pspace, true);
 }
 
 /* Link map info to include in an allocated solib entry */
diff --git a/gdb/solib-frv.c b/gdb/solib-frv.c
index 39ed0e8b731a..d0df24281910 100644
--- a/gdb/solib-frv.c
+++ b/gdb/solib-frv.c
@@ -46,7 +46,7 @@ struct frv_solib_ops : public solib_ops
 solib_ops_up
 make_frv_solib_ops (program_space *pspace)
 {
-  return std::make_unique<frv_solib_ops> (pspace);
+  return std::make_unique<frv_solib_ops> (pspace, true);
 }
 
 /* FR-V pointers are four bytes wide.  */
diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
index a1bbb1a54f42..75cd643f0c83 100644
--- a/gdb/solib-rocm.c
+++ b/gdb/solib-rocm.c
@@ -28,7 +28,6 @@
 #include "linux-tdep.h"
 #include "observable.h"
 #include "solib.h"
-#include "solib-svr4.h"
 #include "symfile.h"
 
 namespace {
@@ -168,33 +167,34 @@ rocm_solib_fd_cache::close (int fd, fileio_error *target_errno)
 
 struct rocm_so
 {
-  rocm_so (const char *name, std::string unique_name, lm_info_svr4_up lm_info)
-    : name (name),
+  rocm_so (std::string name, std::string unique_name, CORE_ADDR load_addr)
+    : name (std::move (name)),
       unique_name (std::move (unique_name)),
-      lm_info (std::move (lm_info))
+      load_addr (load_addr)
   {}
 
   std::string name, unique_name;
-  lm_info_svr4_up lm_info;
+  CORE_ADDR load_addr;
+};
+
+/* Private data for ROCm solibs.  */
+
+struct lm_info_rocm : public lm_info
+{
+  lm_info_rocm (CORE_ADDR load_addr)
+    : load_addr (load_addr)
+  {}
+
+  CORE_ADDR load_addr;
 };
 
 /* solib_ops for ROCm systems.  */
 
 struct rocm_solib_ops : public solib_ops
 {
-  /* HOST_OPS is the host solib_ops that rocm_solib_ops hijacks / wraps,
-     in order to provide support for ROCm code objects.  */
-  explicit rocm_solib_ops (inferior *inf, solib_ops_up host_ops)
-    : solib_ops (inf->pspace), m_host_ops (std::move (host_ops)),
-      m_inf (inf), m_fd_cache (inf)
-  {
-    gdb_assert (m_host_ops != nullptr);
-    gdb_assert (dynamic_cast<rocm_solib_ops *> (m_host_ops.get ()) == nullptr);
-  }
-
-  /* Release the host solib_ops.  */
-  solib_ops_up release_host_ops ()
-  { return std::move (m_host_ops); }
+  explicit rocm_solib_ops (inferior *inf)
+    : solib_ops (inf->pspace, false), m_inf (inf), m_fd_cache (inf)
+  {  }
 
   /* The methods implemented by rocm_solib_ops.  */
   owning_intrusive_list<solib> current_sos () override;
@@ -207,60 +207,11 @@ struct rocm_solib_ops : public solib_ops
      rocm_solib_target_inferior_created.  */
   void update_solib_list ();
 
-  /* Implement the following methods just to forward the calls to the host
-     solib_ops.  We currently need to implement all the methods that
-     svr4_solib_ops implements.  */
-  void clear_so (const solib &so) const override
-  { return m_host_ops->clear_so (so); }
-
-  void clear_solib (program_space *pspace) const override
-  { return m_host_ops->clear_solib (pspace); }
-
-  bool open_symbol_file_object (int from_tty) const override
-  { return m_host_ops->open_symbol_file_object (from_tty); }
-
-  bool in_dynsym_resolve_code (CORE_ADDR pc) const override
-  { return m_host_ops->in_dynsym_resolve_code (pc); }
-
-  bool same (const solib &gdb, const solib &inferior) const override
-  { return m_host_ops->same (gdb, inferior); }
-
-  bool keep_data_in_core (CORE_ADDR vaddr, unsigned long size) const override
-  { return m_host_ops->keep_data_in_core (vaddr, size); }
-
-  void update_breakpoints () const override
-  { return m_host_ops->update_breakpoints (); }
-
-  std::optional<CORE_ADDR> find_solib_addr (solib &so) const override
-  { return m_host_ops->find_solib_addr (so); }
-
-  bool supports_namespaces () const override
-  { return true; }
-
-  int find_solib_ns (const solib &so) const override
-  { return m_host_ops->find_solib_ns (so); }
-
-  int num_active_namespaces () const override
-  { return m_host_ops->num_active_namespaces (); }
-
-  std::vector<const solib *> get_solibs_in_ns (int nsid) const override
-  { return m_host_ops->get_solibs_in_ns (nsid); }
-
-  void iterate_over_objfiles_in_search_order
-    (iterate_over_objfiles_in_search_order_cb_ftype cb,
-     objfile *current_objfile) const override
-  {
-    return m_host_ops->iterate_over_objfiles_in_search_order
-      (cb, current_objfile);
-  }
-
 private:
   owning_intrusive_list<solib>
   solibs_from_rocm_sos (const std::vector<rocm_so> &sos);
   gdb_bfd_iovec_base *bfd_iovec_open (bfd *abfd);
 
-  solib_ops_up m_host_ops;
-
   /* Inferior this rocm_solib_ops is for.  */
   inferior *m_inf;
 
@@ -277,28 +228,16 @@ void
 rocm_solib_ops::relocate_section_addresses (solib &so,
 					    struct target_section *sec) const
 {
-  if (!is_amdgpu_arch (so.abfd.get ()))
-    {
-      m_host_ops->relocate_section_addresses (so, sec);
-      return;
-    }
+  gdb_assert (is_amdgpu_arch (so.abfd.get ()));
 
-  auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
-  sec->addr = sec->addr + li->l_addr;
-  sec->endaddr = sec->endaddr + li->l_addr;
+  auto &li = gdb::checked_static_cast<lm_info_rocm &> (*so.lm_info);
+  sec->addr = sec->addr + li.load_addr;
+  sec->endaddr = sec->endaddr + li.load_addr;
 }
 
 void
 rocm_solib_ops::handle_event ()
 {
-  /* Since we sit on top of a host solib_ops, we might get called following an
-     event concerning host libraries.  We must therefore forward the call.  If
-     the event was for a ROCm code object, it will be a no-op.  On the other hand
-     if the event was for host libraries, rocm_update_solib_list will be
-     essentially be a no-op (it will reload the same code object list as was
-     previously loaded).  */
-  m_host_ops->handle_event ();
-
   this->update_solib_list ();
 }
 
@@ -310,7 +249,7 @@ rocm_solib_ops::solibs_from_rocm_sos (const std::vector<rocm_so> &sos)
   owning_intrusive_list<solib> dst;
 
   for (const rocm_so &so : sos)
-    dst.emplace_back (std::make_unique<lm_info_svr4> (*so.lm_info),
+    dst.emplace_back (std::make_unique<lm_info_rocm> (so.load_addr),
 		      so.unique_name, so.name, *this);
 
   return dst;
@@ -322,22 +261,7 @@ rocm_solib_ops::solibs_from_rocm_sos (const std::vector<rocm_so> &sos)
 owning_intrusive_list<solib>
 rocm_solib_ops::current_sos ()
 {
-  /* First, retrieve the host-side shared library list.  */
-  owning_intrusive_list<solib> sos = m_host_ops->current_sos ();
-
-  /* Then, the device-side shared library list.  */
-  if (m_solib_list.empty ())
-    return sos;
-
-  owning_intrusive_list<solib> dev_solibs = solibs_from_rocm_sos (m_solib_list);
-
-  if (sos.empty ())
-    return dev_solibs;
-
-  /* Append our libraries to the end of the list.  */
-  sos.splice (std::move (dev_solibs));
-
-  return sos;
+  return solibs_from_rocm_sos (m_solib_list);
 }
 
 namespace {
@@ -660,9 +584,7 @@ rocm_solib_ops::bfd_iovec_open (bfd *abfd)
 gdb_bfd_ref_ptr
 rocm_solib_ops::bfd_open (const char *pathname)
 {
-  /* Handle regular files with SVR4 open.  */
-  if (strstr (pathname, "://") == nullptr)
-    return m_host_ops->bfd_open (pathname);
+  gdb_assert (strstr (pathname, "://") != nullptr);
 
   auto open = [this] (bfd *nbfd)
   {
@@ -749,7 +671,6 @@ void
 rocm_solib_ops::create_inferior_hook (int from_tty)
 {
   m_solib_list.clear ();
-  m_host_ops->create_inferior_hook (from_tty);
 }
 
 void
@@ -796,10 +717,6 @@ rocm_solib_ops::update_solib_list ()
 
       gdb::unique_xmalloc_ptr<char> uri_bytes_holder (uri_bytes);
 
-      /* Pass a dummy debug base.  */
-      lm_info_svr4_up li = std::make_unique<lm_info_svr4> (-1);
-      li->l_addr = l_addr;
-
       /* Generate a unique name so that code objects with the same URI but
 	 different load addresses are seen by gdb core as different shared
 	 objects.  */
@@ -807,7 +724,7 @@ rocm_solib_ops::update_solib_list ()
 	= string_printf ("code_object_%ld", code_object_list[i].handle);
 
       m_solib_list.emplace_back (uri_bytes, std::move (unique_name),
-				 std::move (li));
+				 l_addr);
     }
 }
 
@@ -818,12 +735,10 @@ rocm_solib_target_inferior_created (inferior *inf)
   if (inf->vfork_parent != nullptr)
     return;
 
-  auto prev_ops = inf->pspace->release_solib_ops ();
-  auto rocm_ops = std::make_unique<rocm_solib_ops> (inf, std::move (prev_ops));
-  inf->pspace->set_solib_ops (std::move (rocm_ops));
+  auto &rocm_ops
+    = inf->pspace->add_solib_ops (std::make_unique<rocm_solib_ops> (inf));
 
-  gdb::checked_static_cast<rocm_solib_ops *>
-    (inf->pspace->solib_ops ())->update_solib_list ();
+  rocm_ops.update_solib_list ();
 
   /* Force GDB to reload the solibs.  */
   inf->pspace->clear_solib_cache ();
@@ -838,11 +753,8 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
   if (get_amd_dbgapi_process_id (follow_inf) == AMD_DBGAPI_PROCESS_NONE)
     return;
 
-  auto pspace = follow_inf->pspace;
-  auto prev_ops = pspace->release_solib_ops ();
-  auto rocm_ops
-    = std::make_unique<rocm_solib_ops> (follow_inf, std::move (prev_ops));
-  pspace->set_solib_ops (std::move (rocm_ops));
+  follow_inf->pspace->add_solib_ops
+    (std::make_unique<rocm_solib_ops> (follow_inf));
 }
 
 static void
@@ -852,14 +764,13 @@ rocm_solib_target_inferior_forked (inferior *parent_inf, inferior *child_inf,
 {
   if (detach_on_fork && follow_child && fork_kind == TARGET_WAITKIND_FORKED)
     {
-      /* In this particular configuration, infrun's follow_fork_inferior
-	 function moves the parent pspace to the child directly.  Remove the
-	 existing rocm_solib_ops from the child and restore the host solib_ops,
-	 to make it look like a brand new pspace.  */
-      auto rocm_ops_holder = child_inf->pspace->release_solib_ops ();
-      auto rocm_ops
-	= gdb::checked_static_cast<rocm_solib_ops *> (rocm_ops_holder.get ());
-      child_inf->pspace->set_solib_ops (rocm_ops->release_host_ops ());
+      /* In this particular configuration, infrun's follow_fork_inferior moves
+	 the parent pspace to the child directly.  Remove the existing rocm_solib_ops
+	 from the child and restore the host solib_ops, to make it look like a
+	 brand new pspace.  */
+      auto rocm_ops = child_inf->pspace->find_solib_ops<rocm_solib_ops> ();
+      gdb_assert (rocm_ops != nullptr);
+      child_inf->pspace->remove_solib_ops (*rocm_ops);
     }
 }
 
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index f0f13c16bb03..426053a467e8 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -3642,7 +3642,7 @@ find_debug_base_for_solib (const solib *solib)
    stay in the same namespace as that file.  Otherwise, we only consider
    the initial namespace.  */
 
-void
+bool
 svr4_solib_ops::iterate_over_objfiles_in_search_order
   (iterate_over_objfiles_in_search_order_cb_ftype cb,
    objfile *current_objfile) const
@@ -3668,7 +3668,7 @@ svr4_solib_ops::iterate_over_objfiles_in_search_order
 	{
 	  checked_current_objfile = true;
 	  if (cb (current_objfile))
-	    return;
+	    return true;
 	}
     }
 
@@ -3689,13 +3689,25 @@ svr4_solib_ops::iterate_over_objfiles_in_search_order
       if (checked_current_objfile && &objfile == current_objfile)
 	continue;
 
-      /* Try to determine the namespace into which objfile was loaded.
-
-	 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.  */
       const solib *solib = find_one_solib_for_objfile (&objfile);
-      CORE_ADDR solib_base = find_debug_base_for_solib (solib);
+      CORE_ADDR solib_base = 0;
+
+      if (solib != nullptr)
+	{
+	  /* Skip objfiles provided by other solib_ops.  */
+	  if (&solib->ops () != this)
+	    continue;
+
+	  solib_base = find_debug_base_for_solib (solib);
+	}
+      else if (&objfile != m_pspace->symfile_object_file)
+	{
+	  /* Objfiles not associated to an solib_ops are handled in the
+	     program_space method.  The main objfile is an exception to this,
+	     because it is part of the SVR4 domain.  */
+	  continue;
+	}
+
       if (solib_base == 0)
 	solib_base = default_debug_base;
 
@@ -3704,8 +3716,10 @@ svr4_solib_ops::iterate_over_objfiles_in_search_order
 	continue;
 
       if (cb (&objfile))
-	return;
+	return true;
     }
+
+  return false;
 }
 
 std::optional<CORE_ADDR>
diff --git a/gdb/solib-svr4.h b/gdb/solib-svr4.h
index 28b27675f529..95e033d1cc1c 100644
--- a/gdb/solib-svr4.h
+++ b/gdb/solib-svr4.h
@@ -94,7 +94,9 @@ enum probe_action
 
 struct svr4_solib_ops : public solib_ops
 {
-  using solib_ops::solib_ops;
+  explicit svr4_solib_ops (program_space *pspace)
+    : solib_ops (pspace, true)
+  {}
 
   void relocate_section_addresses (solib &so, target_section *) const override;
   void clear_so (const solib &so) const override;
@@ -112,7 +114,7 @@ struct svr4_solib_ops : public solib_ops
   int find_solib_ns (const solib &so) const override;
   int num_active_namespaces () const override;
   std::vector<const solib *> get_solibs_in_ns (int nsid) const override;
-  void iterate_over_objfiles_in_search_order
+  bool iterate_over_objfiles_in_search_order
     (iterate_over_objfiles_in_search_order_cb_ftype cb,
      objfile *current_objfile) const override;
 
diff --git a/gdb/solib-target.c b/gdb/solib-target.c
index 9707596725d4..9365491d30d2 100644
--- a/gdb/solib-target.c
+++ b/gdb/solib-target.c
@@ -390,5 +390,5 @@ target_solib_ops::in_dynsym_resolve_code (CORE_ADDR pc) const
 solib_ops_up
 make_target_solib_ops (program_space *pspace)
 {
-  return std::make_unique<target_solib_ops> (pspace);
+  return std::make_unique<target_solib_ops> (pspace, true);
 }
diff --git a/gdb/solib.c b/gdb/solib.c
index a18f9b5bd0ec..d3ef46a098fb 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -21,6 +21,7 @@
 #include <fcntl.h>
 #include "exceptions.h"
 #include "extract-store-integer.h"
+#include "progspace.h"
 #include "symtab.h"
 #include "bfd.h"
 #include "build-id.h"
@@ -474,17 +475,38 @@ solib_ops::bfd_open (const char *pathname)
 
 /* See solib.h.  */
 
-void
+bool
 solib_ops::iterate_over_objfiles_in_search_order
   (iterate_over_objfiles_in_search_order_cb_ftype cb,
    objfile *current_objfile) const
 {
   if (current_objfile != nullptr && cb (current_objfile))
-    return;
+    return true;
 
   for (objfile &objfile : m_pspace->objfiles ())
-    if (&objfile != current_objfile && cb (&objfile))
-      return;
+    {
+      /* The current objfile, if any, was handled above.  */
+      if (current_objfile == &objfile)
+	continue;
+
+      /* Call CB on OBJFILE if either:
+
+	  - OBJFILE was provided by this solib_ops, or
+	  - this is the main objfile and M_HANDLE_MAIN_OBJFILE is true
+	 */
+      bool is_from_this = (!objfile.solibs ().empty ()
+			   && &objfile.solibs ().front ()->ops () == this);
+      bool is_main_handled_by_this = (&objfile == m_pspace->symfile_object_file
+				      && m_handle_main_objfile);
+
+      if (!is_from_this && !is_main_handled_by_this)
+	continue;
+
+      if (cb (&objfile))
+	return true;
+    }
+
+  return false;
 }
 
 /* Given a pointer to one of the shared objects in our list of mapped
@@ -717,14 +739,9 @@ notify_solib_unloaded (program_space *pspace, const solib &so,
   gdb::observers::solib_unloaded.notify (pspace, so, still_in_use, silent);
 }
 
-/* Remove solib *SOLIB_IT from PSPACE.
+/* See solib.h.  */
 
-   Remove the corresponding objfiles and target sections.
-
-   Return an iterator to the next solib in PSPACE's solib list, helping to
-   continue iterating.  */
-
-static owning_intrusive_list<solib>::iterator
+owning_intrusive_list<solib>::iterator
 remove_solib (program_space *pspace,
 	      owning_intrusive_list<solib>::iterator solib_it)
 {
@@ -759,11 +776,6 @@ remove_solib (program_space *pspace,
 void
 update_solib_list (int from_tty)
 {
-  solib_ops *ops = current_program_space->solib_ops ();
-
-  if (ops == nullptr)
-    return;
-
   /* We can reach here due to changing solib-search-path or the
      sysroot, before having any inferior.  */
   if (target_has_execution () && inferior_ptid != null_ptid)
@@ -778,7 +790,9 @@ update_solib_list (int from_tty)
 	{
 	  try
 	    {
-	      ops->open_symbol_file_object (from_tty);
+	      for (const auto &ops : current_program_space->solib_ops ())
+		if (ops->open_symbol_file_object (from_tty))
+		  break;
 	    }
 	  catch (const gdb_exception_error &ex)
 	    {
@@ -813,7 +827,10 @@ 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.  */
 
-  owning_intrusive_list<solib> inferior = ops->current_sos ();
+  owning_intrusive_list<solib> inferior;
+  for (auto &ops : current_program_space->solib_ops ())
+    inferior.splice (ops->current_sos ());
+
   owning_intrusive_list<solib>::iterator gdb_iter
     = current_program_space->solibs ().begin ();
   while (gdb_iter != current_program_space->solibs ().end ())
@@ -823,8 +840,13 @@ update_solib_list (int from_tty)
       /* Check to see whether the shared object *gdb also appears in
 	 the inferior's current list.  */
       for (; inferior_iter != inferior.end (); ++inferior_iter)
-	if (ops->same (*gdb_iter, *inferior_iter))
-	  break;
+	{
+	  if (&gdb_iter->ops () != &inferior_iter->ops ())
+	    continue;
+
+	  if (gdb_iter->ops ().same (*gdb_iter, *inferior_iter))
+	   break;
+	}
 
       /* 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
@@ -1041,25 +1063,28 @@ print_solib_list_table (std::vector<const solib *> solib_list,
   gdbarch *gdbarch = current_inferior ()->arch ();
   /* "0x", a little whitespace, and two hex digits per byte of pointers.  */
   int addr_width = 4 + (gdbarch_ptr_bit (gdbarch) / 4);
-  const solib_ops *ops = current_program_space->solib_ops ();
   struct ui_out *uiout = current_uiout;
   bool so_missing_debug_info = false;
 
-  if (ops == nullptr)
-    return;
+  /* The conditions for this command to print solib namespaces:
 
-  /* There are 3 conditions for this command to print solib namespaces,
-     first PRINT_NAMESPACE has to be true, second the solib_ops has to
-     support multiple namespaces, and third there must be more than one
-     active namespace.  Fold all these into the PRINT_NAMESPACE condition.  */
-  print_namespace = (print_namespace
-		     && ops != nullptr
-		     && ops->supports_namespaces ()
-		     && ops->num_active_namespaces () > 1);
+      - PRINT_NAMESPACE has to be true
+      - at least one solib_ops has to have more than one active namespace
 
-  int num_cols = 4;
+     Fold these into the PRINT_NAMESPACE condition.  */
   if (print_namespace)
-    num_cols++;
+    {
+      print_namespace = false;
+
+      for (auto &ops : current_program_space->solib_ops ())
+	if (ops->supports_namespaces () && ops->num_active_namespaces () > 1)
+	  {
+	    print_namespace = true;
+	    break;
+	  }
+    }
+
+  int num_cols = print_namespace ? 5 : 4;
 
   {
     ui_out_emit_table table_emitter (uiout, num_cols, solib_list.size (),
@@ -1097,7 +1122,11 @@ print_solib_list_table (std::vector<const solib *> solib_list,
 	  {
 	    try
 	      {
-		uiout->field_fmt ("namespace", "%d", ops->find_solib_ns (*so));
+		if (so->ops ().supports_namespaces ())
+		  uiout->field_fmt ("namespace", "%d",
+				    so->ops ().find_solib_ns (*so));
+		else
+		  uiout->field_skip ("namespace");
 	      }
 	    catch (const gdb_exception_error &er)
 	      {
@@ -1180,81 +1209,92 @@ info_sharedlibrary_command (const char *pattern, int from_tty)
 static void
 info_linker_namespace_command (const char *pattern, int from_tty)
 {
-  const solib_ops *ops = current_program_space->solib_ops ();
-
-  /* This command only really makes sense for inferiors that support
-     linker namespaces, so we can leave early.  */
-  if (ops == nullptr || !ops->supports_namespaces ())
-    error (_("Current inferior does not support linker namespaces.  "
-	     "Use \"info sharedlibrary\" instead."));
-
-  struct ui_out *uiout = current_uiout;
-  std::vector<std::pair<int, std::vector<const solib *>>> all_solibs_to_print;
-
   pattern = skip_spaces (pattern);
 
-  if (pattern == nullptr || pattern[0] == '\0')
-    {
-      uiout->message (_("There are %d linker namespaces loaded.\n"),
-		      ops->num_active_namespaces ());
+  /* Will be set to true if at least one solib_ops in the program space
+     supports namespaces.  */
+  bool one_solib_ops_supports_namespaces = false;
 
-      int printed = 0;
-      for (int i = 0; printed < ops->num_active_namespaces (); i++)
+  for (auto &ops : current_program_space->solib_ops ())
+    {
+      if (!ops->supports_namespaces ())
+	continue;
+
+      one_solib_ops_supports_namespaces = true;
+
+      struct ui_out *uiout = current_uiout;
+      std::vector<std::pair<int, std::vector<const solib *>>>
+	all_solibs_to_print;
+
+      if (pattern == nullptr || pattern[0] == '\0')
 	{
-	  std::vector<const solib *> solibs_to_print
-	    = ops->get_solibs_in_ns (i);
-	  if (solibs_to_print.size () > 0)
+	  /* The output here will be awkward if multiple solib_ops support
+	     namespaces, but it's just theoretical for now, so don't bother
+	     trying to do anything fancier.  */
+	  uiout->message (_("There are %d linker namespaces loaded.\n"),
+			  ops->num_active_namespaces ());
+
+	  int printed = 0;
+	  for (int i = 0; printed < ops->num_active_namespaces (); i++)
 	    {
-	      all_solibs_to_print.push_back (std::make_pair
-					      (i, solibs_to_print));
-	      printed++;
+	      std::vector<const solib *> solibs_to_print
+		= ops->get_solibs_in_ns (i);
+	      if (solibs_to_print.size () > 0)
+		{
+		  all_solibs_to_print.push_back (std::make_pair
+						  (i, solibs_to_print));
+		  printed++;
+		}
 	    }
 	}
-    }
-  else
-    {
-      int ns;
-      /* Check if the pattern includes the optional [[ and ]] decorators.
-	 To match multiple occurrences, '+' needs to be escaped, and every
-	 escape sequence must be doubled to survive the compiler pass.  */
-      re_comp ("^\\[\\[[0-9]\\+\\]\\]$");
-      if (re_exec (pattern))
-	ns = strtol (pattern + 2, nullptr, 10);
       else
 	{
-	  char *end = nullptr;
-	  ns = strtol (pattern, &end, 10);
-	  if (end[0] != '\0')
-	    error (_("Invalid linker namespace identifier: %s"), pattern);
+	  int ns;
+	  /* Check if the pattern includes the optional [[ and ]] decorators.
+	     To match multiple occurrences, '+' needs to be escaped, and every
+	     escape sequence must be doubled to survive the compiler pass.  */
+	  re_comp ("^\\[\\[[0-9]\\+\\]\\]$");
+	  if (re_exec (pattern))
+	    ns = strtol (pattern + 2, nullptr, 10);
+	  else
+	    {
+	      char *end = nullptr;
+	      ns = strtol (pattern, &end, 10);
+	      if (end[0] != '\0')
+		error (_("Invalid linker namespace identifier: %s"), pattern);
+	    }
+
+	  all_solibs_to_print.push_back
+	    (std::make_pair (ns, ops->get_solibs_in_ns (ns)));
 	}
 
-      all_solibs_to_print.push_back
-	(std::make_pair (ns, ops->get_solibs_in_ns (ns)));
-    }
-
-  for (const auto &[ns, solibs_to_print] : all_solibs_to_print)
-    {
-      uiout->message ("\n");
-
-      if (solibs_to_print.size () == 0)
+      for (const auto &[ns, solibs_to_print] : all_solibs_to_print)
 	{
-	  uiout->message (_("Linker namespace %d is not active.\n"), ns);
-	  /* If we got here, a specific namespace was requested, so there
-	     will only be one vector.  We can leave early.  */
-	  break;
+	  uiout->message ("\n");
+
+	  if (solibs_to_print.size () == 0)
+	    {
+	      uiout->message (_("Linker namespace %d is not active.\n"), ns);
+	      /* If we got here, a specific namespace was requested, so there
+		 will only be one vector.  We can leave early.  */
+	      break;
+	    }
+
+	  if (solibs_to_print.size () == 1)
+	    uiout->message
+	      (_("1 library loaded in linker namespace %d:\n"), ns);
+	  else
+	    uiout->message
+	      (_("%zu libraries loaded in linker namespace %d:\n"),
+	       solibs_to_print.size (), ns);
+
+	  print_solib_list_table (solibs_to_print, false);
 	}
-
-      if (solibs_to_print.size () == 1)
-	uiout->message
-	  (_("1 library loaded in linker namespace %d:\n"), ns);
-      else
-	uiout->message
-	  (_("%zu libraries loaded in linker namespace %d:\n"),
-	   solibs_to_print.size (), ns);
-
-
-      print_solib_list_table (solibs_to_print, false);
     }
+
+  if (!one_solib_ops_supports_namespaces)
+    error (_("Current inferior does not support linker namespaces.  "
+	     "Use \"info sharedlibrary\" instead."));
 }
 
 /* See solib.h.  */
@@ -1302,9 +1342,11 @@ solib_ops::same (const solib &a, const solib &b) const
 bool
 solib_keep_data_in_core (CORE_ADDR vaddr, unsigned long size)
 {
-  const solib_ops *ops = current_program_space->solib_ops ();
+  for (const auto &ops : current_program_space->solib_ops ())
+    if (ops->keep_data_in_core (vaddr, size))
+      return true;
 
-  return ops != nullptr && ops->keep_data_in_core (vaddr, size);
+  return false;
 }
 
 /* See solib.h.  */
@@ -1323,8 +1365,7 @@ clear_solib (program_space *pspace)
 
   pspace->solibs ().clear ();
 
-  if (const solib_ops *ops = pspace->solib_ops ();
-      ops != nullptr)
+  for (const auto &ops : pspace->solib_ops ())
     ops->clear_solib (pspace);
 }
 
@@ -1336,8 +1377,7 @@ clear_solib (program_space *pspace)
 void
 solib_create_inferior_hook (int from_tty)
 {
-  if (solib_ops *ops = current_program_space->solib_ops ();
-      ops != nullptr)
+  for (const auto &ops : current_program_space->solib_ops ())
     ops->create_inferior_hook (from_tty);
 }
 
@@ -1346,9 +1386,11 @@ solib_create_inferior_hook (int from_tty)
 bool
 in_solib_dynsym_resolve_code (CORE_ADDR pc)
 {
-  const solib_ops *ops = current_program_space->solib_ops ();
+  for (const auto &ops : current_program_space->solib_ops ())
+    if (ops->in_dynsym_resolve_code (pc))
+      return true;
 
-  return ops != nullptr && ops->in_dynsym_resolve_code (pc);
+  return false;
 }
 
 /* Implements the "sharedlibrary" command.  */
@@ -1390,9 +1432,7 @@ no_shared_libraries_command (const char *ignored, int from_tty)
 void
 update_solib_breakpoints (void)
 {
-  const solib_ops *ops = current_program_space->solib_ops ();
-
-  if (ops != nullptr)
+  for (const auto &ops : current_program_space->solib_ops ())
     ops->update_breakpoints ();
 }
 
@@ -1401,8 +1441,7 @@ update_solib_breakpoints (void)
 void
 handle_solib_event (void)
 {
-  if (solib_ops *ops = current_program_space->solib_ops ();
-      ops != nullptr)
+  for (const auto &ops : current_program_space->solib_ops ())
     ops->handle_event ();
 
   current_inferior ()->pspace->clear_solib_cache ();
@@ -1500,8 +1539,7 @@ reload_shared_libraries (const char *ignored, int from_tty,
     {
       /* Reset or free private data structures not associated with
 	 solib entries.  */
-      if (const solib_ops *ops = current_program_space->solib_ops ();
-	  ops != nullptr)
+      for (const auto &ops : current_program_space->solib_ops ())
 	ops->clear_solib (current_program_space);
 
       /* Remove any previous solib event breakpoint.  This is usually
@@ -1833,11 +1871,13 @@ remove_user_added_objfile (struct objfile *objfile)
 int
 solib_linker_namespace_count (program_space *pspace)
 {
-  if (const auto ops = pspace->solib_ops (); ops != nullptr
-      && ops->supports_namespaces ())
-    return ops->num_active_namespaces ();
+  int num_active_namespaces = 0;
 
-  return 0;
+  for (const auto &ops : pspace->solib_ops ())
+    if (ops->supports_namespaces ())
+      num_active_namespaces += ops->num_active_namespaces ();
+
+  return num_active_namespaces;
 }
 
 /* Implementation of the linker_namespace convenience variable.
diff --git a/gdb/solib.h b/gdb/solib.h
index 512fdf7bce96..e5dbdd71f663 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -145,8 +145,8 @@ using iterate_over_objfiles_in_search_order_cb_ftype
 
 struct solib_ops
 {
-  explicit solib_ops (program_space *pspace)
-    : m_pspace (pspace)
+  explicit solib_ops (program_space *pspace, bool handle_main_objfile)
+    : m_pspace (pspace), m_handle_main_objfile (handle_main_objfile)
   {}
 
   virtual ~solib_ops () = default;
@@ -276,21 +276,29 @@ struct solib_ops
   virtual std::vector<const solib *> get_solibs_in_ns (int ns) const
   { gdb_assert_not_reached ("namespaces not supported"); }
 
-  /* Iterate over all objfiles of the program space in the order that makes the
-     most sense for the architecture to make global symbol searches.
+  /* Iterate over all objfiles associated to this solib_ops in the order that
+     makes the most sense for the architecture to make global symbol searches.
+
+     If M_HANDLE_MAIN_OBJFILE is set, also iterate over the "main" objfile of
+     the program space (program_space::symfile_object_file).
 
      CB is a callback function passed an objfile to be searched.  The iteration
      stops if this function returns true.
 
      If not nullptr, CURRENT_OBJFILE corresponds to the objfile being inspected
-     when the symbol search was requested.  */
-  virtual void iterate_over_objfiles_in_search_order
+     when the symbol search was requested.  If not nullptr, it is guaranteed
+     that CURRENT_OBJFILE is associated to this solib_ops.  */
+  virtual bool iterate_over_objfiles_in_search_order
     (iterate_over_objfiles_in_search_order_cb_ftype cb,
      objfile *current_objfile) const;
 
 protected:
   /* The program space for which this solib_ops was created.  */
   program_space *m_pspace;
+
+  /* Whether this solib_ops should handle the main objfile
+     (pspace->symfile_object_file) in iterate_over_objfiles_in_search_order.  */
+  bool m_handle_main_objfile;
 };
 
 /* A unique pointer to an solib_ops.  */
@@ -356,6 +364,16 @@ extern bool in_solib_dynsym_resolve_code (CORE_ADDR);
 
 extern void no_shared_libraries (program_space *pspace);
 
+/* Remove solib *SOLIB_IT from PSPACE.
+
+   Remove the corresponding objfiles and target sections.
+
+   Return an iterator to the next solib in PSPACE's solib list, helping to
+   continue iterating.  */
+
+extern owning_intrusive_list<solib>::iterator remove_solib
+  (program_space *pspace, owning_intrusive_list<solib>::iterator solib_it);
+
 /* Synchronize GDB's shared object list with inferior's.
 
    Extract the list of currently loaded shared objects from the
diff --git a/gdb/target.c b/gdb/target.c
index 05944319e260..ab46fa6b7678 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2472,7 +2472,7 @@ target_pre_inferior ()
   if (!gdbarch_has_global_solist (current_inferior ()->arch ()))
     {
       no_shared_libraries (current_program_space);
-      current_program_space->unset_solib_ops ();
+      current_program_space->clear_solib_ops ();
 
       invalidate_target_mem_regions ();
 
diff --git a/gdb/testsuite/gdb.rocm/symbol-lookup.cpp b/gdb/testsuite/gdb.rocm/symbol-lookup.cpp
new file mode 100644
index 000000000000..7de2f6a34992
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/symbol-lookup.cpp
@@ -0,0 +1,320 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2025 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/>.  */
+
+#if defined(SHARED_LIB_1)
+
+extern "C"
+{
+
+  using shared_lib_1_t = int;
+
+  void func_host_shared_lib_1 (shared_lib_1_t)
+  {
+  }
+
+  void func_host_shared_lib (shared_lib_1_t)
+  {
+  }
+
+  void func_1 (shared_lib_1_t)
+  {
+  }
+
+  void func_main_shared_lib_1 (shared_lib_1_t)
+  {
+  }
+
+} /* extern "C" */
+
+#elif defined(SHARED_LIB_2)
+
+extern "C"
+{
+
+  using shared_lib_2_t = int;
+
+  void func_host_shared_lib_2 (shared_lib_2_t)
+  {
+  }
+
+  void func_host_shared_lib (shared_lib_2_t)
+  {
+  }
+
+  void func_2 (shared_lib_2_t)
+  {
+  }
+
+  void func_main_shared_lib_2 (shared_lib_2_t)
+  {
+  }
+
+} /* extern "C" */
+
+#elif defined(CODE_OBJECT_1)
+
+extern "C"
+{
+
+  using code_object_1_t = int;
+
+  __global__ void func_device_code_object_1 (code_object_1_t)
+  {
+  }
+
+  __global__ void func_device_code_object (code_object_1_t)
+  {
+  }
+
+  __global__ void func_1 (code_object_1_t)
+  {
+  }
+
+  __global__ void func_main_code_object_1 (code_object_1_t)
+  {
+  }
+
+} /* extern "C" */
+
+#elif defined(CODE_OBJECT_2)
+
+extern "C"
+{
+
+  using code_object_2_t = int;
+
+  __global__ void func_device_code_object_2 (code_object_2_t)
+  {
+  }
+
+  __global__ void func_device_code_object (code_object_2_t)
+  {
+  }
+
+  __global__ void func_2 (code_object_2_t)
+  {
+  }
+
+  __global__ void func_main_code_object_2 (code_object_2_t)
+  {
+  }
+
+} /* extern "C" */
+
+#else
+
+#include <dlfcn.h>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include "hip/hip_runtime.h"
+
+/* This `extern "C"` is necessary to make the test work, due to an oddity in how
+   GDB computes the demangled names.  Without it, the compiler includes a
+   DW_AT_linkage_name in the DW_TAG_subprogram DIE, causing GDB to use that to
+   compute the symbol's demangled name.  The symbols would then appear as
+   `func_main(int)` rather than `func_main(main_program_t)`, making it
+   impossible to differentiate them.  With `extern "C"`, GDB has to construct
+   the demangled name from the DIE structure, resulting in the expected
+   `func_main(main_program_t)`.   */
+
+extern "C"
+{
+
+  using main_program_t = int;
+
+  void func_main (main_program_t)
+  {
+  }
+
+  void func_main_shared_lib_1 (main_program_t)
+  {
+  }
+
+  void func_main_shared_lib_2 (main_program_t)
+  {
+  }
+
+  void func_main_code_object_1 (main_program_t)
+  {
+  }
+
+  void func_main_code_object_2 (main_program_t)
+  {
+  }
+
+} /* extern "C" */
+
+namespace
+{
+
+void
+throw_from_hip_error (const std::string &prefix, hipError_t error)
+{
+  throw std::runtime_error (prefix + ": " + hipGetErrorString (error));
+}
+
+void
+warn_from_hip_error (const std::string &prefix, hipError_t error)
+{
+  std::cerr << prefix + ": " + hipGetErrorString (error) << std::endl;
+}
+
+struct hip_module_unloader
+{
+  void operator() (hipModule_t module) const
+  {
+    hipError_t error = hipModuleUnload (module);
+
+    if (error != HIP_SUCCESS)
+      warn_from_hip_error ("Failed to unload HIP module", error);
+  }
+};
+
+using hip_module_up
+  = std::unique_ptr<std::remove_pointer_t<hipModule_t>, hip_module_unloader>;
+
+hip_module_up
+load_hip_module (const char *module_path)
+{
+  hipModule_t module;
+  hipError_t error = hipModuleLoad (&module, module_path);
+
+  if (error != HIP_SUCCESS)
+    throw_from_hip_error ((std::string ("Failed to load HIP module ")
+			   + module_path),
+			  error);
+
+  return hip_module_up (module);
+}
+
+void
+throw_from_dlerror (const std::string &prefix)
+{
+  throw std::runtime_error (prefix + ": " + dlerror ());
+}
+
+void
+warn_from_dlerror (const std::string &prefix)
+{
+  std::cerr << prefix + ": " + dlerror () << std::endl;
+}
+
+struct dlcloser
+{
+  void operator() (void *lib)
+  {
+    int ret = dlclose (lib);
+
+    if (ret != 0)
+      warn_from_dlerror ("Failed to dlclose");
+  }
+};
+
+using dl_up = std::unique_ptr<void, dlcloser>;
+
+dl_up
+load_shared_lib (const char *lib_path)
+{
+  void *lib = dlopen (lib_path, RTLD_NOW);
+
+  if (lib == nullptr)
+    throw_from_dlerror (std::string ("Failed to dlopen ") + lib_path);
+
+  return dl_up (lib);
+}
+
+using host_function_t = void (*) (int);
+
+host_function_t
+lookup_symbol (dl_up &dl, const char *sym_name)
+{
+  void *sym = dlsym (dl.get (), sym_name);
+
+  if (sym == nullptr)
+    throw_from_dlerror (std::string ("Failed to dlsym ") + sym_name);
+
+  return reinterpret_cast<host_function_t> (sym);
+}
+
+hipFunction_t
+lookup_symbol (hip_module_up &mod, const char *sym_name)
+{
+  hipFunction_t func;
+  hipError_t error = hipModuleGetFunction (&func, mod.get (), sym_name);
+
+  if (error != HIP_SUCCESS)
+    throw_from_hip_error (std::string ("Failed to look up kernel ") + sym_name,
+			  error);
+
+  return func;
+}
+
+void
+launch_kernel (hipFunction_t func)
+{
+  int arg = 2;
+  void *args[1] = { &arg };
+
+  hipError_t error = hipModuleLaunchKernel (func, 1, 1, 1, 1, 1, 1, 0, nullptr,
+					    args, nullptr);
+
+  if (error != HIP_SUCCESS)
+    throw_from_hip_error ("Failed to launch kernel", error);
+
+  error = hipDeviceSynchronize ();
+
+  if (error != HIP_SUCCESS)
+    throw_from_hip_error ("Failed to sync", error);
+}
+
+} /* namespace */
+
+int
+main (int argc, const char **argv)
+{
+  if (argc != 5)
+    {
+      fprintf (stderr, "Usage: %s <shlib1> <shlib2> <hip1> <hip1>\n", argv[0]);
+      return 1;
+    }
+
+  dl_up shared_lib_1 = load_shared_lib (argv[1]);
+  dl_up shared_lib_2 = load_shared_lib (argv[2]);
+  hip_module_up hip_module_1 = load_hip_module (argv[3]);
+  hip_module_up hip_module_2 = load_hip_module (argv[4]);
+
+  host_function_t shared_lib_1_sym
+    = lookup_symbol (shared_lib_1, "func_host_shared_lib_1");
+  host_function_t shared_lib_2_sym
+    = lookup_symbol (shared_lib_2, "func_host_shared_lib_2");
+
+  hipFunction_t code_object_1_kernel
+    = lookup_symbol (hip_module_1, "func_device_code_object_1");
+  hipFunction_t code_object_2_kernel
+    = lookup_symbol (hip_module_2, "func_device_code_object_2");
+
+  func_main (0);
+  shared_lib_1_sym (0);
+  shared_lib_2_sym (0);
+  launch_kernel (code_object_1_kernel);
+  launch_kernel (code_object_2_kernel);
+
+  return 0;
+}
+
+#endif
diff --git a/gdb/testsuite/gdb.rocm/symbol-lookup.exp b/gdb/testsuite/gdb.rocm/symbol-lookup.exp
new file mode 100644
index 000000000000..1eb658a3e4c2
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/symbol-lookup.exp
@@ -0,0 +1,243 @@
+# Copyright 2025 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 symbol lookup with multiple host shared libraries and ROCm code objects.
+#
+# This test verifies that GDB correctly resolves symbols based on where the
+# program is stopped.  The following objfiles are in play:
+#
+#  - the main program
+#  - two host shared libraries
+#  - two ROCm code objects
+#
+# The main program is separated from host shared libraries for the purpose of
+# this test because it is handled differently than shared libraries by the
+# iterate_over_objfiles_in_search_order methods.
+#
+# Each objfile defines functions with various levels of uniqueness:
+#
+#   - func_main: unique to the main program
+#   - func_host_shared_lib_N: unique to host shared library N
+#   - func_device_code_object_N: unique to ROCm code object N
+#   - func_host_shared_lib: defined in both host shared libraries
+#   - func_device_code_object: defined in both ROCm code objects
+#   - func_N: defined in host shared library N and ROCm code object N
+#   - func_main_shared_lib_N: defined in the main program and host shared library N
+#   - func_main_code_object_N: defined in the main program and ROCm code object N
+#
+# The functions have different signatures, to be able to tell them apart.
+#
+# The test stops at breakpoints in each objfile and verifies that symbol
+# lookups return the expected function.  GDB gives priority to the current
+# objfile, and then objfiles from the current "solib provider", before
+# considering other loaded objfiles.
+
+load_lib rocm.exp
+
+standard_testfile .cpp
+
+require allow_hipcc_tests
+
+# Build the main program.
+if { [build_executable "failed to prepare" \
+	  $testfile $srcfile {debug hip}] == -1 } {
+    return
+}
+
+# Return the path to shared library N.
+proc shared_lib_path { n } {
+    return [standard_output_file ${::testfile}_${n}.so]
+}
+
+# Return the path to code object N.
+proc code_object_path { n } {
+    return [standard_output_file ${::testfile}_${n}.co]
+}
+
+# Build the host shared libraries and ROCm code objects.
+foreach n {1 2} {
+    set shared_lib [shared_lib_path $n]
+    set code_object [code_object_path $n]
+
+    # Build the host shlib N
+    if { [gdb_compile_shlib $srcdir/$subdir/$srcfile $shared_lib \
+	  [list debug additional_flags=-DSHARED_LIB_$n]] != "" } {
+	return
+    }
+
+    # Build the ROCm code object N
+    if { [gdb_compile $srcdir/$subdir/$srcfile $code_object object \
+	  [list debug hip additional_flags=--genco additional_flags=-DCODE_OBJECT_$n]] != "" } {
+	return
+    }
+}
+
+# Verify symbol lookup results at the current stop location.
+#
+# Each parameter is a regex matching the expected parameter type in the
+# function signature.  This parameter type is what allows us to confirm
+# that GDB found the expected version of the function.
+proc check_funcs { func_host_shared_lib func_device_code_object
+		   func_1 func_2
+		   func_main_shared_lib_1 func_main_shared_lib_2
+		   func_main_code_object_1 func_main_code_object_2 } {
+    # Unique functions: only one definition exists, so lookup is unambiguous.
+    gdb_test "print func_main" "<func_main\\(main_program_t\\)>"
+    gdb_test "print func_host_shared_lib_1" "<func_host_shared_lib_1\\(shared_lib_1_t\\)>"
+    gdb_test "print func_host_shared_lib_2" "<func_host_shared_lib_2\\(shared_lib_2_t\\)>"
+    gdb_test "print func_device_code_object_1" "<func_device_code_object_1\\(code_object_1_t\\)>"
+    gdb_test "print func_device_code_object_2" "<func_device_code_object_2\\(code_object_2_t\\)>"
+
+    # Functions defined in either both host shlibs or both code objects.
+    gdb_test "print func_host_shared_lib" "<func_host_shared_lib\\($func_host_shared_lib\\)>"
+    gdb_test "print func_device_code_object" "<func_device_code_object\\($func_device_code_object\\)>"
+
+    # Functions defined in one host shlib and one code object.
+    gdb_test "print func_1" "<func_1\\($func_1\\)>"
+    gdb_test "print func_2" "<func_2\\($func_2\\)>"
+
+    # Functions defined in main program and one objfile.
+    gdb_test "print func_main_shared_lib_1" "<func_main_shared_lib_1\\($func_main_shared_lib_1\\)>"
+    gdb_test "print func_main_shared_lib_2" "<func_main_shared_lib_2\\($func_main_shared_lib_2\\)>"
+    gdb_test "print func_main_code_object_1" "<func_main_code_object_1\\($func_main_code_object_1\\)>"
+    gdb_test "print func_main_code_object_2" "<func_main_code_object_2\\($func_main_code_object_2\\)>"
+}
+
+proc do_test {} {
+    clean_restart
+    gdb_load $::binfile
+
+    set shared_lib_1 [shared_lib_path 1]
+    set shared_lib_2 [shared_lib_path 2]
+    set code_object_1 [code_object_path 1]
+    set code_object_2 [code_object_path 2]
+
+    gdb_test_no_output "set args $shared_lib_1 $shared_lib_2 $code_object_1 $code_object_2" \
+	"set args"
+
+    with_rocm_gpu_lock {
+	if ![runto_main] {
+	    return
+	}
+
+	# Set a breakpoint in the main program after all libraries / code
+	# objects have been loaded, as well as one breakpoint in each objfile.
+	gdb_test "break func_main" \
+	    "Breakpoint $::decimal at $::hex.*"
+	gdb_test "with breakpoint pending on -- break func_host_shared_lib_1" \
+	    "Breakpoint $::decimal \\(func_host_shared_lib_1\\) pending."
+	gdb_test "with breakpoint pending on -- break func_host_shared_lib_2" \
+	    "Breakpoint $::decimal \\(func_host_shared_lib_2\\) pending."
+	gdb_test "with breakpoint pending on -- break func_device_code_object_1" \
+	    "Breakpoint $::decimal \\(func_device_code_object_1\\) pending."
+	gdb_test "with breakpoint pending on -- break func_device_code_object_2" \
+	    "Breakpoint $::decimal \\(func_device_code_object_2\\) pending."
+
+	# Stop in main program.  We expect:
+	#  - func_host_shared_lib to resolve to either shared lib (no preference)
+	#  - func_device_code_object to resolve to either code object (no preference)
+	#  - func_1 to resolve to shared_lib_1 (current solib provider)
+	#  - func_2 to resolve to shared_lib_2 (current solib provider)
+	#  - func_main_* symbols to resolve to main program's version (current objfile)
+	with_test_prefix "main program" {
+	    gdb_test "continue" "hit Breakpoint $::decimal, func_main .*"
+	    check_funcs \
+		"shared_lib_._t" \
+		"code_object_._t" \
+		"shared_lib_1_t" \
+		"shared_lib_2_t" \
+		"main_program_t" \
+		"main_program_t" \
+		"main_program_t" \
+		"main_program_t"
+	}
+
+	# Stop in host shared library 1.  We expect:
+	#  - func_host_shared_lib to resolve to shared_lib_1's version (current objfile)
+	#  - func_device_code_object to resolve to either code object (no preference)
+	#  - func_1 to resolve to shared_lib_1's version (current objfile)
+	#  - func_2 to resolve to shared_lib_2's version (current solib provider)
+	#  - func_main_shared_lib_1 to resolve to shared_lib_1's version (current objfile)
+	#  - func_main_shared_lib_2 to resolve to either main or shared_lib_2 (no preference)
+	#  - func_main_code_object_1 to resolve to either main or code_object_1 (no preference)
+	#  - func_main_code_object_2 to resolve to either main or code_object_2 (no preference)
+	with_test_prefix "host shared lib 1" {
+	    gdb_test "continue" "hit Breakpoint $::decimal, func_host_shared_lib_1 .*"
+	    check_funcs \
+		"shared_lib_1_t" \
+		"code_object_._t" \
+		"shared_lib_1_t" \
+		"shared_lib_2_t" \
+		"shared_lib_1_t" \
+		"(main_program_t|shared_lib_2_t)" \
+		"(main_program_t|code_object_1_t)" \
+		"(main_program_t|code_object_2_t)"
+	}
+
+	# Stop in host shared library 2.  Similar logic but now shared_lib_2
+	# is the current objfile.
+	with_test_prefix "host shared lib 2" {
+	    gdb_test "continue" "hit Breakpoint $::decimal, func_host_shared_lib_2 .*"
+	    check_funcs \
+		"shared_lib_2_t" \
+		"code_object_._t" \
+		"shared_lib_1_t" \
+		"shared_lib_2_t" \
+		"(main_program_t|shared_lib_1_t)" \
+		"shared_lib_2_t" \
+		"(main_program_t|code_object_1_t)" \
+		"(main_program_t|code_object_2_t)"
+	}
+
+	# Stop in ROCm code object 1.  We expect:
+	#  - func_host_shared_lib to resolve to either shared lib (no preference)
+	#  - func_device_code_object to resolve to code_object_1's version (current objfile)
+	#  - func_1 to resolve to code_object_1's version (current objfile)
+	#  - func_2 to resolve to code_object_2's version (current solib provider)
+	#  - func_main_shared_lib_1 to resolve to either main or shared_lib_1 (no preference)
+	#  - func_main_shared_lib_2 to resolve to either main or shared_lib_2 (no preference)
+	#  - func_main_code_object_1 to resolve to code_object_1's version (current objfile)
+	#  - func_main_code_object_2 to resolve to either main or code_object_2 (no preference)
+	with_test_prefix "device code object 1" {
+	    gdb_test "continue" "hit Breakpoint $::decimal, func_device_code_object_1 .*"
+	    check_funcs \
+		"shared_lib_._t" \
+		"code_object_1_t" \
+		"code_object_1_t" \
+		"code_object_2_t" \
+		"(main_program_t|shared_lib_1_t)" \
+		"(main_program_t|shared_lib_2_t)" \
+		"code_object_1_t" \
+		"(main_program_t|code_object_2_t)"
+	}
+
+	# Stop in ROCm code object 2.  Similar logic but now code_object_2
+	# is the current objfile.
+	with_test_prefix "device code object 2" {
+	    gdb_test "continue" "hit Breakpoint $::decimal, func_device_code_object_2 .*"
+	    check_funcs \
+		"shared_lib_._t" \
+		"code_object_2_t" \
+		"code_object_1_t" \
+		"code_object_2_t" \
+		"(main_program_t|shared_lib_1_t)" \
+		"(main_program_t|shared_lib_2_t)" \
+		"(main_program_t|code_object_1_t)" \
+		"code_object_2_t"
+	}
+    }
+}
+
+do_test
diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c
index 1df19a70adbf..7294deb80767 100644
--- a/gdb/windows-tdep.c
+++ b/gdb/windows-tdep.c
@@ -839,7 +839,7 @@ struct windows_solib_ops : target_solib_ops
 static solib_ops_up
 make_windows_solib_ops (program_space *pspace)
 {
-  return std::make_unique<windows_solib_ops> (pspace);
+  return std::make_unique<windows_solib_ops> (pspace, true);
 }
 
 /* Implement the "solib_create_inferior_hook" solib_ops method.  */
-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 00/11] Multiple solib_ops in a program_space
  2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
                   ` (10 preceding siblings ...)
  2025-12-09 19:32 ` [PATCH 11/11] gdb: multiple solib_ops per program space Simon Marchi
@ 2026-04-28 16:00 ` Simon Marchi
  2026-04-28 17:28   ` Tom Tromey
  11 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2026-04-28 16:00 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

Ping.

On 12/9/25 2:32 PM, Simon Marchi wrote:
> This series adds support for having multiple solib_ops instances
> providing solibs for a given program_space.  The main use case to
> justify this change is to make rocm_solib_ops less clunky.
> 
> When debugging ROCm programs, we need to have an instance of
> svr4_solib_ops providing solibs for host shared libraries, and
> an instance of rocm_solib_ops providing solibs for device code objects.
> Since there can only be one solib_ops in a program, rocm_solib_ops has
> to wrap and replce the svr4_solib_ops instance previously installed.
> 
> By allowing multiple solib_ops in the same program_space, the two
> solib_ops instances can co-exist without knowing each other.
> 
> The main change is contained in the last patch.  The patches before that
> are cleanup or preparatory patches.
> 
> Simon Marchi (11):
>   gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
>   gdb/solib: use early return in solib_read_symbols
>   gdb/solib-rocm: pass reference to cache to
>     rocm_code_object_stream_file
>   gdb/solib-rocm: add cached_fd to manage cached fd lifetime
>   gdb: de-constify some methods of solib_ops
>   gdb/solib-rocm: move per-inferior data to rocm_solib_ops
>   gdb/solib-rocm: save inferior in rocm_solib_ops
>   gdb/solib: add remove_solib function
>   gdb: add objfile -> solib backlink
>   gdb: change default objfile iteration order to start with current
>     objfile
>   gdb: multiple solib_ops per program space
> 
>  gdb/amd-dbgapi-target.c                  |   3 +-
>  gdb/infcmd.c                             |  16 +-
>  gdb/inferior.h                           |   6 +-
>  gdb/infrun.c                             |   7 +-
>  gdb/objfiles.h                           |  23 ++
>  gdb/observable.h                         |   8 +-
>  gdb/progspace.c                          |  91 +++++-
>  gdb/progspace.h                          |  57 +++-
>  gdb/solib-aix.c                          |  14 +-
>  gdb/solib-darwin.c                       |  14 +-
>  gdb/solib-dsbt.c                         |  10 +-
>  gdb/solib-frv.c                          |  10 +-
>  gdb/solib-rocm.c                         | 348 +++++++++-----------
>  gdb/solib-svr4.c                         |  60 ++--
>  gdb/solib-svr4.h                         |  20 +-
>  gdb/solib-target.c                       |   4 +-
>  gdb/solib-target.h                       |   2 +-
>  gdb/solib.c                              | 393 +++++++++++++----------
>  gdb/solib.h                              |  50 ++-
>  gdb/target.c                             |   2 +-
>  gdb/testsuite/gdb.rocm/symbol-lookup.cpp | 320 ++++++++++++++++++
>  gdb/testsuite/gdb.rocm/symbol-lookup.exp | 243 ++++++++++++++
>  gdb/windows-tdep.c                       |  46 +--
>  23 files changed, 1221 insertions(+), 526 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.cpp
>  create mode 100644 gdb/testsuite/gdb.rocm/symbol-lookup.exp
> 
> 
> base-commit: bfc430c99b66ab12643a65388c4ace925ebd68b2
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/11] gdb/solib: use early return in solib_read_symbols
  2025-12-09 19:32 ` [PATCH 02/11] gdb/solib: use early return in solib_read_symbols Simon Marchi
@ 2026-04-28 16:16   ` Tom Tromey
  2026-05-08  1:44     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 16:16 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> From: Simon Marchi <simon.marchi@polymtl.ca>
Simon> This reduces the indent a bit, and makes it clearer where the
Simon> "interesting" part of the function is.

Simon> At the same time, move the comment to solib.h.

Ok.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops
  2025-12-09 19:32 ` [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops Simon Marchi
@ 2026-04-28 16:19   ` Tom Tromey
  2026-05-08  2:02     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 16:19 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> Move the data currently stored in an inferior registry directly into
Simon> rocm_solib_ops.  This changes the storage of this data from "per
Simon> inferior" to "per program-space", but it should be equivalent, since
Simon> there is usually a 1:1 mapping between those two.  The only time there
Simon> isn't a 1:1 mapping is during a vfork, before the exec/exit, but no ROCm
Simon> activity happens during that slice of time.

Worth noting maybe that solib stuff is already per program space so this
probably is more correct in some pedantic way anyway.

Simon> The rocm_solib_ops constructor has to take an inferior as a parameter,
Simon> rather than a program_space, because of the fd cache.

I wonder if this could also use the program space.

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/11] gdb/solib: add remove_solib function
  2025-12-09 19:32 ` [PATCH 08/11] gdb/solib: add remove_solib function Simon Marchi
@ 2026-04-28 16:23   ` Tom Tromey
  0 siblings, 0 replies; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 16:23 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> From: Simon Marchi <simon.marchi@polymtl.ca>
Simon> Factor out the code to remove an solib into a new remove_solib function.
Simon> This function is used by a patch later in this series.

Simon> This patch only moves code, so no behavior change expected.

Looks good.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 09/11] gdb: add objfile -> solib backlink
  2025-12-09 19:32 ` [PATCH 09/11] gdb: add objfile -> solib backlink Simon Marchi
@ 2026-04-28 16:28   ` Tom Tromey
  2026-05-08  2:25     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 16:28 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> However, I learned that there may be more than one solib linked to one
Simon> objfile, so this backlink is actually a vector.

How / when can this happen?  It seems surprising to me, except maybe for
separate debug info.

Simon> @@ -732,10 +733,14 @@ remove_solib (program_space *pspace,
Simon>    notify_solib_unloaded (pspace, *solib_it, still_in_use, false);
 
Simon>    /* Unless the user loaded it explicitly, free SO's objfile.  */
Simon> -  if (solib_it->objfile != nullptr
Simon> -      && !(solib_it->objfile->flags & OBJF_USERLOADED)
Simon> -      && !still_in_use)
Simon> -    solib_it->objfile->unlink ();
Simon> +  if (solib_it->objfile != nullptr)
Simon> +    {
Simon> +      /* Remove the objfile -> solib backlink.  */
Simon> +      solib_it->objfile->remove_solib (*solib_it);
Simon> +
Simon> +      if (!(solib_it->objfile->flags & OBJF_USERLOADED) && !still_in_use)
Simon> +	solib_it->objfile->unlink ();
Simon> +    }
 
Doesn't this mean that if one solib is removed, then other solibs with
the same objfile will have a dangling objfile pointer?

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile
  2025-12-09 19:32 ` [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile Simon Marchi
@ 2026-04-28 16:42   ` Tom Tromey
  2026-05-08  2:40     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 16:42 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> The current default objfile search strategy, defined both in
Simon> solib_ops::iterate_over_objfiles_in_search_order (when the solib_ops
Simon> hasn't overridden it) and in
Simon> program_space::iterate_over_objfiles_in_search_order (when there is no
Simon> solib_ops in the program space, for some reason), is to scan the objfile
Simon> list linearly.

I think this is https://sourceware.org/bugzilla/show_bug.cgi?id=17003

Simon> Intuitively, searching the current objfile first makes sense, unless
Simon> there is some fancy symbol interposition happening, in which case that
Simon> solib_ops should probably implement a custom
Simon> iterate_over_objfiles_in_search_order.

Agreed.

Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 11/11] gdb: multiple solib_ops per program space
  2025-12-09 19:32 ` [PATCH 11/11] gdb: multiple solib_ops per program space Simon Marchi
@ 2026-04-28 17:27   ` Tom Tromey
  2026-05-08  2:52     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 17:27 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches, Simon Marchi

>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:

Simon> This patch adds the possibility for a program space to have multiple
Simon> solib_ops.  The motivation for this is to support ROCm (GPU) debugging
Simon> more cleanly.

Simon> Currently, when debugging a ROCm program, in order to be able to list
Simon> device code objects (the equivalent of shared libraries but for the
Simon> GPU), we install an instance of rocm_solib_ops as the program space's
Simon> sole solib_ops.  But in order to still be able to list host shared
Simon> libraries, the rocm_solib_ops wraps the previously installed solib_ops
Simon> (currently always an svr4_solib_ops instance) and forwards method calls
Simon> to it.

From this description and the comment in progspace.h, I wonder if this
is a case where an inferior should have multiple program spaces.

However I don't really want to block the work that's already been done.

Simon>  struct solib_ops
Simon>  {
Simon> -  explicit solib_ops (program_space *pspace)
Simon> -    : m_pspace (pspace)
Simon> +  explicit solib_ops (program_space *pspace, bool handle_main_objfile)
Simon> +    : m_pspace (pspace), m_handle_main_objfile (handle_main_objfile)

No need for 'explicit' any more.

Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 00/11] Multiple solib_ops in a program_space
  2026-04-28 16:00 ` [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
@ 2026-04-28 17:28   ` Tom Tromey
  2026-05-08  2:40     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Tom Tromey @ 2026-04-28 17:28 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Simon Marchi, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

Simon> Ping.

I sent some notes.  I don't really have comments on the ROCm stuff and I
didn't really look at it.

Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  2025-12-09 19:32 ` [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops Simon Marchi
@ 2026-05-07 17:56   ` Lancelot SIX
  2026-05-08  1:37     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Lancelot SIX @ 2026-05-07 17:56 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches

Hi Simon,

Please accept my appologies for the delay to get to this series.

On Tue, Dec 09, 2025 at 02:32:05PM -0500, Simon Marchi wrote:
> In some fork cases, the rocm_solib_target_inferior_created observer gets
> called while the child inferior (passed as a parameter) already has a
> rocm_solib_ops installed.  Since we unconditionally wrap the existing
> solib_ops with a new rocm_solib_ops, we can end up with a chain of
> multiple rocm_solib_ops, like:
> 
>   rocm_solib_ops -> rocm_solib_ops -> svr4_solib_ops
> 
> I don't think it is technically harmful as of now (unless the process
> does a ton of forks and the rocm_solib_ops accumulate), but it is for
> sure useless.  Add an assert for this in the rocm_solib_ops constructor,
> which reveals the cases where this happens.
> 
> When a fork happens with follow-fork-mode == child and detach-on-fork
> on, infrun's follow_fork_inferior function directly moves the program
> space from the parent the child inferior, as an optimization.  Coming
> into rocm_solib_target_inferior_created, the inferior's pspace
> unexpectedly already has a rocm_solib_ops pushed.
> 
> Fix this locally by using an inferior_forked observer.  In the scenario
> described above, remove the rocm_solib_ops and restore the host
> solib_ops as the program space's solib_ops, to make it look as if infrun
> didn't do this trick.  This requires adding two parameters to the
> inferior_forked observer (detach_on_fork and follow_child).
> 
> This should probably be done by infrun directly at some point.  The
> logic being that it's fine to do an optimization, but it should look as
> if it didn't occur.  If infrun created a brand new pspace for the child,
> there wouldn't be a rocm_solib_ops there already.  But I prefer a local
> fix for now.
> 
> Finally, there are also the vfork cases, where the child inferior shares
> the program space with its parent, and therefore the child's program
> space already has a rocm_solib_ops installed.  Address this case by
> returning early (the `inf->vfork_parent != nullptr` check), because
> there is nothing we want to do for a vfork child anyway.
> 
> Change-Id: I2e76d111e96f1e01b6799b04da9cdd6f6e2984c9
> ---
>  gdb/amd-dbgapi-target.c |  3 ++-
>  gdb/infrun.c            |  3 ++-
>  gdb/observable.h        |  8 ++++++--
>  gdb/solib-rocm.c        | 31 +++++++++++++++++++++++++++++++
>  4 files changed, 41 insertions(+), 4 deletions(-)
> 
> diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
> index d296f2830063..87b834739e2d 100644
> --- a/gdb/amd-dbgapi-target.c
> +++ b/gdb/amd-dbgapi-target.c
> @@ -2102,7 +2102,8 @@ amd_dbgapi_inferior_execd (inferior *exec_inf, inferior *follow_inf)
>  
>  static void
>  amd_dbgapi_inferior_forked (inferior *parent_inf, inferior *child_inf,
> -			    target_waitkind fork_kind)
> +			    target_waitkind fork_kind, bool detach_on_fork,
> +			    bool follow_child)
>  {
>    if (child_inf != nullptr)
>      {
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index bd114e16b80e..e0ffe95e4e45 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -645,7 +645,8 @@ holding the child stopped.  Try \"set %ps\" or \"%ps\".\n"),
>    target_follow_fork (child_inf, child_ptid, fork_kind, follow_child,
>  		      detach_fork);
>  
> -  gdb::observers::inferior_forked.notify (parent_inf, child_inf, fork_kind);
> +  gdb::observers::inferior_forked.notify (parent_inf, child_inf, fork_kind,
> +					  detach_fork, follow_child);
>  
>    /* target_follow_fork must leave the parent as the current inferior.  If we
>       want to follow the child, we make it the current one below.  */
> diff --git a/gdb/observable.h b/gdb/observable.h
> index 5f064cf1fc8c..c8cbdbad97d1 100644
> --- a/gdb/observable.h
> +++ b/gdb/observable.h
> @@ -92,9 +92,13 @@ extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
>     the child (because we follow only the child or we follow both), CHILD_INF
>     is the child inferior.  Otherwise, CHILD_INF is nullptr.
>  
> -   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.  */
> +   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.
> +
> +   detach_on_fork and follow_child represent the "detach-on-fork" and
> +   "follow-fork-mode" settings.  */

Just a nitpick here.

Should DETACH_ON_FORK and FOLLOW_CHILD be upper case?

>  extern observable<inferior */* parent_inf */, inferior */* child_inf */,
> -		  target_waitkind /* fork_kind */> inferior_forked;
> +		  target_waitkind /* fork_kind */, bool /* detach_on_fork */,
> +		  bool /* follow_child */> inferior_forked;
>  
>  /* The shared library specified by SOLIB has been loaded.  Note that
>     when gdb calls this observer, the library's symbols probably
> diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
> index d97ab25df5d0..a9573f8eefde 100644
> --- a/gdb/solib-rocm.c
> +++ b/gdb/solib-rocm.c
> @@ -164,8 +164,14 @@ struct rocm_solib_ops : public solib_ops
>    explicit rocm_solib_ops (program_space *pspace, solib_ops_up host_ops)
>      : solib_ops (pspace), m_host_ops (std::move (host_ops))
>    {
> +    gdb_assert (m_host_ops != nullptr);
> +    gdb_assert (dynamic_cast<rocm_solib_ops *> (m_host_ops.get ()) == nullptr);
>    }
>  
> +  /* Release the host solib_ops.  */
> +  solib_ops_up release_host_ops ()
> +  { return std::move (m_host_ops); }
> +
>    /* The methods implemented by rocm_solib_ops.  */
>    owning_intrusive_list<solib> current_sos () const override;
>    void create_inferior_hook (int from_tty) const override;
> @@ -808,6 +814,10 @@ rocm_update_solib_list ()
>  static void
>  rocm_solib_target_inferior_created (inferior *inf)
>  {
> +  /* A vfork child shares its pspace with its parent, do not touch anything.  */
> +  if (inf->vfork_parent != nullptr)
> +    return;
> +
>    get_solib_info (inf)->solib_list.clear ();
>  
>    auto prev_ops = inf->pspace->release_solib_ops ();
> @@ -839,6 +849,24 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
>    get_solib_info (exec_inf)->solib_list.clear ();
>  }
>  
> +static void
> +rocm_solib_target_inferior_forked (inferior *parent_inf, inferior *child_inf,
> +				   target_waitkind fork_kind,
> +				   bool detach_on_fork, bool follow_child)
> +{
> +  if (detach_on_fork && follow_child && fork_kind == TARGET_WAITKIND_FORKED)
> +    {
> +      /* In this particular configuration, infrun's follow_fork_inferior
> +	 function moves the parent pspace to the child directly.  Remove the
> +	 existing rocm_solib_ops from the child and restore the host solib_ops,
> +	 to make it look like a brand new pspace.  */
> +      auto rocm_ops_holder = child_inf->pspace->release_solib_ops ();
> +      auto rocm_ops
> +	= gdb::checked_static_cast<rocm_solib_ops *> (rocm_ops_holder.get ());
> +      child_inf->pspace->set_solib_ops (rocm_ops->release_host_ops ());

I think I am overall OK with the approach, even if I would prefer not
have the target explicitly rely on assumption about what the core does.

If the core in infrun was to change at some point, I expect this would
fail: the pspace's solib would not be a rocm_solib_ops, so
checked_static_cast would fail.  Should this use dynimac_cast and test
for nullptr instead?

Would it work to do it unconditionnaly (not check for detach_on_fork and
follow_inf), check if the pspace's solib is the rocm one, and if so use
the host one instead?

And last question, shouldn't this be in solib-rocm.c rather than
amd-dbgapi-target.c?

Best,
Lancelot.

> +    }
> +}
> +
>  INIT_GDB_FILE (rocm_solib)
>  {
>    /* The dependency on the amd-dbgapi exists because solib-rocm's
> @@ -852,4 +880,7 @@ INIT_GDB_FILE (rocm_solib)
>    gdb::observers::inferior_execd.attach
>      (rocm_solib_target_inferior_execd, "solib-rocm",
>       { &get_amd_dbgapi_target_inferior_execd_observer_token () });
> +
> +  gdb::observers::inferior_forked.attach
> +    (rocm_solib_target_inferior_forked, "solib-rocm");
>  }
> -- 
> 2.52.0

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 3/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file
  2025-12-09 19:32 ` [PATCH 03/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file Simon Marchi
@ 2026-05-07 18:20   ` Lancelot SIX
  2026-05-08  1:48     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Lancelot SIX @ 2026-05-07 18:20 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches

On Tue, Dec 09, 2025 at 02:32:07PM -0500, Simon Marchi wrote:
> ~rocm_code_object_stream_file obtains the fd cache to call "close" on
> using its m_inf field, obtaining the per-inferior info using
> get_solib_info.  A patch later in this series moves the fd cache from
> the per-inferior registry to the rocm_solib_ops directly.  This implies
> that we will need a new way to get a reference to the fd_cache owning
> the fd, as it won't be easy to get it from the inferior anymore.
> 
> To achieve this, update rocm_code_object_stream_file to keep a reference
> to the fd cache directly (because the rocm_code_object_stream is not
> meant to be copied nor moved, make the field a reference).  The inferior
> parameter and field are not needed anymore.
> 
> Change-Id: Ia10f8f125840274e51e188cafcb7384fdff92240
> ---
>  gdb/solib-rocm.c | 19 +++++++++----------
>  1 file changed, 9 insertions(+), 10 deletions(-)
> 
> diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
> index a9573f8eefde..bde34bafd8b1 100644
> --- a/gdb/solib-rocm.c
> +++ b/gdb/solib-rocm.c
> @@ -360,8 +360,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>  {
>    DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file);
>  
> -  rocm_code_object_stream_file (inferior *inf, int fd, ULONGEST offset,
> -				ULONGEST size);
> +  rocm_code_object_stream_file (rocm_solib_fd_cache &fd_cache, int fd,
> +				ULONGEST offset, ULONGEST size);

Just noting that this does not apply anymore, just need to change
"int fd" to "target_fd fd".

Other than that, this looks good to me.

Thanks.

Approved-by: Lancelot Six <lancelot.six@amd.com> (amdgpu)

>  
>    file_ptr read (bfd *abfd, void *buf, file_ptr size,
>  		 file_ptr offset) override;
> @@ -371,9 +371,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>    ~rocm_code_object_stream_file () override;
>  
>  protected:
> -
> -  /* The inferior owning this code object stream.  */
> -  inferior *m_inf;
> +  /* The fd cache owning this code object stream.  */
> +  rocm_solib_fd_cache &m_fd_cache;
>  
>    /* The target file descriptor for this stream.  */
>    int m_fd;
> @@ -387,8 +386,9 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>  };
>  
>  rocm_code_object_stream_file::rocm_code_object_stream_file
> -  (inferior *inf, int fd, ULONGEST offset, ULONGEST size)
> -  : m_inf (inf), m_fd (fd), m_offset (offset), m_size (size)
> +  (rocm_solib_fd_cache &fd_cache, int fd, ULONGEST offset,
> +   ULONGEST size)
> +  : m_fd_cache (fd_cache), m_fd (fd), m_offset (offset), m_size (size)
>  {
>  }
>  
> @@ -453,9 +453,8 @@ rocm_code_object_stream_file::size ()
>  
>  rocm_code_object_stream_file::~rocm_code_object_stream_file ()
>  {
> -  auto info = get_solib_info (m_inf);
>    fileio_error target_errno;
> -  if (info->fd_cache.close (m_fd, &target_errno) != 0)
> +  if (m_fd_cache.close (m_fd, &target_errno) != 0)
>      warning (_("Failed to close solib: %s"),
>  	     strerror (fileio_error_to_host (target_errno)));
>  }
> @@ -608,7 +607,7 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
>  	      return nullptr;
>  	    }
>  
> -	  return new rocm_code_object_stream_file (inferior, fd, offset,
> +	  return new rocm_code_object_stream_file (info->fd_cache, fd, offset,
>  						   size);
>  	}
>  
> -- 
> 2.52.0

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime
  2025-12-09 19:32 ` [PATCH 04/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime Simon Marchi
@ 2026-05-07 20:17   ` Lancelot SIX
  2026-05-08  1:52     ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Lancelot SIX @ 2026-05-07 20:17 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches

On Tue, Dec 09, 2025 at 02:32:08PM -0500, Simon Marchi wrote:
> Make the management of the cached fds automatic.
> 
> Change rocm_solib_fd_cache::open to return a cached_fd, an RAII type
> that calls rocm_solib_fd_cache::close on destruction.  cached_fd holds
> the actual fd and a reference to the cache it comes from.
> 
> Change rocm_code_object_stream_file to hold a cached_fd and remove the
> explicit destructor.
> 
> This is not essential, I just thought it would be nice.
> 
> Change-Id: I2529ac1caebbe6b7e6e60d228a403064f6f38d06
> ---
>  gdb/solib-rocm.c | 91 ++++++++++++++++++++++++++++++------------------
>  1 file changed, 58 insertions(+), 33 deletions(-)
> 
> diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
> index bde34bafd8b1..5560a6035184 100644
> --- a/gdb/solib-rocm.c
> +++ b/gdb/solib-rocm.c
> @@ -39,22 +39,58 @@ struct rocm_solib_fd_cache
>    explicit rocm_solib_fd_cache (inferior *inf) : m_inferior (inf) {}
>    DISABLE_COPY_AND_ASSIGN (rocm_solib_fd_cache);
>  
> +  /* The open method returns an object of this type.
> +
> +     On destruction, automatically call close to indicate the caller no longer
> +     uses the fd.  */
> +  struct cached_fd
> +  {
> +    cached_fd (int fd, rocm_solib_fd_cache &cache)
> +      : fd (fd),
> +	cache (cache)
> +    {
> +    }
> +
> +    cached_fd (cached_fd &&other)
> +      : fd (other.fd),
> +	cache (other.cache)
> +    {
> +      other.fd = -1;
> +    }
> +
> +    ~cached_fd ()
> +    {
> +      if (fd == -1)
> +	return;
> +
> +      fileio_error target_errno;
> +      if (cache.close (fd, &target_errno) != 0)
> +	warning (_("Failed to close solib: %s"),
> +		 strerror (fileio_error_to_host (target_errno)));
> +    }
> +
> +    DISABLE_COPY_AND_ASSIGN (cached_fd);
> +
> +    int fd;

Given some changes since this patch was sent, this is now a target_fd.

> +    rocm_solib_fd_cache &cache;

the cache field is never accessed outside of the class, there is no need
to have it public.

I expect quite a few conflicts when rebasing this patch, nevertheless
the direction so far works for me.

Best,
Lancelot.

> +  };
> +
>    /* Return a read-only file descriptor to FILENAME and increment the
>       associated reference count.
>  
>       Open the file FILENAME if it is not already opened, reuse the existing file
>       descriptor otherwise.
>  
> -     On error -1 is returned, and TARGET_ERRNO is set.  */
> -  int open (const std::string &filename, fileio_error *target_errno);
> +     On error, return a cached_fd with .fd == -1 and set *TARGET_ERRNO.  */
> +  cached_fd open (const std::string &filename, fileio_error *target_errno);
>  
> +private:
>    /* Decrement the reference count to FD and close FD if the reference count
>       reaches 0.
>  
>       On success, return 0.  On error, return -1 and set TARGET_ERRNO.  */
>    int close (int fd, fileio_error *target_errno);
>  
> -private:
>    struct refcnt_fd
>    {
>      refcnt_fd (int fd, int refcnt) : fd (fd), refcnt (refcnt) {}
> @@ -72,7 +108,7 @@ struct rocm_solib_fd_cache
>    gdb::unordered_map<std::string, refcnt_fd> m_cache;
>  };
>  
> -int
> +rocm_solib_fd_cache::cached_fd
>  rocm_solib_fd_cache::open (const std::string &filename,
>  			   fileio_error *target_errno)
>  {
> @@ -83,11 +119,14 @@ rocm_solib_fd_cache::open (const std::string &filename,
>        int fd
>  	= target_fileio_open (m_inferior, filename.c_str (), FILEIO_O_RDONLY,
>  			      false, 0, target_errno);
> -      if (fd != -1)
> -	m_cache.emplace (std::piecewise_construct,
> -			 std::forward_as_tuple (filename),
> -			 std::forward_as_tuple (fd, 1));
> -      return fd;
> +      if (fd == -1)
> +	return cached_fd { -1, *this };
> +
> +      m_cache.emplace (std::piecewise_construct,
> +		       std::forward_as_tuple (filename),
> +		       std::forward_as_tuple (fd, 1));
> +
> +      return cached_fd { fd, *this };
>      }
>    else
>      {
> @@ -95,7 +134,7 @@ rocm_solib_fd_cache::open (const std::string &filename,
>  	 already opened FD.  */
>        it->second.refcnt++;
>        gdb_assert (it->second.fd != -1);
> -      return it->second.fd;
> +      return cached_fd { it->second.fd, *this };
>      }
>  }
>  
> @@ -360,7 +399,7 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>  {
>    DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file);
>  
> -  rocm_code_object_stream_file (rocm_solib_fd_cache &fd_cache, int fd,
> +  rocm_code_object_stream_file (rocm_solib_fd_cache::cached_fd fd,
>  				ULONGEST offset, ULONGEST size);
>  
>    file_ptr read (bfd *abfd, void *buf, file_ptr size,
> @@ -368,14 +407,9 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>  
>    LONGEST size () override;
>  
> -  ~rocm_code_object_stream_file () override;
> -
>  protected:
> -  /* The fd cache owning this code object stream.  */
> -  rocm_solib_fd_cache &m_fd_cache;
> -
>    /* The target file descriptor for this stream.  */
> -  int m_fd;
> +  rocm_solib_fd_cache::cached_fd m_fd;
>  
>    /* The offset of the ELF file image in the target file.  */
>    ULONGEST m_offset;
> @@ -386,9 +420,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>  };
>  
>  rocm_code_object_stream_file::rocm_code_object_stream_file
> -  (rocm_solib_fd_cache &fd_cache, int fd, ULONGEST offset,
> -   ULONGEST size)
> -  : m_fd_cache (fd_cache), m_fd (fd), m_offset (offset), m_size (size)
> +  (rocm_solib_fd_cache::cached_fd fd, ULONGEST offset, ULONGEST size)
> +  : m_fd (std::move (fd)), m_offset (offset), m_size (size)
>  {
>  }
>  
> @@ -403,7 +436,7 @@ rocm_code_object_stream_file::read (bfd *, void *buf, file_ptr size,
>        QUIT;
>  
>        file_ptr bytes_read
> -	= target_fileio_pread (m_fd, static_cast<gdb_byte *> (buf) + nbytes,
> +	= target_fileio_pread (m_fd.fd, static_cast<gdb_byte *> (buf) + nbytes,
>  			       size, m_offset + offset + nbytes,
>  			       &target_errno);
>  
> @@ -431,7 +464,7 @@ rocm_code_object_stream_file::size ()
>      {
>        fileio_error target_errno;
>        struct stat stat;
> -      if (target_fileio_fstat (m_fd, &stat, &target_errno) < 0)
> +      if (target_fileio_fstat (m_fd.fd, &stat, &target_errno) < 0)
>  	{
>  	  errno = fileio_error_to_host (target_errno);
>  	  bfd_set_error (bfd_error_system_call);
> @@ -451,14 +484,6 @@ rocm_code_object_stream_file::size ()
>    return m_size;
>  }
>  
> -rocm_code_object_stream_file::~rocm_code_object_stream_file ()
> -{
> -  fileio_error target_errno;
> -  if (m_fd_cache.close (m_fd, &target_errno) != 0)
> -    warning (_("Failed to close solib: %s"),
> -	     strerror (fileio_error_to_host (target_errno)));
> -}
> -
>  /* Interface to a code object which lives in the inferior's memory.  */
>  
>  struct rocm_code_object_stream_memory final : public rocm_code_object_stream
> @@ -598,16 +623,16 @@ rocm_bfd_iovec_open (bfd *abfd, inferior *inferior)
>  	{
>  	  auto info = get_solib_info (inferior);
>  	  fileio_error target_errno;
> -	  int fd = info->fd_cache.open (decoded_path, &target_errno);
> +	  auto fd = info->fd_cache.open (decoded_path, &target_errno);
>  
> -	  if (fd == -1)
> +	  if (fd.fd == -1)
>  	    {
>  	      errno = fileio_error_to_host (target_errno);
>  	      bfd_set_error (bfd_error_system_call);
>  	      return nullptr;
>  	    }
>  
> -	  return new rocm_code_object_stream_file (info->fd_cache, fd, offset,
> +	  return new rocm_code_object_stream_file (std::move (fd), offset,
>  						   size);
>  	}
>  
> -- 
> 2.52.0

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  2026-05-07 17:56   ` [PATCH 1/11] " Lancelot SIX
@ 2026-05-08  1:37     ` Simon Marchi
  2026-05-08 10:36       ` Lancelot SIX
  0 siblings, 1 reply; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  1:37 UTC (permalink / raw)
  To: Lancelot SIX, Simon Marchi; +Cc: gdb-patches

On 5/7/26 1:56 PM, Lancelot SIX wrote:
>> diff --git a/gdb/observable.h b/gdb/observable.h
>> index 5f064cf1fc8c..c8cbdbad97d1 100644
>> --- a/gdb/observable.h
>> +++ b/gdb/observable.h
>> @@ -92,9 +92,13 @@ extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
>>     the child (because we follow only the child or we follow both), CHILD_INF
>>     is the child inferior.  Otherwise, CHILD_INF is nullptr.
>>  
>> -   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.  */
>> +   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.
>> +
>> +   detach_on_fork and follow_child represent the "detach-on-fork" and
>> +   "follow-fork-mode" settings.  */
> 
> Just a nitpick here.
> 
> Should DETACH_ON_FORK and FOLLOW_CHILD be upper case?

Yes (it's not a nitpick, you're objectively right).

>> @@ -839,6 +849,24 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
>>    get_solib_info (exec_inf)->solib_list.clear ();
>>  }
>>  
>> +static void
>> +rocm_solib_target_inferior_forked (inferior *parent_inf, inferior *child_inf,
>> +				   target_waitkind fork_kind,
>> +				   bool detach_on_fork, bool follow_child)
>> +{
>> +  if (detach_on_fork && follow_child && fork_kind == TARGET_WAITKIND_FORKED)
>> +    {
>> +      /* In this particular configuration, infrun's follow_fork_inferior
>> +	 function moves the parent pspace to the child directly.  Remove the
>> +	 existing rocm_solib_ops from the child and restore the host solib_ops,
>> +	 to make it look like a brand new pspace.  */
>> +      auto rocm_ops_holder = child_inf->pspace->release_solib_ops ();
>> +      auto rocm_ops
>> +	= gdb::checked_static_cast<rocm_solib_ops *> (rocm_ops_holder.get ());
>> +      child_inf->pspace->set_solib_ops (rocm_ops->release_host_ops ());
> 
> I think I am overall OK with the approach, even if I would prefer not
> have the target explicitly rely on assumption about what the core does.
> 
> If the core in infrun was to change at some point, I expect this would
> fail: the pspace's solib would not be a rocm_solib_ops, so
> checked_static_cast would fail.  Should this use dynimac_cast and test
> for nullptr instead?
>
> Would it work to do it unconditionnaly (not check for detach_on_fork and
> follow_inf), check if the pspace's solib is the rocm one, and if so use
> the host one instead?

I also hesitated between the two options:

 1) do a very targeted check + checked_static_cast
 2) just do a dynamic cast and see

We can afford to do 1), because we control both sides (core and target).
It's not like it's an external lib that could change out of our contorl.
If the core ever changes in this regard, yes the checked_static_cast
will fail (in debug mode), then we simply update the target accordingly.
I like doing it this way, so that our expectations are written in code
and verified.  It also has the potential to catch some unwanted changes
in the core, which the dynamic_cast would hide.

So I'm still team 1) for the moment.  I guess in general I'm team "use
asserts generously".

> And last question, shouldn't this be in solib-rocm.c rather than
> amd-dbgapi-target.c?

I'm confused, it is in solib-rocm.c currently, isn't it?

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/11] gdb/solib: use early return in solib_read_symbols
  2026-04-28 16:16   ` Tom Tromey
@ 2026-05-08  1:44     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  1:44 UTC (permalink / raw)
  To: Tom Tromey, Simon Marchi; +Cc: gdb-patches

On 4/28/26 12:16 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> From: Simon Marchi <simon.marchi@polymtl.ca>
> Simon> This reduces the indent a bit, and makes it clearer where the
> Simon> "interesting" part of the function is.
> 
> Simon> At the same time, move the comment to solib.h.
> 
> Ok.
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom

Thanks, I pushed this patch.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 3/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file
  2026-05-07 18:20   ` [PATCH 3/11] " Lancelot SIX
@ 2026-05-08  1:48     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  1:48 UTC (permalink / raw)
  To: Lancelot SIX; +Cc: gdb-patches

On 5/7/26 2:20 PM, Lancelot SIX wrote:
> On Tue, Dec 09, 2025 at 02:32:07PM -0500, Simon Marchi wrote:
>> ~rocm_code_object_stream_file obtains the fd cache to call "close" on
>> using its m_inf field, obtaining the per-inferior info using
>> get_solib_info.  A patch later in this series moves the fd cache from
>> the per-inferior registry to the rocm_solib_ops directly.  This implies
>> that we will need a new way to get a reference to the fd_cache owning
>> the fd, as it won't be easy to get it from the inferior anymore.
>>
>> To achieve this, update rocm_code_object_stream_file to keep a reference
>> to the fd cache directly (because the rocm_code_object_stream is not
>> meant to be copied nor moved, make the field a reference).  The inferior
>> parameter and field are not needed anymore.
>>
>> Change-Id: Ia10f8f125840274e51e188cafcb7384fdff92240
>> ---
>>  gdb/solib-rocm.c | 19 +++++++++----------
>>  1 file changed, 9 insertions(+), 10 deletions(-)
>>
>> diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
>> index a9573f8eefde..bde34bafd8b1 100644
>> --- a/gdb/solib-rocm.c
>> +++ b/gdb/solib-rocm.c
>> @@ -360,8 +360,8 @@ struct rocm_code_object_stream_file final : rocm_code_object_stream
>>  {
>>    DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file);
>>  
>> -  rocm_code_object_stream_file (inferior *inf, int fd, ULONGEST offset,
>> -				ULONGEST size);
>> +  rocm_code_object_stream_file (rocm_solib_fd_cache &fd_cache, int fd,
>> +				ULONGEST offset, ULONGEST size);
> 
> Just noting that this does not apply anymore, just need to change
> "int fd" to "target_fd fd".

Thanks, I saw that while rebasing.

> 
> Other than that, this looks good to me.
> 
> Thanks.
> 
> Approved-by: Lancelot Six <lancelot.six@amd.com> (amdgpu)

Ack, thanks.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime
  2026-05-07 20:17   ` [PATCH 4/11] " Lancelot SIX
@ 2026-05-08  1:52     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  1:52 UTC (permalink / raw)
  To: Lancelot SIX; +Cc: gdb-patches

On 5/7/26 4:17 PM, Lancelot SIX wrote:
> On Tue, Dec 09, 2025 at 02:32:08PM -0500, Simon Marchi wrote:
>> Make the management of the cached fds automatic.
>>
>> Change rocm_solib_fd_cache::open to return a cached_fd, an RAII type
>> that calls rocm_solib_fd_cache::close on destruction.  cached_fd holds
>> the actual fd and a reference to the cache it comes from.
>>
>> Change rocm_code_object_stream_file to hold a cached_fd and remove the
>> explicit destructor.
>>
>> This is not essential, I just thought it would be nice.
>>
>> Change-Id: I2529ac1caebbe6b7e6e60d228a403064f6f38d06
>> ---
>>  gdb/solib-rocm.c | 91 ++++++++++++++++++++++++++++++------------------
>>  1 file changed, 58 insertions(+), 33 deletions(-)
>>
>> diff --git a/gdb/solib-rocm.c b/gdb/solib-rocm.c
>> index bde34bafd8b1..5560a6035184 100644
>> --- a/gdb/solib-rocm.c
>> +++ b/gdb/solib-rocm.c
>> @@ -39,22 +39,58 @@ struct rocm_solib_fd_cache
>>    explicit rocm_solib_fd_cache (inferior *inf) : m_inferior (inf) {}
>>    DISABLE_COPY_AND_ASSIGN (rocm_solib_fd_cache);
>>  
>> +  /* The open method returns an object of this type.
>> +
>> +     On destruction, automatically call close to indicate the caller no longer
>> +     uses the fd.  */
>> +  struct cached_fd
>> +  {
>> +    cached_fd (int fd, rocm_solib_fd_cache &cache)
>> +      : fd (fd),
>> +	cache (cache)
>> +    {
>> +    }
>> +
>> +    cached_fd (cached_fd &&other)
>> +      : fd (other.fd),
>> +	cache (other.cache)
>> +    {
>> +      other.fd = -1;
>> +    }
>> +
>> +    ~cached_fd ()
>> +    {
>> +      if (fd == -1)
>> +	return;
>> +
>> +      fileio_error target_errno;
>> +      if (cache.close (fd, &target_errno) != 0)
>> +	warning (_("Failed to close solib: %s"),
>> +		 strerror (fileio_error_to_host (target_errno)));
>> +    }
>> +
>> +    DISABLE_COPY_AND_ASSIGN (cached_fd);
>> +
>> +    int fd;
> 
> Given some changes since this patch was sent, this is now a target_fd.

Ok, I will rename the type to cached_target_fd.

> 
>> +    rocm_solib_fd_cache &cache;
> 
> the cache field is never accessed outside of the class, there is no need
> to have it public.

Ok, while at it I made the fd field private too and added a getter.

> I expect quite a few conflicts when rebasing this patch, nevertheless
> the direction so far works for me.

Not too bad, because the fd didn't change meaning, it juste changed to a
stricter type.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops
  2026-04-28 16:19   ` Tom Tromey
@ 2026-05-08  2:02     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:02 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, Simon Marchi

On 4/28/26 12:19 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> Move the data currently stored in an inferior registry directly into
> Simon> rocm_solib_ops.  This changes the storage of this data from "per
> Simon> inferior" to "per program-space", but it should be equivalent, since
> Simon> there is usually a 1:1 mapping between those two.  The only time there
> Simon> isn't a 1:1 mapping is during a vfork, before the exec/exit, but no ROCm
> Simon> activity happens during that slice of time.
> 
> Worth noting maybe that solib stuff is already per program space so this
> probably is more correct in some pedantic way anyway.

That's right.

> Simon> The rocm_solib_ops constructor has to take an inferior as a parameter,
> Simon> rather than a program_space, because of the fd cache.
> 
> I wonder if this could also use the program space.

rocm_solib_fd_cache::open calls target_fileio_open, which requires an
inferior.  If rocm_solib_ops took a program space instead of an
inferior, we would probably call find_inferior_for_program_space in
rocm_solib_fd_cache::open to get back to an inferior.  Might as well
keep it inferior all the way.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 07/11] gdb/solib-rocm: save inferior in rocm_solib_ops
       [not found]   ` <87a4unm59w.fsf@tromey.com>
@ 2026-05-08  2:10     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:10 UTC (permalink / raw)
  To: Tom Tromey, Simon Marchi; +Cc: gdb-patches

On 4/28/26 12:14 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> From: Simon Marchi <simon.marchi@polymtl.ca>
> Simon> Saving the inferior the rocm_solib_ops is for can avoid using
> Simon> `current_inferior ()` at a couple of places.
> 
> I had to read this a few times to parse it.

You're right, me too, after a few months.

> Maybe rewording it to something like "Storing the inferior in
> rocm_solib_ops lets us avoid some calls to current_inferior()" or
> something along those lines.

I took your suggestion as-is, it's straight to the point.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 09/11] gdb: add objfile -> solib backlink
  2026-04-28 16:28   ` Tom Tromey
@ 2026-05-08  2:25     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:25 UTC (permalink / raw)
  To: Tom Tromey, Simon Marchi; +Cc: gdb-patches

On 4/28/26 12:28 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> However, I learned that there may be more than one solib linked to one
> Simon> objfile, so this backlink is actually a vector.
> 
> How / when can this happen?  It seems surprising to me, except maybe for
> separate debug info.

The only case I'm aware of is the dynamic linker itself.  It appears in
all namespaces, with the same load address.  We get one solib for each
"view" of it, but all these solibs point to the same objfile.

I think I learned that around this time:

https://gitlab.com/gnutools/binutils-gdb/-/commit/d33a66a31134bd63c4945d0d570e7296aaac3574

I will make this sentence more precise.

> 
> Simon> @@ -732,10 +733,14 @@ remove_solib (program_space *pspace,
> Simon>    notify_solib_unloaded (pspace, *solib_it, still_in_use, false);
>  
> Simon>    /* Unless the user loaded it explicitly, free SO's objfile.  */
> Simon> -  if (solib_it->objfile != nullptr
> Simon> -      && !(solib_it->objfile->flags & OBJF_USERLOADED)
> Simon> -      && !still_in_use)
> Simon> -    solib_it->objfile->unlink ();
> Simon> +  if (solib_it->objfile != nullptr)
> Simon> +    {
> Simon> +      /* Remove the objfile -> solib backlink.  */
> Simon> +      solib_it->objfile->remove_solib (*solib_it);
> Simon> +
> Simon> +      if (!(solib_it->objfile->flags & OBJF_USERLOADED) && !still_in_use)
> Simon> +	solib_it->objfile->unlink ();
> Simon> +    }
>  
> Doesn't this mean that if one solib is removed, then other solibs with
> the same objfile will have a dangling objfile pointer?

I don't think so because of the still_in_use check, which is initialized
with:

  bool still_in_use
    = solib_it->objfile != nullptr && solib_used (pspace, *solib_it);

solib_used is:

  /* Return true if KNOWN->objfile is used by any other solib object
     in PSPACE's list of shared libraries.  Return false otherwise.  */

  static bool
  solib_used (program_space *pspace, const solib &known)
  {
    for (const solib &pivot : pspace->solibs ())
      if (&pivot != &known && pivot.objfile == known.objfile)
        return true;

    return false;
  }

If another solib points to the same objfile, we won't unlink the
objfile.  "solib_used" is not the best name for this function.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile
  2026-04-28 16:42   ` Tom Tromey
@ 2026-05-08  2:40     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:40 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, Simon Marchi

On 4/28/26 12:42 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> The current default objfile search strategy, defined both in
> Simon> solib_ops::iterate_over_objfiles_in_search_order (when the solib_ops
> Simon> hasn't overridden it) and in
> Simon> program_space::iterate_over_objfiles_in_search_order (when there is no
> Simon> solib_ops in the program space, for some reason), is to scan the objfile
> Simon> list linearly.
> 
> I think this is https://sourceware.org/bugzilla/show_bug.cgi?id=17003

Oh wow, thanks for the reference.  It makes me feel better that I am not
the only one to think this should be the default.

> Simon> Intuitively, searching the current objfile first makes sense, unless
> Simon> there is some fancy symbol interposition happening, in which case that
> Simon> solib_ops should probably implement a custom
> Simon> iterate_over_objfiles_in_search_order.
> 
> Agreed.
> 
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 00/11] Multiple solib_ops in a program_space
  2026-04-28 17:28   ` Tom Tromey
@ 2026-05-08  2:40     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:40 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Simon Marchi, gdb-patches

On 4/28/26 1:28 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
> 
> Simon> Ping.
> 
> I sent some notes.  I don't really have comments on the ROCm stuff and I
> didn't really look at it.

Thanks, Lancelot will take care of those.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 11/11] gdb: multiple solib_ops per program space
  2026-04-28 17:27   ` Tom Tromey
@ 2026-05-08  2:52     ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-05-08  2:52 UTC (permalink / raw)
  To: Tom Tromey, Simon Marchi; +Cc: gdb-patches

On 4/28/26 1:27 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
> 
> Simon> This patch adds the possibility for a program space to have multiple
> Simon> solib_ops.  The motivation for this is to support ROCm (GPU) debugging
> Simon> more cleanly.
> 
> Simon> Currently, when debugging a ROCm program, in order to be able to list
> Simon> device code objects (the equivalent of shared libraries but for the
> Simon> GPU), we install an instance of rocm_solib_ops as the program space's
> Simon> sole solib_ops.  But in order to still be able to list host shared
> Simon> libraries, the rocm_solib_ops wraps the previously installed solib_ops
> Simon> (currently always an svr4_solib_ops instance) and forwards method calls
> Simon> to it.
> 
> From this description and the comment in progspace.h, I wonder if this
> is a case where an inferior should have multiple program spaces.

I never thought about that.

The host and device do share a single virtual address space.  This
doesn't mean that both host and device can necessarily access each
other's memory transparently, but the host stuff and the device stuff
won't overlap.  So perhaps that could indeed be modeled by one struct
address_space bound to two struct program_space.  I'll discuss that with
my team.

> However I don't really want to block the work that's already been done.

Going to multiple solib ops is a big design change, so it if turns out
that it's not the right one, it's better to know now.

> Simon>  struct solib_ops
> Simon>  {
> Simon> -  explicit solib_ops (program_space *pspace)
> Simon> -    : m_pspace (pspace)
> Simon> +  explicit solib_ops (program_space *pspace, bool handle_main_objfile)
> Simon> +    : m_pspace (pspace), m_handle_main_objfile (handle_main_objfile)
> 
> No need for 'explicit' any more.

Ack.

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  2026-05-08  1:37     ` Simon Marchi
@ 2026-05-08 10:36       ` Lancelot SIX
  2026-06-08 18:36         ` Simon Marchi
  0 siblings, 1 reply; 35+ messages in thread
From: Lancelot SIX @ 2026-05-08 10:36 UTC (permalink / raw)
  To: Simon Marchi, Simon Marchi; +Cc: gdb-patches



On 08/05/2026 02:37, Simon Marchi wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On 5/7/26 1:56 PM, Lancelot SIX wrote:
>>> diff --git a/gdb/observable.h b/gdb/observable.h
>>> index 5f064cf1fc8c..c8cbdbad97d1 100644
>>> --- a/gdb/observable.h
>>> +++ b/gdb/observable.h
>>> @@ -92,9 +92,13 @@ extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
>>>      the child (because we follow only the child or we follow both), CHILD_INF
>>>      is the child inferior.  Otherwise, CHILD_INF is nullptr.
>>>
>>> -   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.  */
>>> +   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.
>>> +
>>> +   detach_on_fork and follow_child represent the "detach-on-fork" and
>>> +   "follow-fork-mode" settings.  */
>>
>> Just a nitpick here.
>>
>> Should DETACH_ON_FORK and FOLLOW_CHILD be upper case?
> 
> Yes (it's not a nitpick, you're objectively right).
> 
>>> @@ -839,6 +849,24 @@ rocm_solib_target_inferior_execd (inferior *exec_inf, inferior *follow_inf)
>>>     get_solib_info (exec_inf)->solib_list.clear ();
>>>   }
>>>
>>> +static void
>>> +rocm_solib_target_inferior_forked (inferior *parent_inf, inferior *child_inf,
>>> +                               target_waitkind fork_kind,
>>> +                               bool detach_on_fork, bool follow_child)
>>> +{
>>> +  if (detach_on_fork && follow_child && fork_kind == TARGET_WAITKIND_FORKED)
>>> +    {
>>> +      /* In this particular configuration, infrun's follow_fork_inferior
>>> +     function moves the parent pspace to the child directly.  Remove the
>>> +     existing rocm_solib_ops from the child and restore the host solib_ops,
>>> +     to make it look like a brand new pspace.  */
>>> +      auto rocm_ops_holder = child_inf->pspace->release_solib_ops ();
>>> +      auto rocm_ops
>>> +    = gdb::checked_static_cast<rocm_solib_ops *> (rocm_ops_holder.get ());
>>> +      child_inf->pspace->set_solib_ops (rocm_ops->release_host_ops ());
>>
>> I think I am overall OK with the approach, even if I would prefer not
>> have the target explicitly rely on assumption about what the core does.
>>
>> If the core in infrun was to change at some point, I expect this would
>> fail: the pspace's solib would not be a rocm_solib_ops, so
>> checked_static_cast would fail.  Should this use dynimac_cast and test
>> for nullptr instead?
>>
>> Would it work to do it unconditionnaly (not check for detach_on_fork and
>> follow_inf), check if the pspace's solib is the rocm one, and if so use
>> the host one instead?
> 
> I also hesitated between the two options:
> 
>   1) do a very targeted check + checked_static_cast
>   2) just do a dynamic cast and see
> 
> We can afford to do 1), because we control both sides (core and target).
> It's not like it's an external lib that could change out of our contorl.
> If the core ever changes in this regard, yes the checked_static_cast
> will fail (in debug mode), then we simply update the target accordingly.
> I like doing it this way, so that our expectations are written in code
> and verified.  It also has the potential to catch some unwanted changes
> in the core, which the dynamic_cast would hide.
> 

My main concern would be that most people contributing to GDB do not 
build the amdgpu target (requires the rocm-dbgapi library to be 
available), so it would be easy for many to build with 
--enable-targets-all and not enable this code path.

That being said, we (at AMD) would pull such change from master and hit 
a CI failure quite promptly, so I do not expect this would be un-noticed 
for very long.

> So I'm still team 1) for the moment.  I guess in general I'm team "use
> asserts generously".
> 

I am happy to follow you there and keep this approach.

>> And last question, shouldn't this be in solib-rocm.c rather than
>> amd-dbgapi-target.c?
> 
> I'm confused, it is in solib-rocm.c currently, isn't it?

You are absolutely right, somehow I thought I was in amd-dbgapi-target.c…

Best,
Lancelot.

> 
> Simon


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops
  2026-05-08 10:36       ` Lancelot SIX
@ 2026-06-08 18:36         ` Simon Marchi
  0 siblings, 0 replies; 35+ messages in thread
From: Simon Marchi @ 2026-06-08 18:36 UTC (permalink / raw)
  To: Lancelot SIX, Simon Marchi; +Cc: gdb-patches

> My main concern would be that most people contributing to GDB do not
> build the amdgpu target (requires the rocm-dbgapi library to be
> available), so it would be easy for many to build with
> --enable-targets-all and not enable this code path.

Even if some people built against dbgapi (which is not that difficult),
even less people would actually run the rocm tests.

> That being said, we (at AMD) would pull such change from master and
> hit a CI failure quite promptly, so I do not expect this would be
> un-noticed for very long.

Exactly, if the core happened to change, I think we would like to know
(via an assert breaking) so we can check if something needs to change on
our side.

> 
>> So I'm still team 1) for the moment.  I guess in general I'm team
>> "use asserts generously".
>>
> 
> I am happy to follow you there and keep this approach.

Ok, so is this Approved-By?

Thanks,

Simon

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2026-06-08 18:36 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-09 19:32 [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
2025-12-09 19:32 ` [PATCH 01/11] gdb/solib-rocm: assert that host ops isn't rocm_solib_ops Simon Marchi
2026-05-07 17:56   ` [PATCH 1/11] " Lancelot SIX
2026-05-08  1:37     ` Simon Marchi
2026-05-08 10:36       ` Lancelot SIX
2026-06-08 18:36         ` Simon Marchi
2025-12-09 19:32 ` [PATCH 02/11] gdb/solib: use early return in solib_read_symbols Simon Marchi
2026-04-28 16:16   ` Tom Tromey
2026-05-08  1:44     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 03/11] gdb/solib-rocm: pass reference to cache to rocm_code_object_stream_file Simon Marchi
2026-05-07 18:20   ` [PATCH 3/11] " Lancelot SIX
2026-05-08  1:48     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 04/11] gdb/solib-rocm: add cached_fd to manage cached fd lifetime Simon Marchi
2026-05-07 20:17   ` [PATCH 4/11] " Lancelot SIX
2026-05-08  1:52     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 05/11] gdb: de-constify some methods of solib_ops Simon Marchi
2025-12-09 19:32 ` [PATCH 06/11] gdb/solib-rocm: move per-inferior data to rocm_solib_ops Simon Marchi
2026-04-28 16:19   ` Tom Tromey
2026-05-08  2:02     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 07/11] gdb/solib-rocm: save inferior in rocm_solib_ops Simon Marchi
     [not found]   ` <87a4unm59w.fsf@tromey.com>
2026-05-08  2:10     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 08/11] gdb/solib: add remove_solib function Simon Marchi
2026-04-28 16:23   ` Tom Tromey
2025-12-09 19:32 ` [PATCH 09/11] gdb: add objfile -> solib backlink Simon Marchi
2026-04-28 16:28   ` Tom Tromey
2026-05-08  2:25     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 10/11] gdb: change default objfile iteration order to start with current objfile Simon Marchi
2026-04-28 16:42   ` Tom Tromey
2026-05-08  2:40     ` Simon Marchi
2025-12-09 19:32 ` [PATCH 11/11] gdb: multiple solib_ops per program space Simon Marchi
2026-04-28 17:27   ` Tom Tromey
2026-05-08  2:52     ` Simon Marchi
2026-04-28 16:00 ` [PATCH 00/11] Multiple solib_ops in a program_space Simon Marchi
2026-04-28 17:28   ` Tom Tromey
2026-05-08  2:40     ` Simon Marchi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox