diff --git a/gdb/NEWS b/gdb/NEWS index 1e25cb8cc36..e0fb006909e 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -34,6 +34,12 @@ maint show internal-warning backtrace 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 diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index b123b240980..15bf9dc3e21 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -3418,6 +3418,16 @@ This has a single attribute: 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 diff --git a/gdb/observable.c b/gdb/observable.c index 51f5edb0a1f..b020076cf26 100644 --- a/gdb/observable.c +++ b/gdb/observable.c @@ -77,6 +77,7 @@ DEFINE_OBSERVABLE (register_changed); 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 */ diff --git a/gdb/observable.h b/gdb/observable.h index 915770ff363..f20f532870f 100644 --- a/gdb/observable.h +++ b/gdb/observable.h @@ -248,9 +248,11 @@ extern observable<> source_styling_changed; /* 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 gdb_exiting; + } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index d12a2103e80..83f10989e4a 100644 --- a/gdb/python/py-all-events.def +++ b/gdb/python/py-all-events.def @@ -38,3 +38,4 @@ GDB_PY_DEFINE_EVENT(breakpoint_created) GDB_PY_DEFINE_EVENT(breakpoint_deleted) GDB_PY_DEFINE_EVENT(breakpoint_modified) GDB_PY_DEFINE_EVENT(before_prompt) +GDB_PY_DEFINE_EVENT(gdb_exiting) diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index 70df4804fc4..aeaee02e8bb 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -105,3 +105,8 @@ GDB_PY_DEFINE_EVENT_TYPE (thread, "ThreadEvent", "GDB thread event object", event_object_type); + +GDB_PY_DEFINE_EVENT_TYPE (gdb_exiting, + "GdbExitingEvent", + "GDB is about to exit", + event_object_type); diff --git a/gdb/python/python.c b/gdb/python/python.c index fcd367fe2ec..451c5bdfe0b 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -36,6 +36,7 @@ #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"; @@ -1720,6 +1721,38 @@ init__gdb_module (void) } #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 () { @@ -1871,6 +1904,8 @@ 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 (); diff --git a/gdb/testsuite/gdb.python/py-events.exp b/gdb/testsuite/gdb.python/py-events.exp index 78915241e0f..529eeeb4049 100644 --- a/gdb/testsuite/gdb.python/py-events.exp +++ b/gdb/testsuite/gdb.python/py-events.exp @@ -282,21 +282,70 @@ with_test_prefix "inferior continue exit" { } # 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 : 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 + } + } +} diff --git a/gdb/testsuite/gdb.python/py-events.py b/gdb/testsuite/gdb.python/py-events.py index b21f562bfd3..57b0324598f 100644 --- a/gdb/testsuite/gdb.python/py-events.py +++ b/gdb/testsuite/gdb.python/py-events.py @@ -132,3 +132,28 @@ class test_newobj_events(gdb.Command): 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() diff --git a/gdb/top.c b/gdb/top.c index 75692d34dfa..08cdb487df4 100644 --- a/gdb/top.c +++ b/gdb/top.c @@ -1761,8 +1761,6 @@ quit_force (int *exit_arg, int from_tty) { 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) @@ -1770,6 +1768,10 @@ quit_force (int *exit_arg, int from_tty) 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. */