integer, the index of the new item in the history list, is
returned.
+ ** New gdb.events.gdb_exiting event. This event is called with a
+ gdb.GdbExitingEvent object which has the read-only attribute
+ 'exit_code', which contains the value of the GDB exit code. This
+ event is triggered once GDB decides it is going to exit, but
+ before GDB starts to clean up its internal state.
+
*** Changes in GDB 11
* The 'set disassembler-options' command now supports specifying options
The new thread.
@end defvar
+@item events.gdb_exiting
+This is emitted when @value{GDBN} exits. This event is not emitted if
+@value{GDBN} exits as a result of an internal error, or after an
+unexpected signal. The event is of type @code{gdb.GdbExitingEvent},
+which has a single attribute:
+
+@defvar GdbExitingEvent.exit_code
+An integer, the value of the exit code @value{GDBN} will return.
+@end defvar
+
@end table
@node Threads In Python
DEFINE_OBSERVABLE (user_selected_context_changed);
DEFINE_OBSERVABLE (source_styling_changed);
DEFINE_OBSERVABLE (current_source_symtab_and_line_changed);
+DEFINE_OBSERVABLE (gdb_exiting);
} /* namespace observers */
} /* namespace gdb */
/* The CLI's notion of the current source has changed. This differs
from user_selected_context_changed in that it is also set by the
"list" command. */
-
extern observable<> current_source_symtab_and_line_changed;
+/* Called when GDB is about to exit. */
+extern observable<int> gdb_exiting;
+
} /* namespace observers */
} /* namespace gdb */
GDB_PY_DEFINE_EVENT(breakpoint_deleted)
GDB_PY_DEFINE_EVENT(breakpoint_modified)
GDB_PY_DEFINE_EVENT(before_prompt)
+GDB_PY_DEFINE_EVENT(gdb_exiting)
"ThreadEvent",
"GDB thread event object",
event_object_type);
+
+GDB_PY_DEFINE_EVENT_TYPE (gdb_exiting,
+ "GdbExitingEvent",
+ "GDB is about to exit",
+ event_object_type);
#include "location.h"
#include "run-on-main-thread.h"
#include "gdbsupport/selftest.h"
+#include "observable.h"
/* Declared constants and enum for python stack printing. */
static const char python_excp_none[] = "none";
}
#endif
+/* Emit a gdb.GdbExitingEvent, return a negative value if there are any
+ errors, otherwise, return 0. */
+
+static int
+emit_exiting_event (int exit_code)
+{
+ gdbpy_ref<> event_obj = create_event_object (&gdb_exiting_event_object_type);
+ if (event_obj == nullptr)
+ return -1;
+
+ gdbpy_ref<> code = gdb_py_object_from_longest (exit_code);
+ if (evpy_add_attribute (event_obj.get (), "exit_code", code.get ()) < 0)
+ return -1;
+
+ return evpy_emit_event (event_obj.get (), gdb_py_events.gdb_exiting);
+}
+
+/* Callback for the gdb_exiting observable. EXIT_CODE is the value GDB
+ will exit with. */
+
+static void
+gdbpy_gdb_exiting (int exit_code)
+{
+ if (!gdb_python_initialized)
+ return;
+
+ gdbpy_enter enter_py (python_gdbarch, python_language);
+
+ if (emit_exiting_event (exit_code) < 0)
+ gdbpy_print_stack ();
+}
+
static bool
do_start_initialization ()
{
if (gdbpy_value_cst == NULL)
return false;
+ gdb::observers::gdb_exiting.attach (gdbpy_gdb_exiting, "python");
+
/* Release the GIL while gdb runs. */
PyEval_SaveThread ();
}
# Check that when GDB exits, we see gdb.ExitedEvent objects with no
-# 'exit_code' attribute.
-with_test_prefix "gdb exiting" {
+# 'exit_code' attribute, and that a gdb.GdbExitingEvent is emitted.
+with_test_prefix "gdb exiting: normal" {
+ gdb_test "test-exiting-event normal" "GDB exiting event registered\\."
+
+ set saw_exiting_event 0
set saw_inferior_exit 0
gdb_test_multiple "quit" "" {
-re "Quit anyway\\? \\(y or n\\) $" {
send_gdb "y\n"
exp_continue
}
+ -re "event type: gdb-exiting\r\nexit code: $decimal" {
+ incr saw_exiting_event
+ exp_continue
+ }
-re "event type: exit\r\nexit code: not-present\r\nexit inf: $decimal\r\nexit pid: $decimal\r\ndir ok: False\r\n" {
incr saw_inferior_exit
exp_continue
}
eof {
+ gdb_assert { $saw_exiting_event == 1 }
gdb_assert { $saw_inferior_exit == 2 }
pass $gdb_test_name
}
}
}
+
+# Check that if the GdbExitingEvent raises an exception then this
+# doesn't impact GDB's exit process.
+with_test_prefix "gdb exiting: error" {
+ clean_restart ${testfile}
+
+ if ![runto_main] then {
+ fail "cannot run to main."
+ return 0
+ }
+
+ gdb_test_no_output "source ${pyfile}" "load python file"
+ gdb_test "test-exiting-event error" "GDB exiting event registered\\."
+ gdb_test "test-events" "Event testers registered\\."
+
+ set saw_exiting_error 0
+ set saw_inferior_exit 0
+ gdb_test_multiple "quit" "" {
+ -re "Quit anyway\\? \\(y or n\\) $" {
+ send_gdb "y\n"
+ exp_continue
+ }
+ -re "event type: gdb-exiting\r\nexit code: $decimal" {
+ fail "$gdb_test_name XXXX"
+ exp_continue
+ }
+ -re "Python Exception <class 'gdb.GdbError'>: error from gdb_exiting_handler\r\n" {
+ incr saw_exiting_error
+ exp_continue
+ }
+ -re "event type: exit\r\nexit code: not-present\r\nexit inf: $decimal\r\nexit pid: $decimal\r\ndir ok: False\r\n" {
+ incr saw_inferior_exit
+ exp_continue
+ }
+ eof {
+ gdb_assert { $saw_inferior_exit == 1 }
+ gdb_assert { $saw_exiting_error == 1 }
+ pass $gdb_test_name
+ }
+ }
+}
test_newobj_events()
+
+def gdb_exiting_handler(event, throw_error):
+ assert isinstance(event, gdb.GdbExitingEvent)
+ if throw_error:
+ raise gdb.GdbError("error from gdb_exiting_handler")
+ else:
+ print("event type: gdb-exiting")
+ print("exit code: %d" % (event.exit_code))
+
+class test_exiting_event(gdb.Command):
+ """GDB Exiting event."""
+
+ def __init__(self):
+ gdb.Command.__init__(self, "test-exiting-event", gdb.COMMAND_STACK)
+
+ def invoke(self, arg, from_tty):
+ if arg == "normal":
+ gdb.events.gdb_exiting.connect(lambda e: gdb_exiting_handler(e,False))
+ elif arg == "error":
+ gdb.events.gdb_exiting.connect(lambda e: gdb_exiting_handler(e,True))
+ else:
+ raise gdb.GdbError("invalid or missing argument")
+ print("GDB exiting event registered.")
+
+test_exiting_event()
{
int exit_code = 0;
- undo_terminal_modifications_before_exit ();
-
/* An optional expression may be used to cause gdb to terminate with the
value of that expression. */
if (exit_arg)
else if (return_child_result)
exit_code = return_child_result_value;
+ gdb::observers::gdb_exiting.notify (exit_code);
+
+ undo_terminal_modifications_before_exit ();
+
/* We want to handle any quit errors and exit regardless. */
/* Get out of tfind mode, and kill or detach all inferiors. */