gdb: Implement stop-on-solib-events for GPU code objects

Message ID 20261001104000.7968-1-bfilipov@amd.com
State New
Headers
Series gdb: Implement stop-on-solib-events for GPU code objects |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gdb_build--master-arm fail Patch failed to apply

Commit Message

Bratislav Filipovic Oct. 1, 2026, 10:35 a.m. UTC
  GDB's "set stop-on-solib-events 1" setting allows users to stop execution
when shared libraries are loaded or unloaded, enabling inspection and
breakpoint placement before library code executes. This feature works for
CPU shared libraries but is not working for GPU code objects loaded by the
AMD ROCm runtime.

This commit implements stop-on-solib-events support for GPU code objects,
making GPU code object load/unload events behave consistently with CPU
shared library events.

The root cause is in amd_dbgapi_target_breakpoint::check_status(), which
unconditionally sets bs->stop = 0 and bs->print_it = print_it_noop,
regardless of the stop_on_solib_events setting. This is in contrast to
internal_breakpoint::check_status() for CPU shared libraries, which
respects the setting.

The fix includes:

1. Generalize print_solib_event() function to eliminate code duplication
   between CPU and GPU event printing. The function now accepts parameters
   for event description, field names, and plural forms. This reduces
   ~50 lines of duplicated code.

2. Add code_object_list_updated flag to amd_dbgapi_inferior_info to track
   when AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED events occur during
   process_event_queue().

3. Modify check_status() to check this flag after processing events and
   update bs->stop, bs->print, and bs->print_it based on stop_on_solib_events.

4. Implement print_it() override to display "Stopped due to GPU code object
   event" to distinguish GPU events from CPU shared library events.

This makes GPU code object load events behave consistently with CPU shared
library events. A test is included in gdb.rocm/solib-event.exp.

Thanks for the review Simon. Addressed your comments, here's the v2.

Regards Bratislav
---
 gdb/NEWS                                      |   6 +
 gdb/amd-dbgapi-target.c                       |  56 ++++++++-
 gdb/breakpoint.c                              |  31 +++--
 gdb/breakpoint.h                              |   6 +-
 gdb/doc/gdb.texinfo                           |  13 +-
 gdb/infrun.c                                  |   5 +-
 gdb/testsuite/gdb.rocm/solib-event-kernel.cpp |  27 +++++
 gdb/testsuite/gdb.rocm/solib-event.cpp        | 105 ++++++++++++++++
 gdb/testsuite/gdb.rocm/solib-event.exp        | 112 ++++++++++++++++++
 9 files changed, 340 insertions(+), 21 deletions(-)
 create mode 100644 gdb/testsuite/gdb.rocm/solib-event-kernel.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/solib-event.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/solib-event.exp
  

Comments

Eli Zaretskii Oct. 1, 2026, 10:54 a.m. UTC | #1
> From: Bratislav Filipovic <bfilipov@amd.com>
> CC: <simark@simark.ca>, <Lancelot.Six@amd.com>, <TankutBaris.Aktemur@amd.com>, 
>  <luis.machado.foss@gmail.com>, <pedro@palves.net>, Bratislav Filipovic
>  <bfilipov@amd.com>
> Date: Thu, 1 Oct 2026 12:35:05 +0200
> 
> GDB's "set stop-on-solib-events 1" setting allows users to stop execution
> when shared libraries are loaded or unloaded, enabling inspection and
> breakpoint placement before library code executes. This feature works for
> CPU shared libraries but is not working for GPU code objects loaded by the
> AMD ROCm runtime.
> 
> This commit implements stop-on-solib-events support for GPU code objects,
> making GPU code object load/unload events behave consistently with CPU
> shared library events.
> 
> The root cause is in amd_dbgapi_target_breakpoint::check_status(), which
> unconditionally sets bs->stop = 0 and bs->print_it = print_it_noop,
> regardless of the stop_on_solib_events setting. This is in contrast to
> internal_breakpoint::check_status() for CPU shared libraries, which
> respects the setting.
> 
> The fix includes:
> 
> 1. Generalize print_solib_event() function to eliminate code duplication
>    between CPU and GPU event printing. The function now accepts parameters
>    for event description, field names, and plural forms. This reduces
>    ~50 lines of duplicated code.
> 
> 2. Add code_object_list_updated flag to amd_dbgapi_inferior_info to track
>    when AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED events occur during
>    process_event_queue().
> 
> 3. Modify check_status() to check this flag after processing events and
>    update bs->stop, bs->print, and bs->print_it based on stop_on_solib_events.
> 
> 4. Implement print_it() override to display "Stopped due to GPU code object
>    event" to distinguish GPU events from CPU shared library events.
> 
> This makes GPU code object load events behave consistently with CPU shared
> library events. A test is included in gdb.rocm/solib-event.exp.
> 
> Thanks for the review Simon. Addressed your comments, here's the v2.
> 
> Regards Bratislav
> ---
>  gdb/NEWS                                      |   6 +
>  gdb/amd-dbgapi-target.c                       |  56 ++++++++-
>  gdb/breakpoint.c                              |  31 +++--
>  gdb/breakpoint.h                              |   6 +-
>  gdb/doc/gdb.texinfo                           |  13 +-
>  gdb/infrun.c                                  |   5 +-
>  gdb/testsuite/gdb.rocm/solib-event-kernel.cpp |  27 +++++
>  gdb/testsuite/gdb.rocm/solib-event.cpp        | 105 ++++++++++++++++
>  gdb/testsuite/gdb.rocm/solib-event.exp        | 112 ++++++++++++++++++
>  9 files changed, 340 insertions(+), 21 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.rocm/solib-event-kernel.cpp
>  create mode 100644 gdb/testsuite/gdb.rocm/solib-event.cpp
>  create mode 100644 gdb/testsuite/gdb.rocm/solib-event.exp

Thanks, the documentation parts of this patch are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Simon Marchi Oct. 1, 2026, 7:26 p.m. UTC | #2
Hi Bratislav,

Some advice for future submissions:

 - Since this is v2, pass `-v2` to git-format-patch or git-send-email so
   that it inserts "v2" in the subject.

 - Set `format.useAutoBase` in your git config to make git record the
   base commit on which your patch is based.  This makes it easier to
   apply the patch.

   Ref: https://git-scm.com/docs/git-config#Documentation/git-config.txt-formatuseAutoBase

On 10/1/26 6:35 AM, Bratislav Filipovic wrote:
> GDB's "set stop-on-solib-events 1" setting allows users to stop execution
> when shared libraries are loaded or unloaded, enabling inspection and
> breakpoint placement before library code executes. This feature works for
> CPU shared libraries but is not working for GPU code objects loaded by the
> AMD ROCm runtime.
> 
> This commit implements stop-on-solib-events support for GPU code objects,
> making GPU code object load/unload events behave consistently with CPU
> shared library events.
> 
> The root cause is in amd_dbgapi_target_breakpoint::check_status(), which
> unconditionally sets bs->stop = 0 and bs->print_it = print_it_noop,
> regardless of the stop_on_solib_events setting. This is in contrast to
> internal_breakpoint::check_status() for CPU shared libraries, which
> respects the setting.
> 
> The fix includes:
> 
> 1. Generalize print_solib_event() function to eliminate code duplication
>    between CPU and GPU event printing. The function now accepts parameters
>    for event description, field names, and plural forms. This reduces
>    ~50 lines of duplicated code.
> 
> 2. Add code_object_list_updated flag to amd_dbgapi_inferior_info to track
>    when AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED events occur during
>    process_event_queue().

Point #2, not true anymore.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index 77ad2d3fc22..957ba2e8563 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,12 @@
>  
>  *** Changes since GDB 18
>  
> +* The 'set stop-on-solib-events' setting now applies to GPU code objects
> +  loaded by the AMD ROCm runtime, in addition to CPU shared libraries.
> +  When enabled (non-zero value), GDB will stop execution whenever GPU code

Remove `(non-zero value)`, since stop-on-solib-events is now a bool.

The MI changes should be documented in NEWS, in an "MI changes" section
(see past releases for an example).

> @@ -665,6 +682,29 @@ amd_dbgapi_target_breakpoint::check_status (struct bpstat *bs)
>  	   pulongest (resume_breakpoint_id.handle));
>  
>    amd_dbgapi_event_processed (resume_event_id);
> +
> +  /* If a CODE_OBJECT_LIST_UPDATED event was seen during event processing,
> +     check if the user requested to stop on solib events.  This implements
> +     stop-on-solib-events for GPU code objects.  */
> +  if (code_object_list_updated && stop_on_solib_events != 0)

Remove the `!= 0`, as stop_on_solib_events is now a bool.

> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index 988737b6f96..05f55db6a7e 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -5113,7 +5113,9 @@ print_bp_stop_message (bpstat *bs)
>  /* See breakpoint.h.  */
>  
>  void
> -print_solib_event (bool is_catchpoint)
> +print_solib_event (bool is_catchpoint, const char *event_description,
> +		   const char *plural_description, const char *item_field_name,
> +		   const char *object_kind)

Instead of passing all these `const char *`, I think it would be better
to centralize all the messages here.  One reason is that when we'll have
a second GPU target, we won't want to have it repeat the same GPU code
object strings.  Also, the strings for shared library are "hidden" as
default parameters to this function, in the header, which makes it hard
to follow.  Finally, having "decomposed" messages like this is not good
for translation, it's better to have full sentences.

I would suggest something like this.  In the header file:

---

/* Strings used by print_solib_event to describe a kind of loadable
   object.  */

struct print_solib_event_strings
{
  /* Message printed when objects were added or removed.  */
  const char *stopped_msg;

  /* Message printed when no objects were added or removed.  */
  const char *stopped_no_change_msg;

  /* MI field name for each item of the "added" and "removed" lists.  */
  const char *item_field_name;

  /* Value of the MI "object-kind" field.  */
  const char *object_kind;
};

/* Strings for shared libraries.  */

extern const print_solib_event_strings
  print_solib_event_strings_shared_library;

/* Strings for GPU code objects.  */

extern const print_solib_event_strings
  print_solib_event_strings_gpu_code_object;

/* A helper function that prints a shared library stopped event.
   IS_CATCHPOINT is true if the event is due to a "catch load"
   catchpoint, false otherwise.  */

extern void print_solib_event (bool is_catchpoint,
			       const print_solib_event_strings &strings);

---

In the source file:

---

/* See breakpoint.h.  */

const print_solib_event_strings print_solib_event_strings_shared_library =
{
  N_("Stopped due to shared library event:\n"),
  N_("Stopped due to shared library event (no libraries added or "
     "removed)\n"),
  "library",
  "shared-library"
};

/* See breakpoint.h.  */

const print_solib_event_strings print_solib_event_strings_gpu_code_object =
{
  N_("Stopped due to GPU code object event:\n"),
  N_("Stopped due to GPU code object event (no GPU code objects added or "
     "removed)\n"),
  "code-object",
  "gpu-code-object"
};

/* See breakpoint.h.  */

void
print_solib_event (bool is_catchpoint,
		   const print_solib_event_strings &strings)

---

And then in print_solib_event_strings you get the strings from the
`strings` parameter.  For the translated strings (those marked with N_),
pass them through the _ macro:

  if (!is_catchpoint)
    current_uiout->text (any_added || any_deleted
			 ? _(strings.stopped_msg)
			 : _(strings.stopped_no_change_msg));

And then in amd-dbgapi-target.c, the print_solib_event call would look
like:

  print_solib_event (false, print_solib_event_strings_gpu_code_object);

Same for the other calls, but they would pass the other object.

Just a note regarding the MI output, I think it's fine to add the
"object-kind" value so that a frontend could adjust its display if it
wants to say "GPU code objects" instead of "shared libraries".  However,
I would leave the existing "library" entry as-is.  The reason being that
frontends that do not know about this new GPU code object thing would
expect to see a "library" entry, so it would be a backwards incompatible
change.  Also, if you leave it as "library", existing frontends can work
just fine with GPU code objects (they'll present them as libraries),
since there's nothing fundamentally different between shared libraries
and GPU code objects in GDB, other than the name.

So I'd remove the "item_field_name" from the struct above.

Also, I would always emit the object-kind key, not just for GPU objects,
it's clearer that way.  It's not a breaking change to add an entry to a
dict (or whatever MI calls them).  Some tests will need to be updated
though, so adding this entry could perhaps be done as a preparatory
patch.

> diff --git a/gdb/testsuite/gdb.rocm/solib-event.exp b/gdb/testsuite/gdb.rocm/solib-event.exp
> new file mode 100644
> index 00000000000..f91b8d41995
> --- /dev/null
> +++ b/gdb/testsuite/gdb.rocm/solib-event.exp
> @@ -0,0 +1,112 @@
> +# Copyright 2026 Free Software Foundation, Inc.
> +# Copyright 2026 Advanced Micro Devices, Inc.

Remove AMD copyright.

> +
> +# 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 that "set stop-on-solib-events 1" works for GPU code objects.
> +# Tests both file:// events (hipModuleLoad) and memory:// events
> +# (hipModuleLoadData), as well as unload events (hipModuleUnload).
> +# Also tests that the default setting (0) does not stop execution.
> +
> +load_lib rocm.exp
> +
> +require allow_hip_tests
> +
> +standard_testfile .cpp
> +
> +# Build device module (.co file).
> +set kernel_srcfile ${testfile}-kernel.cpp
> +set hipmodule_path [standard_output_file ${testfile}.co]
> +
> +if {[gdb_compile $srcdir/$subdir/$kernel_srcfile \
> +     $hipmodule_path object \
> +     {debug hip additional_flags=--cuda-device-only}] != ""} {
> +    return
> +}

Since you have a "Build host executable" comment below, I would add a
"Build GPU code object" above this.

> +proc test_enabled {} {
> +    with_rocm_gpu_lock {
> +	clean_restart $::testfile
> +
> +	if {![runto_main -inferior-args $::hipmodule_path]} {
> +	    return
> +	}
> +
> +	gdb_breakpoint [gdb_get_line_number "Enable SOLIB events here"] -temporary
> +	gdb_continue_to_breakpoint "at enable solib-event"
> +
> +	gdb_test_no_output "set stop-on-solib-events 1"

Since stop-on-solib-events is now a bool, I would suggest using on/off
instead of 1/0 throughout.

It would be nice to have a test for the MI change.

> +
> +	with_test_prefix "file://" {
> +	    gdb_breakpoint "test_file_load" -temporary
> +	    gdb_continue_to_breakpoint "at test_file_load"
> +
> +	    # Test 1: file:// load event (hipModuleLoad).
> +	    gdb_test "continue" \
> +		"Stopped due to GPU code object event.*Inferior loaded file://\[^\r\n\]+" \
> +		"load"
> +
> +	    # Test 2: file:// unload event (hipModuleUnload).
> +	    gdb_test "continue" \
> +		"Stopped due to GPU code object event.*Inferior unloaded file://\[^\r\n\]+" \
> +		"unload"
> +	}
> +
> +	with_test_prefix "memory://" {
> +	    gdb_breakpoint "test_memory_load" -temporary
> +	    gdb_continue_to_breakpoint "at test_memory_load"
> +
> +	    # Test 3: memory:// load event (hipModuleLoadData).
> +	    gdb_test "continue" \
> +		"Stopped due to GPU code object event.*Inferior loaded memory://\[^\r\n\]+" \
> +		"load"
> +
> +	    # Test 4: memory:// unload event (hipModuleUnload).
> +	    gdb_test "continue" \
> +		"Stopped due to GPU code object event.*Inferior unloaded memory://\[^\r\n\]+" \
> +		"unload"
> +	}
> +    }

I don't know if we need a test for that, but how does it behave if we
have some code objects loaded and we let the host program terminate?
Does it fire some code object updated events?

Simon
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 77ad2d3fc22..957ba2e8563 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,12 @@ 
 
 *** Changes since GDB 18
 
+* The 'set stop-on-solib-events' setting now applies to GPU code objects
+  loaded by the AMD ROCm runtime, in addition to CPU shared libraries.
+  When enabled (non-zero value), GDB will stop execution whenever GPU code
+  objects are loaded or unloaded, allowing inspection and breakpoint
+  placement.
+
 * GDB now distinguishes between the GNU (MinGW) and MSVC Windows ABIs.
 
   The "set osabi" command accepts two new values, "Windows-GNU" and
diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
index 93207a8fdd2..9b937bc21f9 100644
--- a/gdb/amd-dbgapi-target.c
+++ b/gdb/amd-dbgapi-target.c
@@ -30,11 +30,13 @@ 
 #include "gdbsupport/unordered_map.h"
 #include "inf-loop.h"
 #include "inferior.h"
+#include "infrun.h"
 #include "objfiles.h"
 #include "observable.h"
 #include "registry.h"
 #include "solib.h"
 #include "target.h"
+#include "ui-out.h"
 
 #include <map>
 
@@ -104,6 +106,10 @@  amd_dbgapi_lib_debug_module ()
     scoped_debug_start_end (debug_amd_dbgapi, amd_dbgapi_debug_module (), \
 			    fmt, ##__VA_ARGS__)
 
+/* MI field value for GPU code object kind.  */
+
+static const char *gpu_code_object_kind = "gpu-code-object";
+
 /* inferior_created observer token.  */
 
 static gdb::observers::token amd_dbgapi_target_inferior_created_observer_token;
@@ -275,7 +281,17 @@  struct amd_dbgapi_inferior_info
     wave_info_map;
 };
 
-static amd_dbgapi_event_id_t process_event_queue
+/* Return value for process_event_queue.  */
+struct process_event_queue_result
+{
+  /* The event ID, or AMD_DBGAPI_EVENT_NONE if no event was found.  */
+  amd_dbgapi_event_id_t event_id;
+
+  /* True if a CODE_OBJECT_LIST_UPDATED event was seen during processing.  */
+  bool code_object_list_updated;
+};
+
+static process_event_queue_result process_event_queue
   (amd_dbgapi_inferior_info &info,
    amd_dbgapi_event_kind_t until_event_kind = AMD_DBGAPI_EVENT_KIND_NONE);
 
@@ -587,6 +603,7 @@  struct amd_dbgapi_target_breakpoint : public code_breakpoint
 
   void re_set (program_space *) override;
   void check_status (struct bpstat *bs) override;
+  enum print_stop_action print_it (const bpstat *bs) const override;
 };
 
 void
@@ -639,7 +656,7 @@  amd_dbgapi_target_breakpoint::check_status (struct bpstat *bs)
 
   /* If the action is AMD_DBGAPI_BREAKPOINT_ACTION_HALT, we need to wait until
      a breakpoint resume event for this breakpoint_id is seen.  */
-  amd_dbgapi_event_id_t resume_event_id
+  auto [resume_event_id, code_object_list_updated]
     = process_event_queue (info, AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME);
 
   /* We should always get a breakpoint_resume event after processing all
@@ -665,6 +682,29 @@  amd_dbgapi_target_breakpoint::check_status (struct bpstat *bs)
 	   pulongest (resume_breakpoint_id.handle));
 
   amd_dbgapi_event_processed (resume_event_id);
+
+  /* If a CODE_OBJECT_LIST_UPDATED event was seen during event processing,
+     check if the user requested to stop on solib events.  This implements
+     stop-on-solib-events for GPU code objects.  */
+  if (code_object_list_updated && stop_on_solib_events != 0)
+    {
+      bs->stop = true;
+      bs->print = true;
+      /* Allow print_it () to print the GPU code object event message.  */
+      bs->print_it = print_it_normal;
+    }
+}
+
+enum print_stop_action
+amd_dbgapi_target_breakpoint::print_it (const bpstat *bs) const
+{
+  /* We only reach here when check_status set bs->print_it to print_it_normal,
+     which happens only for GPU code object events when stop_on_solib_events
+     is enabled.  */
+  print_solib_event (false, "GPU code object", "GPU code objects",
+		     "code-object", gpu_code_object_kind);
+
+  return PRINT_NOTHING;
 }
 
 bool
@@ -1460,7 +1500,8 @@  add_gpu_thread (inferior *inf, ptid_t wave_ptid)
 static void
 process_one_event (amd_dbgapi_inferior_info &info,
 		   amd_dbgapi_event_id_t event_id,
-		   amd_dbgapi_event_kind_t event_kind)
+		   amd_dbgapi_event_kind_t event_kind,
+		   bool &code_object_list_updated)
 {
   /* Automatically mark this event processed when going out of scope.  */
   scoped_amd_dbgapi_event_processed mark_event_processed (event_id);
@@ -1565,6 +1606,7 @@  process_one_event (amd_dbgapi_inferior_info &info,
 	 inferior is the inferior that hit the breakpoint, which should still be
 	 the case now.  */
       gdb_assert (info.inf == current_inferior ());
+      code_object_list_updated = true;
       handle_solib_event ();
       break;
 
@@ -1639,7 +1681,7 @@  event_kind_str (amd_dbgapi_event_kind_t kind)
    Wave stop events that are not returned are queued into their inferior's
    amd_dbgapi_inferior_info pending wave events. */
 
-static amd_dbgapi_event_id_t
+static process_event_queue_result
 process_event_queue (amd_dbgapi_inferior_info &info,
 		     amd_dbgapi_event_kind_t until_event_kind)
 {
@@ -1647,6 +1689,8 @@  process_event_queue (amd_dbgapi_inferior_info &info,
      performance, make sure it is not required.  */
   gdb_assert (!info.forward_progress_required);
 
+  bool code_object_list_updated = false;
+
   while (true)
     {
       amd_dbgapi_event_id_t event_id;
@@ -1667,9 +1711,9 @@  process_event_queue (amd_dbgapi_inferior_info &info,
 				 event_kind_str (event_kind));
 
       if (event_id == AMD_DBGAPI_EVENT_NONE || event_kind == until_event_kind)
-	return event_id;
+	return {event_id, code_object_list_updated};
 
-      process_one_event (info, event_id, event_kind);
+      process_one_event (info, event_id, event_kind, code_object_list_updated);
     }
 }
 
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 988737b6f96..05f55db6a7e 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -5113,7 +5113,9 @@  print_bp_stop_message (bpstat *bs)
 /* See breakpoint.h.  */
 
 void
-print_solib_event (bool is_catchpoint)
+print_solib_event (bool is_catchpoint, const char *event_description,
+		   const char *plural_description, const char *item_field_name,
+		   const char *object_kind)
 {
   bool any_deleted = !current_program_space->deleted_solibs.empty ();
   bool any_added = !current_program_space->added_solibs.empty ();
@@ -5121,15 +5123,28 @@  print_solib_event (bool is_catchpoint)
   if (!is_catchpoint)
     {
       if (any_added || any_deleted)
-	current_uiout->text (_("Stopped due to shared library event:\n"));
+	{
+	  std::string msg = string_printf (_("Stopped due to %s event:\n"),
+					   event_description);
+	  current_uiout->text (msg.c_str ());
+	}
       else
-	current_uiout->text (_("Stopped due to shared library event (no "
-			       "libraries added or removed)\n"));
+	{
+	  std::string msg = string_printf (_("Stopped due to %s event (no "
+					     "%s added or removed)\n"),
+					   event_description,
+					   plural_description);
+	  current_uiout->text (msg.c_str ());
+	}
     }
 
   if (current_uiout->is_mi_like_p ())
-    current_uiout->field_string ("reason",
-				 async_reason_lookup (EXEC_ASYNC_SOLIB_EVENT));
+    {
+      current_uiout->field_string
+	("reason", async_reason_lookup (EXEC_ASYNC_SOLIB_EVENT));
+      if (object_kind != nullptr)
+	current_uiout->field_string ("object-kind", object_kind);
+    }
 
   if (any_deleted)
     {
@@ -5141,7 +5156,7 @@  print_solib_event (bool is_catchpoint)
 
 	  if (ix > 0)
 	    current_uiout->text ("    ");
-	  current_uiout->field_string ("library", name);
+	  current_uiout->field_string (item_field_name, name);
 	  current_uiout->text ("\n");
 	}
     }
@@ -5156,7 +5171,7 @@  print_solib_event (bool is_catchpoint)
 	  if (!first)
 	    current_uiout->text ("    ");
 	  first = false;
-	  current_uiout->field_string ("library", iter->name);
+	  current_uiout->field_string (item_field_name, iter->name);
 	  current_uiout->text ("\n");
 	}
     }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 75de448b16e..d7078a0e198 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -2088,7 +2088,11 @@  extern void catch_exception_event (enum exception_event_kind ex_event,
    IS_CATCHPOINT is true if the event is due to a "catch load"
    catchpoint, false otherwise.  */
 
-extern void print_solib_event (bool is_catchpoint);
+extern void print_solib_event (bool is_catchpoint,
+			       const char *event_description = "shared library",
+			       const char *plural_description = "libraries",
+			       const char *item_field_name = "library",
+			       const char *object_kind = nullptr);
 
 /* Print a message describing any user-breakpoints set at PC.  This
    concerns with logical breakpoints, so we match program spaces, not
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a24f67cb8de..fd67204a47f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22513,14 +22513,15 @@  The surrounding square brackets are optional.
 @item set stop-on-solib-events
 @kindex set stop-on-solib-events
 This command controls whether @value{GDBN} should give you control
-when the dynamic linker notifies it about some shared library event.
-The most common event of interest is loading or unloading of a new
-shared library.
+when the dynamic linker notifies it about some shared library event,
+or when GPU code objects are loaded or unloaded.
+The most common events of interest are loading or unloading of a new
+shared library or code object.
 
 @item show stop-on-solib-events
 @kindex show stop-on-solib-events
 Show whether @value{GDBN} stops and gives you control when shared
-library events happen.
+library events or GPU code object events happen.
 @end table
 
 Shared libraries are also supported in many cross or remote debugging
@@ -32638,6 +32639,10 @@  The inferior has stopped due to a library being loaded or unloaded.
 This can happen when @code{stop-on-solib-events} (@pxref{Files}) is
 set or when a @code{catch load} or @code{catch unload} catchpoint is
 in use (@pxref{Set Catchpoints}).
+
+This stop reason may include an optional @code{object-kind} field
+to distinguish different types of loadable objects.  Currently defined
+values are @code{gpu-code-object} for GPU code objects.
 @item fork
 The inferior has forked.  This is reported when @code{catch fork}
 (@pxref{Set Catchpoints}) has been used.
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 92b21017b03..5f63a85e8d8 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -10849,8 +10849,9 @@  leave it stopped or free to run as needed."),
 Set stopping for shared library events."), _("\
 Show stopping for shared library events."), _("\
 If nonzero, gdb will give control to the user when the dynamic linker\n\
-notifies gdb of shared library events.  The most common event of interest\n\
-to the user would be loading/unloading of a new library."),
+notifies gdb of shared library events, or when GPU code objects are loaded\n\
+or unloaded.  The most common events of interest to the user would be\n\
+loading/unloading of a new library or code object."),
 			    set_stop_on_solib_events,
 			    show_stop_on_solib_events,
 			    &setlist, &showlist);
diff --git a/gdb/testsuite/gdb.rocm/solib-event-kernel.cpp b/gdb/testsuite/gdb.rocm/solib-event-kernel.cpp
new file mode 100644
index 00000000000..b2c47e26879
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/solib-event-kernel.cpp
@@ -0,0 +1,27 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 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/>.  */
+
+/* Device kernel for solib-event test.
+   Compiled with --cuda-device-only to produce .co file.  */
+
+#include <hip/hip_runtime.h>
+
+extern "C" __global__ void
+test_kernel ()
+{
+  asm volatile ("s_nop 1");
+}
diff --git a/gdb/testsuite/gdb.rocm/solib-event.cpp b/gdb/testsuite/gdb.rocm/solib-event.cpp
new file mode 100644
index 00000000000..823ab5629e1
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/solib-event.cpp
@@ -0,0 +1,105 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 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/>.  */
+
+#include "rocm-test-utils.h"
+#include <hip/hip_runtime.h>
+#include <fstream>
+#include <vector>
+
+/* Test file:// events (hipModuleLoad).  */
+static void
+test_file_load (const char *module_path)
+{
+  hipModule_t module;
+  CHECK (hipModuleLoad (&module, module_path));
+
+  hipFunction_t function;
+  CHECK (hipModuleGetFunction (&function, module, "test_kernel"));
+
+  CHECK (hipModuleLaunchKernel (function, 1, 1, 1, 1, 1, 1,
+				0, nullptr, nullptr, nullptr));
+
+  CHECK (hipDeviceSynchronize ());
+  CHECK (hipModuleUnload (module));
+}
+
+/* Test memory:// events (hipModuleLoadData).  */
+static void
+test_memory_load (const char *module_path)
+{
+  /* Read module file into memory buffer.  */
+  std::ifstream mod (module_path, std::ios::binary | std::ios::ate);
+  if (!mod.is_open ())
+    {
+      fprintf (stderr, "Failed to open module file\n");
+      exit (EXIT_FAILURE);
+    }
+
+  size_t module_size = mod.tellg ();
+  mod.seekg (0, std::ios::beg);
+  std::vector<unsigned char> module_buffer (module_size);
+
+  if (!mod.read (reinterpret_cast<char *> (module_buffer.data ()),
+		 module_size))
+    {
+      fprintf (stderr, "Failed to read module into memory\n");
+      exit (EXIT_FAILURE);
+    }
+  mod.close ();
+
+  /* Load from memory buffer.  */
+  hipModule_t module;
+  CHECK (hipModuleLoadData (&module, module_buffer.data ()));
+
+  hipFunction_t function;
+  CHECK (hipModuleGetFunction (&function, module, "test_kernel"));
+
+  CHECK (hipModuleLaunchKernel (function, 1, 1, 1, 1, 1, 1,
+				0, nullptr, nullptr, nullptr));
+
+  CHECK (hipDeviceSynchronize ());
+  CHECK (hipModuleUnload (module));
+}
+
+__global__ void
+warm_up ()
+{
+}
+
+int
+main (int argc, char **argv)
+{
+  if (argc != 2)
+    {
+      fprintf (stderr, "Usage: %s <module_path>\n", argv[0]);
+      return EXIT_FAILURE;
+    }
+
+  const char *module_path = argv[1];
+
+  /* Submit an empty kernel to force the runtime to do any necessary
+     setup which might include loading internal code objects.  */
+  warm_up<<<1, 1>>>();
+  CHECK (hipDeviceSynchronize ());
+
+  /* Enable SOLIB events here.  */
+
+  test_file_load (module_path);
+  test_memory_load (module_path);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.rocm/solib-event.exp b/gdb/testsuite/gdb.rocm/solib-event.exp
new file mode 100644
index 00000000000..f91b8d41995
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/solib-event.exp
@@ -0,0 +1,112 @@ 
+# Copyright 2026 Free Software Foundation, Inc.
+# Copyright 2026 Advanced Micro Devices, 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 that "set stop-on-solib-events 1" works for GPU code objects.
+# Tests both file:// events (hipModuleLoad) and memory:// events
+# (hipModuleLoadData), as well as unload events (hipModuleUnload).
+# Also tests that the default setting (0) does not stop execution.
+
+load_lib rocm.exp
+
+require allow_hip_tests
+
+standard_testfile .cpp
+
+# Build device module (.co file).
+set kernel_srcfile ${testfile}-kernel.cpp
+set hipmodule_path [standard_output_file ${testfile}.co]
+
+if {[gdb_compile $srcdir/$subdir/$kernel_srcfile \
+     $hipmodule_path object \
+     {debug hip additional_flags=--cuda-device-only}] != ""} {
+    return
+}
+
+# Build host executable.
+if {[build_executable "failed to prepare" $testfile $srcfile \
+	 {debug hip}] == -1} {
+    return
+}
+
+proc test_disabled {} {
+    with_rocm_gpu_lock {
+	clean_restart $::testfile
+
+	if {![runto_main -inferior-args $::hipmodule_path]} {
+	    return
+	}
+
+	# Set breakpoint at program exit and verify execution continues
+	# through all code object load/unload events without stopping.
+	gdb_breakpoint [gdb_get_line_number "return 0;"]
+	gdb_test "continue" \
+	    "Breakpoint .* main .*return 0.*" \
+	    "no stops for code object events when disabled"
+    }
+}
+
+proc test_enabled {} {
+    with_rocm_gpu_lock {
+	clean_restart $::testfile
+
+	if {![runto_main -inferior-args $::hipmodule_path]} {
+	    return
+	}
+
+	gdb_breakpoint [gdb_get_line_number "Enable SOLIB events here"] -temporary
+	gdb_continue_to_breakpoint "at enable solib-event"
+
+	gdb_test_no_output "set stop-on-solib-events 1"
+
+	with_test_prefix "file://" {
+	    gdb_breakpoint "test_file_load" -temporary
+	    gdb_continue_to_breakpoint "at test_file_load"
+
+	    # Test 1: file:// load event (hipModuleLoad).
+	    gdb_test "continue" \
+		"Stopped due to GPU code object event.*Inferior loaded file://\[^\r\n\]+" \
+		"load"
+
+	    # Test 2: file:// unload event (hipModuleUnload).
+	    gdb_test "continue" \
+		"Stopped due to GPU code object event.*Inferior unloaded file://\[^\r\n\]+" \
+		"unload"
+	}
+
+	with_test_prefix "memory://" {
+	    gdb_breakpoint "test_memory_load" -temporary
+	    gdb_continue_to_breakpoint "at test_memory_load"
+
+	    # Test 3: memory:// load event (hipModuleLoadData).
+	    gdb_test "continue" \
+		"Stopped due to GPU code object event.*Inferior loaded memory://\[^\r\n\]+" \
+		"load"
+
+	    # Test 4: memory:// unload event (hipModuleUnload).
+	    gdb_test "continue" \
+		"Stopped due to GPU code object event.*Inferior unloaded memory://\[^\r\n\]+" \
+		"unload"
+	}
+    }
+}
+
+with_test_prefix "stop-on-solib-events=0" {
+    test_disabled
+}
+
+with_test_prefix "stop-on-solib-events=1" {
+    test_enabled
+}