@@ -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',
@@ -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
@@ -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)
@@ -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. */
@@ -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);
@@ -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"
+ }
+}
@@ -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")