Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 0/3] New Python events.corefile_changed API
@ 2026-03-30 15:30 Andrew Burgess
  2026-03-30 15:30 ` [PATCH 1/3] gdb: delete some unnecessary code from core_target::detach Andrew Burgess
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Andrew Burgess @ 2026-03-30 15:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Patch #3 adds a new Python events.corefile_changed registry.

Patches #1 and #2 are some refactoring to make this possible.

Thanks,
Andrew

---

Andrew Burgess (3):
  gdb: delete some unnecessary code from core_target::detach
  gdb: refactor core_target ::close and ::detach functions
  gdb/python: new events.corefile_changed event

 gdb/NEWS                                 |   5 +
 gdb/corelow.c                            |  78 +++++++++----
 gdb/doc/python.texi                      |  11 ++
 gdb/python/py-all-events.def             |   1 +
 gdb/python/py-corefile.c                 |  36 ++++++
 gdb/python/py-event-types.def            |   5 +
 gdb/testsuite/gdb.python/py-corefile.exp | 140 ++++++++++++++++++++---
 gdb/testsuite/gdb.python/py-corefile.py  | 117 +++++++++++++++++++
 8 files changed, 351 insertions(+), 42 deletions(-)


base-commit: 82e460365ba9729e1e66b5f146c17a2ed1b4646c
-- 
2.25.4


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

* [PATCH 1/3] gdb: delete some unnecessary code from core_target::detach
  2026-03-30 15:30 [PATCH 0/3] New Python events.corefile_changed API Andrew Burgess
@ 2026-03-30 15:30 ` Andrew Burgess
  2026-03-30 15:30 ` [PATCH 2/3] gdb: refactor core_target ::close and ::detach functions Andrew Burgess
  2026-03-30 15:30 ` [PATCH 3/3] gdb/python: new events.corefile_changed event Andrew Burgess
  2 siblings, 0 replies; 5+ messages in thread
From: Andrew Burgess @ 2026-03-30 15:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This commit removes some unnecessary code from core_target::detach.

When a core_target is created the core BFD (m_core_bfd) is set, and
will never be NULL.  The m_core_bfd remains set until either
core_target::detach or core_target::close is called.

The core_target::close function is only called when the refcount of a
core_target reaches zero, the core_target::close function deletes the
core_target, so we know that after calling core_target::close no other
core_target member functions will be called (as the core_target will
have been deleted).

The core_target::detach function is called as a result of calling
target_detach, which is called as a result of either the 'detach'
command, or the 'core-file' command (without passing a file name).

As a core_target is not shareable (see
process_stratum_target::is_shareable), once a core_target is detached,
its reference count will reduce to zero, and then it will be closed
and deleted.

What this means is that there is absolutely no way that a core_target
can ever be detached twice, not that such a thing would make much
sense, but it cannot happen.

Understanding this we can know that when core_target::detach is called
m_core_bfd will never be NULL, I've added an assert for this case.

Given this assert, if we look at core_target::clear_core, which
core_target::detach calls, we can see that exit_inferior will always
be called.  If we look at exit_inferior (in inferior.c) we see that
the last two actions of that function are:

  /* Clear the register cache and the frame cache.  */
  registers_changed ();
  reinit_frame_cache ();

Which are also two of the last three actions of core_target::detach.
Clearly the calls in core_target::detach are redundant.

Just for good measure, if we look in target_detach, from where
core_target::detach will have been called, just before the function
returns we have:

  registers_changed_ptid (proc_target, save_pid_ptid);
  reinit_frame_cache ();

The registers_changed_ptid call is slightly more restrictive, only
clearing the register cache for the target being detached, but that
should be good enough -- I think exit_inferior could probably be
changed to call registers_changed_ptid for the inferior that exited,
but that's a problem for another day.

What this all tells me is that the registers_changed call and the
reinit_frame_cache call in core_target::detach are unnecessary, and
can be deleted, which is what this patch does.

There should be no user visible changes after this commit.
---
 gdb/corelow.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/gdb/corelow.c b/gdb/corelow.c
index 954607134f4..216b4e70066 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -1239,6 +1239,13 @@ core_target_open (const char *arg, int from_tty)
 void
 core_target::detach (inferior *inf, int from_tty)
 {
+  /* The core BFD is set when the core_target is created and attached to
+     the inferior.  It is only cleared during detach or close.  After
+     detaching the core target will be closed and deleted, so detach can
+     never be called twice.  What this means is that detach will never be
+     called without the core BFD being set.  */
+  gdb_assert (this->core_bfd () != nullptr);
+
   /* Get rid of the core.  Don't rely on core_target::close doing it,
      because target_detach may be called with core_target's refcount > 1,
      meaning core_target::close may not be called yet by the
@@ -1250,9 +1257,7 @@ core_target::detach (inferior *inf, int from_tty)
      implementation deletes 'this'.  */
   inf->unpush_target (this);
 
-  /* Clear the register cache and the frame cache.  */
-  registers_changed ();
-  reinit_frame_cache ();
+  /* Inform the user.  */
   maybe_say_no_core_file_now (from_tty);
 }
 
-- 
2.25.4


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

* [PATCH 2/3] gdb: refactor core_target ::close and ::detach functions
  2026-03-30 15:30 [PATCH 0/3] New Python events.corefile_changed API Andrew Burgess
  2026-03-30 15:30 ` [PATCH 1/3] gdb: delete some unnecessary code from core_target::detach Andrew Burgess
@ 2026-03-30 15:30 ` Andrew Burgess
  2026-03-30 15:30 ` [PATCH 3/3] gdb/python: new events.corefile_changed event Andrew Burgess
  2 siblings, 0 replies; 5+ messages in thread
From: Andrew Burgess @ 2026-03-30 15:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This patch refactors the core_target ::close, ::detach, and
::clear_core functions so that the m_core_bfd is not cleared before
the core_target is deleted.

My motivation for this change is the get_inferior_core_bfd function.
This function checks to see if an inferior has a core_target in its
target stack, if it does then there is an assert that the
core_target's m_core_bfd will not be NULL.

Currently, this assert is mostly correct, but during a call to
target_detach, the assert stops being true.  Calling target_detach
will call core_target::detach, which calls core_target::clear_core,
which sets m_core_bfd to NULL.  The core_target is not unpushed from
the inferior's target stack until GDB returns from ::clear_core back
to ::detach.  This means that, for a short period of time, from the
moment m_core_bfd is set to NULL in ::clear_core, until the unpush
back in ::detach, the assertion in get_inferior_core_bfd is no longer
valid.

Within this window we trigger the core_file_changed observer.  If any
of the observers call get_inferior_core_bfd then the assertion will
trigger.

Currently, no observer calls get_inferior_core_bfd, the observer just
clears some caches.  However, in the next commit I'd like to add a new
Python event API for when the core file is changed.  User code
attached to this event can call Inferior.corefile, which is
implemented by a call to get_inferior_core_bfd, and in this case the
assert will trigger.

I could just delete the assertion, but I'd prefer to not do that.  I
think by restructuring the code we can leave the assertion in place.

The first thing to understand is that a core_target is not shareable,
see process_stratum_target::is_shareable.  This means that a
core_target will only appear within a single inferior's target stack.

Next there are two ways that a core_target can be removed from an
inferior's target stack.  First is via target_detach, this is
triggered either by the 'detach' command, or by the 'core-file'
command without a core filename.  In both these cases target_detach is
called, which calls core_target::detach, this function unpushes the
core_target from the inferior's target stack.  As the core_target is
not shareable the reference count will return to zero, at which point
the core_target will be closed and deleted.

The second way that a core_target can be removed from an inferior's
target_stack is by direct replacement.  If a user loads a different
process_stratum_target, e.g. 'target remote ....' then this replaces
the core_target in the target_stack.  Doing this reduces the
core_target's reference count to zero, which causes the target to be
closed and deleted.

These two approaches differ in that the first calls
core_target::detach and then core_target::close, while the second
avoids calling ::detach, and immediately calls ::close.

My proposal is that we can defer calling the core_file_changed
observer until core_target::close.  By this point the core_target will
have been removed from the inferior's target_stack, and so the assert
in get_inferior_core_bfd will still hold.  We already call the
observer at this point for the process_stratum_target replacement
case (e.g. when a user does 'target remote ...' to replace a core file
target), this proposal would just mean that we always call the
observer at this point, rather than potentially calling it earlier in
the detach case.

This commit does this change by making a number of changes:

  * The code to reset m_core_bfd to NULL, and to trigger the
    core_file_changed observer, is removed from core_target::clear,
    this only leaves the code relating to exiting and cleaning up
    after the inferior that was created for inspecting the core file.

  * To reflect this change of focus, core_target::clear_core is
    renamed to core_target::exit_core_file_inferior.

  * In core_target::detach, nothing really needs to change other than
    calling ::exit_core_file_inferior.  I have added an assert that
    reflects the fact that ::detach cannot be called twice on the same
    core_target (after the first call the core_target will always be
    closed and deleted).

  * In core_target::close the call to ::exit_core_file_inferior needs
    to be conditional.  As discussed above, in the replacement case,
    ::close can be called without first calling ::detach.  But in the
    target_detach case, ::detach will have already been called, and as
    a result ::exit_core_file_inferior will have already been called.

  * Also in core_target::close, we now unconditionally trigger the
    core_target_changed observer.

This commit is a refactor, and there should be no user observable
changes.
---
 gdb/corelow.c | 75 ++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 51 insertions(+), 24 deletions(-)

diff --git a/gdb/corelow.c b/gdb/corelow.c
index 216b4e70066..b4438990a7a 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -286,7 +286,7 @@ class core_target final : public process_stratum_target
 private: /* per-core data */
 
   /* Get rid of the core inferior.  */
-  void clear_core ();
+  void exit_core_file_inferior ();
 
   /* The core's section table.  Note that these target sections are
      *not* mapped in the current address spaces' set of target
@@ -614,24 +614,23 @@ core_target::build_file_mappings ()
 /* An arbitrary identifier for the core inferior.  */
 #define CORELOW_PID 1
 
+/* See class declaration above.  */
+
 void
-core_target::clear_core ()
+core_target::exit_core_file_inferior ()
 {
-  if (this->core_bfd () != nullptr)
-    {
-      switch_to_no_thread ();    /* Avoid confusion from thread
-				    stuff.  */
-      exit_inferior (current_inferior ());
+  /* Opening a core file ensures that some thread, even if it's just a
+     "fake" thread, will have been selected.  */
+  gdb_assert (inferior_ptid != null_ptid);
 
-      /* Clear out solib state while the bfd is still open.  See
-	 comments in clear_solib in solib.c.  */
-      clear_solib (current_program_space);
+  /* Avoid confusion from thread stuff.  */
+  switch_to_no_thread ();
 
-      m_core_bfd.reset (nullptr);
+  exit_inferior (current_inferior ());
 
-      /* Notify that the core file has changed.  */
-      gdb::observers::core_file_changed.notify (current_inferior ());
-    }
+  /* Clear out solib state while the bfd is still open.  See
+     comments in clear_solib in solib.c.  */
+  clear_solib (current_program_space);
 }
 
 /* Close the core target.  */
@@ -639,11 +638,38 @@ core_target::clear_core ()
 void
 core_target::close ()
 {
-  clear_core ();
+  /* The core BFD is set when the core_target is created and attached to
+     the inferior.  It is never explicitly cleared, instead m_core_bfd will
+     have its reference count reduced when the core_target is deleted.  */
+  gdb_assert (this->core_bfd () != nullptr);
+
+  /* If we called ::detach before calling ::close then the inferior will
+     have already been exited.  This will happen if the user clears the
+     core file with the 'core-file' or 'detach' commands.
+
+     However, if the user just causes the core_target to be unpushed, by
+     pushing an alternative target, e.g. 'target remote ....', then we will
+     not call ::detach before calling ::close.
+
+     In the former case we don't want to exit the inferior twice; this is
+     mostly harmless except it causes two 'exited' events to be emitted in
+     the Python API, which isn't ideal.
+
+     As opening a core_target always ensures that some thread is selected,
+     then we can tell if exit_core_file_inferior has already been called by
+     checking if no thread is now selected.  */
+  if (inferior_ptid != null_ptid)
+    exit_core_file_inferior ();
 
   /* Core targets are heap-allocated (see core_target_open), so here
      we delete ourselves.  */
   delete this;
+
+  /* Notify that the core file has changed.  This is intentionally done
+     after the core_target is deleted as nothing in here depends on the
+     core_target itself, the core_target has already been removed from the
+     inferior's target stack by this point.  */
+  gdb::observers::core_file_changed.notify (current_inferior ());
 }
 
 /* Look for sections whose names start with `.reg/' so that we can
@@ -1240,17 +1266,18 @@ void
 core_target::detach (inferior *inf, int from_tty)
 {
   /* The core BFD is set when the core_target is created and attached to
-     the inferior.  It is only cleared during detach or close.  After
-     detaching the core target will be closed and deleted, so detach can
-     never be called twice.  What this means is that detach will never be
-     called without the core BFD being set.  */
+     the inferior.  It is never explicitly cleared, instead m_core_bfd will
+     have its reference count reduced when the core_target is deleted.  */
   gdb_assert (this->core_bfd () != nullptr);
 
-  /* Get rid of the core.  Don't rely on core_target::close doing it,
-     because target_detach may be called with core_target's refcount > 1,
-     meaning core_target::close may not be called yet by the
-     unpush_target call below.  */
-  clear_core ();
+  /* Similarly, the inferior and thread are created when the core_target is
+     opened, and are only exited when this function, or ::close are called.
+     As calling ::close deletes the core_target, then when this function is
+     called, the inferior will still be live.  */
+  gdb_assert (inferior_ptid != null_ptid);
+
+  /* Get rid of the core inferior.  */
+  exit_core_file_inferior ();
 
   /* Note that 'this' may be dangling after this call.  unpush_target
      closes the target if the refcount reaches 0, and our close
-- 
2.25.4


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

* [PATCH 3/3] gdb/python: new events.corefile_changed event
  2026-03-30 15:30 [PATCH 0/3] New Python events.corefile_changed API Andrew Burgess
  2026-03-30 15:30 ` [PATCH 1/3] gdb: delete some unnecessary code from core_target::detach Andrew Burgess
  2026-03-30 15:30 ` [PATCH 2/3] gdb: refactor core_target ::close and ::detach functions Andrew Burgess
@ 2026-03-30 15:30 ` Andrew Burgess
  2026-03-30 17:06   ` Eli Zaretskii
  2 siblings, 1 reply; 5+ messages in thread
From: Andrew Burgess @ 2026-03-30 15:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Add a new Python event registry, events.corefile_changed.  This event
is emitted each time the corefile within an inferior changes.

The event object has a single 'inferior' attribute which is the
gdb.Inferior object for which the core file changed.  The user can
then inspect Inferior.corefile to see details about the new core file,
or this will be None if the core file was removed from the inferior.

I've updated the existing test to cover this new event.

The new test covers both the corefile_changed event, but also monitors
the exited event.  This ties in to the work done in the previous
commit where we use whether the inferior has exited or not as a guard
for whether core_target::exit_core_file_inferior should be called.
Unloading a core file should result in a single corefile_changed event
and a single exited event.
---
 gdb/NEWS                                 |   5 +
 gdb/doc/python.texi                      |  11 ++
 gdb/python/py-all-events.def             |   1 +
 gdb/python/py-corefile.c                 |  36 ++++++
 gdb/python/py-event-types.def            |   5 +
 gdb/testsuite/gdb.python/py-corefile.exp | 140 ++++++++++++++++++++---
 gdb/testsuite/gdb.python/py-corefile.py  | 117 +++++++++++++++++++
 7 files changed, 296 insertions(+), 19 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 4cf91053c95..225ce15df16 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -238,6 +238,11 @@ qExecAndArgs
      a tuple of pairs each representing a single range.  Contiguous blocks
      have only one range.
 
+  ** New event registry gdb.events.corefile_changed, which emits a
+     CorefileChangedEvent whenever the core file associated with an
+     inferior changes.  The event has an 'inferior' attribute which is
+     the gdb.Inferior in which the core file has changed.
+
 * Guile API
 
   ** Procedures 'memory-port-read-buffer-size',
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index e1e983726e8..6c0b3310c7f 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -4147,6 +4147,17 @@ Events In Python
 filename will have changed, but the symbol filename might still hold
 its previous value.
 
+@item events.corefile_changed
+Emits @code{gdb.CorefileChangedEvent} which indicates that the core
+file associated with a @code{gdb.Inferior} has changed, either a new
+core file has been loaded, or the existing core file has been
+unloaded (@pxref{Core Files In Python}).
+
+@defvar CorefileChangedEvent.inferior
+The @code{gdb.Inferior} in which the corefile has changed
+(@pxref{Inferiors In Python}).
+@end defvar
+
 @item events.new_progspace
 This is emitted when @value{GDBN} adds a new program space
 (@pxref{Progspaces In Python,,Program Spaces In Python}).  The event
diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def
index 24724038562..3711cf29287 100644
--- a/gdb/python/py-all-events.def
+++ b/gdb/python/py-all-events.def
@@ -47,3 +47,4 @@ GDB_PY_DEFINE_EVENT(new_progspace)
 GDB_PY_DEFINE_EVENT(free_progspace)
 GDB_PY_DEFINE_EVENT(tui_enabled)
 GDB_PY_DEFINE_EVENT(selected_context)
+GDB_PY_DEFINE_EVENT(corefile_changed)
diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
index d35838c7523..4da70d631a4 100644
--- a/gdb/python/py-corefile.c
+++ b/gdb/python/py-corefile.c
@@ -23,6 +23,7 @@
 #include "inferior.h"
 #include "gdbcore.h"
 #include "gdbsupport/rsp-low.h"
+#include "py-event.h"
 
 /* A gdb.Corefile object.  */
 
@@ -320,13 +321,48 @@ cfpy_mapped_files (PyObject *self, PyObject *args)
   return obj->mapped_files;
 }
 
+/* Emit a CorefileChangedEvent event to REGISTRY.  Return 0 on success,
+   or a negative value on error.  INF is the inferior in which the core
+   file changed.  */
+
+static int
+emit_corefile_changed_event (eventregistry_object *registry, inferior *inf)
+{
+  /* If there are no listeners then we are done.  */
+  if (evregpy_no_listeners_p (gdb_py_events.corefile_changed))
+    return 0;
+
+  gdbpy_ref<> event_obj
+    = create_event_object (&corefile_changed_event_object_type);
+  if (event_obj == nullptr)
+    return -1;
+
+  gdbpy_ref<inferior_object> inf_obj = inferior_to_inferior_object (inf);
+  if (inf_obj == nullptr
+      || evpy_add_attribute (event_obj.get (), "inferior",
+			     inf_obj.get ()) < 0)
+    return -1;
+
+  return evpy_emit_event (event_obj.get (), registry);
+}
+
 /* Callback from gdb::observers::core_file_changed.  The core file in
    PSPACE has been changed.  */
 
 static void
 cfpy_corefile_changed (inferior *inf)
 {
+  /* It's safe to do this even if Python is not initialized, but there
+     should be nothing to clear in that case.  */
   cfpy_inferior_corefile_data_key.clear (inf);
+
+  if (!gdb_python_initialized)
+    return;
+
+  gdbpy_enter enter_py;
+
+  if (emit_corefile_changed_event (gdb_py_events.corefile_changed, inf) < 0)
+    gdbpy_print_stack ();
 }
 
 /* Called when a gdb.Corefile is destroyed.  */
diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def
index fe3e0978a55..7dddcc156ec 100644
--- a/gdb/python/py-event-types.def
+++ b/gdb/python/py-event-types.def
@@ -150,3 +150,8 @@ GDB_PY_DEFINE_EVENT_TYPE (selected_context,
 			  "SelectedContextEvent",
 			  "GDB user selected context event object",
 			  event_object_type);
+
+GDB_PY_DEFINE_EVENT_TYPE (corefile_changed,
+			  "CorefileChangedEvent",
+			  "GDB corefile changed event",
+			  event_object_type);
diff --git a/gdb/testsuite/gdb.python/py-corefile.exp b/gdb/testsuite/gdb.python/py-corefile.exp
index ecbff30cf7c..1b7a6458133 100644
--- a/gdb/testsuite/gdb.python/py-corefile.exp
+++ b/gdb/testsuite/gdb.python/py-corefile.exp
@@ -17,6 +17,7 @@
 # support in Python.
 
 require isnative
+require {!is_remote host}
 
 load_lib gdb-python.exp
 
@@ -28,17 +29,93 @@ if {[build_executable "build executable" $testfile $srcfile] == -1} {
     return
 }
 
+set remote_python_file \
+    [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
 set corefile [core_find $binfile]
 if {$corefile == ""} {
     unsupported "couldn't create or find corefile"
     return
 }
 
+# Helper proc to run the 'core-file' command.  Takes optional arguments:
+#
+#   -corefile FILENAME : Load FILENAME as the new core file.  If this
+#                        argument is not given then the current core
+#                        file will be unloaded.
+#
+#   -inferior NUM : The inferior in which the corefile is being changed.
+#                   This is used to match the corefile_changed events
+#                   that will be emitted.
+#
+#   -prefix STRING : A test prefix, to make test names unique.
+proc core_file_cmd { args } {
+    parse_some_args {
+	{corefile ""}
+	{inferior 1}
+	{prefix ""}
+    }
+
+    if { $prefix eq "" } {
+	if { $corefile eq "" } {
+	    set prefix "unload corefile"
+	} else {
+	    set prefix "load corefile"
+	}
+    }
+
+    with_test_prefix $prefix {
+	gdb_test "events corefile_changed check" \
+	    "^No corefile_changed event has been seen\\." \
+	    "no corefile event has been seen"
+
+	gdb_test "events exited check" \
+	    "^No exited event has been seen\\." \
+	    "no exited event has been seen"
+
+	if { $corefile eq "" } {
+	    gdb_test "core-file" "^No core file now\\." "unload current core file"
+
+	    gdb_test "events corefile_changed check" \
+		"Event 1/1, Inferior $inferior, Corefile None" \
+		"expected corefile event has been seen"
+
+	    gdb_test "events exited check" \
+		"Event 1/1, Inferior $inferior, Exit Code None" \
+		"expected exited event has been seen"
+	} else {
+	    gdb_test "core-file $corefile" ".*" \
+		"load core file"
+
+	    gdb_test "events corefile_changed check" \
+		"Event 1/1, Inferior $inferior, Corefile [string_to_regexp $corefile]" \
+		"expected corefile event has been seen"
+
+	    gdb_test "events exited check" \
+		"^No exited event has been seen\\." \
+		"no exited event was emitted"
+	}
+    }
+
+    gdb_test_no_output -nopass "events corefile_changed reset"
+    gdb_test_no_output -nopass "events exited reset"
+}
+
+# A helper proc runs clean_restart passing through ARGS, and then loads the
+# test's Python script.
+proc clean_restart_and_load_py_script { args } {
+    clean_restart {*}$args
+
+    # Load the Python script into GDB.
+    gdb_test "source $::remote_python_file" "^Success" \
+	"source python script"
+}
+
 # Create a copy of the corefile.
 set other_corefile [standard_output_file ${testfile}-other.core]
 remote_exec build "cp $corefile $other_corefile"
 
-clean_restart
+clean_restart_and_load_py_script
 
 gdb_test_no_output "python inf = gdb.selected_inferior()" \
     "capture current inferior"
@@ -46,8 +123,7 @@ gdb_test_no_output "python inf = gdb.selected_inferior()" \
 gdb_test "python print(inf.corefile)" "^None" \
     "Inferior.corefile is None before loading a core file"
 
-gdb_test "core-file $corefile" ".*" \
-    "load core file"
+core_file_cmd -corefile $corefile
 
 set file_re [string_to_regexp $corefile]
 gdb_test "python print(inf.corefile)" "^<gdb\\.Corefile inferior=1 filename='$file_re'>" \
@@ -73,7 +149,7 @@ gdb_test "python print(core1.filename)" "^$file_re" \
 gdb_test "python print(core1.is_valid())" "^True" \
     "Corefile.is_valid() is True while corefile is loaded"
 
-gdb_test "core-file" "^No core file now\\." "unload current core file"
+core_file_cmd
 
 gdb_test "python print(core1.is_valid())" "^False" \
     "Corefile.is_valid() is False after corefile is unloaded"
@@ -101,8 +177,7 @@ gdb_test "add-inferior"
 gdb_test "inferior 2"
 
 with_test_prefix "in second inferior" {
-    gdb_test "core-file $corefile" ".*" \
-	"load core file"
+    core_file_cmd -corefile $corefile -inferior 2
 
     gdb_test "python print(inf.corefile)" "^None" \
 	"first inferior still has no core file"
@@ -128,8 +203,8 @@ gdb_test "python print(core2.filename)" "^$file_re" \
     "Corefile.filename attribute works from different progspace"
 
 # Load the other corefile into the first inferior.
-gdb_test "core $other_corefile" ".*" \
-    "load other corefile into inferior 1"
+core_file_cmd -corefile $other_corefile \
+    -prefix "load other corefile into inferior 1"
 
 # Delete the second inferior.  We need to switch to the second
 # inferior and unload its corefile before we can do that.  Then,
@@ -152,7 +227,7 @@ with_test_prefix "remove second inferior" {
 	"AttributeError.*: 'gdb\\.Corefile' object has no attribute '_my_attribute'" \
 	"try to read attribute that doesn't exist"
 
-    gdb_test "core-file"
+    core_file_cmd -inferior 2
 
     gdb_test "python print(core2.filename)" \
 	[multi_line \
@@ -182,18 +257,10 @@ with_test_prefix "remove second inferior" {
 # mapped_files API.  The output from the built-in command, and the
 # Python command should be identical.
 with_test_prefix "test mapped files data" {
-    clean_restart
-
-    set remote_python_file \
-	[gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
-
-    # Load the Python script into GDB.
-    gdb_test "source $remote_python_file" "^Success" \
-	"source python script"
+    clean_restart_and_load_py_script
 
     # Load the core file.
-    gdb_test "core-file $corefile" ".*" \
-	"load core file"
+    core_file_cmd -corefile $corefile
 
     # Two files to write the output to.
     set out_1 [standard_output_file ${gdb_test_file_name}-out-1.txt]
@@ -279,3 +346,38 @@ with_test_prefix "test mapped files data" {
     gdb_test "python regions\[0\] = None" \
 	"'tuple' object does not support item assignment"
 }
+
+# Load a core file.  GDB should figure out which file is being debugged.
+# Then use 'start' to run this executable, this will replace the core file
+# target.  At least on Linux, this replacement is done without calling
+# target_detach.  This test checks that the expected core file changed and
+# inferior exited events are still seen.
+with_test_prefix "start from corefile" {
+    if { [gdb_protocol_is_native] } {
+	clean_restart_and_load_py_script
+
+	# Load the core file.
+	core_file_cmd -corefile $corefile
+
+	# Check GDB figured out the executable.
+	gdb_test "info inferiors 1" \
+	    [multi_line \
+		 "\[^\r\n\]+[string_to_regexp $binfile]\\s*" \
+		 "\[^\r\n\]+[string_to_regexp $corefile]\\s*"] \
+	    "check executable was detected correctly"
+
+	gdb_test "start" \
+	    "Temporary breakpoint $::decimal, main \\(\\).*" \
+
+	gdb_test "events corefile_changed check" \
+	    "Event 1/1, Inferior 1, Corefile None" \
+	    "expected corefile event has been seen"
+
+	gdb_test "events exited check" \
+	    "Event 1/1, Inferior 1, Exit Code None" \
+	    "expected exited event has been seen"
+
+	gdb_test_no_output -nopass "events corefile_changed reset"
+	gdb_test_no_output -nopass "events exited reset"
+    }
+}
diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py
index 43b64085117..1aaf15093ac 100644
--- a/gdb/testsuite/gdb.python/py-corefile.py
+++ b/gdb/testsuite/gdb.python/py-corefile.py
@@ -196,4 +196,121 @@ class CheckMainExec(gdb.Command):
 CheckMainExec()
 
 
+# An 'events' prefix command.
+class events_cmd(gdb.Command):
+    """Information about recent Python events."""
+
+    def __init__(self):
+        gdb.Command.__init__(self, "events", gdb.COMMAND_USER, prefix=True)
+
+
+# An 'events corefile_changed' sub-command.
+class events_corefile_changed_cmd(gdb.Command):
+    """Check recent corefile_changed events.
+
+    Requires a single argument either 'check' or 'reset'.  With
+    'check', print details of every recent corefile_changed event.
+    With 'reset' clear the list of recent corefile_changed events."""
+
+    def __init__(self):
+        gdb.Command.__init__(self, "events corefile_changed", gdb.COMMAND_USER)
+        self._events = []
+        gdb.events.corefile_changed.connect(lambda e: self._corefile_changed_handler(e))
+
+    def _corefile_changed_handler(self, event):
+        assert isinstance(event, gdb.CorefileChangedEvent)
+        inf = event.inferior
+        assert isinstance(inf, gdb.Inferior)
+
+        corefile = inf.corefile
+        if corefile is not None:
+            assert corefile.is_valid()
+            corefile = corefile.filename
+
+        obj = {"inferior": inf.num, "corefile": corefile}
+        self._events.append(obj)
+
+    def invoke(self, args, from_tty):
+        if args == "check":
+            if len(self._events) == 0:
+                print("No corefile_changed event has been seen.")
+            else:
+                total = len(self._events)
+                for idx, obj in enumerate(self._events, start=1):
+                    inf_num = obj["inferior"]
+                    corefile = obj["corefile"]
+
+                    if corefile is None:
+                        msg = "None"
+                    else:
+                        msg = corefile
+
+                    print(
+                        "Event {}/{}, Inferior {}, Corefile {}".format(
+                            idx, total, inf_num, msg
+                        )
+                    )
+        elif args == "reset":
+            self._events = []
+        else:
+            raise gdb.GdbError("Unknown command args: {}".format(args))
+
+
+# An 'events exited' sub-command.
+class events_exited_cmd(gdb.Command):
+    """Check recent exited events.
+
+    Requires a single argument either 'check' or 'reset'.  With
+    'check', print details of every recent corefile_changed event.
+    With 'reset' clear the list of recent corefile_changed events."""
+
+    def __init__(self):
+        gdb.Command.__init__(self, "events exited", gdb.COMMAND_USER)
+        self._events = []
+        gdb.events.exited.connect(lambda e: self._exited_handler(e))
+
+    def _exited_handler(self, event):
+        assert isinstance(event, gdb.ExitedEvent)
+        inf = event.inferior
+        assert isinstance(inf, gdb.Inferior)
+
+        if hasattr(event, "exit_code"):
+            assert isinstance(event.exit_code, int)
+            exit_code = event.exit_code
+        else:
+            exit_code = None
+
+        obj = {"inferior": inf.num, "exit_code": exit_code}
+        self._events.append(obj)
+
+    def invoke(self, args, from_tty):
+        if args == "check":
+            if len(self._events) == 0:
+                print("No exited event has been seen.")
+            else:
+                total = len(self._events)
+                for idx, obj in enumerate(self._events, start=1):
+                    inf_num = obj["inferior"]
+                    exit_code = obj["exit_code"]
+
+                    if exit_code is None:
+                        msg = "None"
+                    else:
+                        msg = exit_code
+
+                    print(
+                        "Event {}/{}, Inferior {}, Exit Code {}".format(
+                            idx, total, inf_num, msg
+                        )
+                    )
+        elif args == "reset":
+            self._events = []
+        else:
+            raise gdb.GdbError("Unknown command args: {}".format(args))
+
+
+events_cmd()
+events_corefile_changed_cmd()
+events_exited_cmd()
+
 print("Success")
-- 
2.25.4


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

* Re: [PATCH 3/3] gdb/python: new events.corefile_changed event
  2026-03-30 15:30 ` [PATCH 3/3] gdb/python: new events.corefile_changed event Andrew Burgess
@ 2026-03-30 17:06   ` Eli Zaretskii
  0 siblings, 0 replies; 5+ messages in thread
From: Eli Zaretskii @ 2026-03-30 17:06 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Mon, 30 Mar 2026 16:30:53 +0100
> 
> Add a new Python event registry, events.corefile_changed.  This event
> is emitted each time the corefile within an inferior changes.
> 
> The event object has a single 'inferior' attribute which is the
> gdb.Inferior object for which the core file changed.  The user can
> then inspect Inferior.corefile to see details about the new core file,
> or this will be None if the core file was removed from the inferior.
> 
> I've updated the existing test to cover this new event.
> 
> The new test covers both the corefile_changed event, but also monitors
> the exited event.  This ties in to the work done in the previous
> commit where we use whether the inferior has exited or not as a guard
> for whether core_target::exit_core_file_inferior should be called.
> Unloading a core file should result in a single corefile_changed event
> and a single exited event.
> ---
>  gdb/NEWS                                 |   5 +
>  gdb/doc/python.texi                      |  11 ++
>  gdb/python/py-all-events.def             |   1 +
>  gdb/python/py-corefile.c                 |  36 ++++++
>  gdb/python/py-event-types.def            |   5 +
>  gdb/testsuite/gdb.python/py-corefile.exp | 140 ++++++++++++++++++++---
>  gdb/testsuite/gdb.python/py-corefile.py  | 117 +++++++++++++++++++
>  7 files changed, 296 insertions(+), 19 deletions(-)

Thanks, the documentation parts are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

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

end of thread, other threads:[~2026-03-30 17:07 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-30 15:30 [PATCH 0/3] New Python events.corefile_changed API Andrew Burgess
2026-03-30 15:30 ` [PATCH 1/3] gdb: delete some unnecessary code from core_target::detach Andrew Burgess
2026-03-30 15:30 ` [PATCH 2/3] gdb: refactor core_target ::close and ::detach functions Andrew Burgess
2026-03-30 15:30 ` [PATCH 3/3] gdb/python: new events.corefile_changed event Andrew Burgess
2026-03-30 17:06   ` Eli Zaretskii

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