]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[gdb/python] Make sure python sys.exit makes gdb exit
authorTom de Vries <tdevries@suse.de>
Wed, 25 Sep 2024 17:29:57 +0000 (19:29 +0200)
committerTom de Vries <tdevries@suse.de>
Wed, 25 Sep 2024 17:29:57 +0000 (19:29 +0200)
With gdb 15.1, python sys.exit no longer makes gdb exit:
...
$ gdb -q -batch -ex "python sys.exit(2)" -ex "print 123"; echo $?
Python Exception <class 'SystemExit'>: 2
Error occurred in Python: 2
$1 = 123
0
...

This is a change in behaviour since commit a207f6b3a38 ("Rewrite "python"
command exception handling"), first available in gdb 15.1.

This patch reverts to the old behaviour by handling PyExc_SystemExit in
gdbpy_handle_exception, such what we have instead:
...
$ gdb -q -batch -ex "python sys.exit(2)" -ex "print 123"; echo $?
2
...

Tested on x86_64-linux, with python 3.6 and 3.13.

Tested-By: Guinevere Larsen <blarsen@redhat.com>
Approved-By: Tom Tromey <tom@tromey.com>
PR python/31946
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31946

gdb/python/py-utils.c
gdb/testsuite/gdb.python/sys-exit.exp [new file with mode: 0644]

index 8064cec73a76f85bb251366d541683f519b3ba11..0bc18a668b7ef9685162b1bfce5d829f399e213d 100644 (file)
@@ -390,6 +390,35 @@ gdbpy_handle_exception ()
 
   if (fetched_error.type_matches (PyExc_KeyboardInterrupt))
     throw_quit ("Quit");
+  else if (fetched_error.type_matches (PyExc_SystemExit))
+    {
+      gdbpy_ref<> value = fetched_error.value ();
+      gdbpy_ref<> code (PyObject_GetAttrString (value.get (), "code"));
+      int exit_arg;
+
+      if (code.get () == Py_None)
+       {
+         /* CODE == None: exit status is 0.  */
+         exit_arg = 0;
+       }
+      else if (code.get () != nullptr && PyLong_Check (code.get ()))
+       {
+         /* CODE == integer: exit status is aforementioned integer.  */
+         exit_arg = PyLong_AsLong (code.get ());
+       }
+      else
+       {
+         if (code.get () == nullptr)
+           gdbpy_print_stack ();
+
+         /* Otherwise: exit status is 1, print code to stderr.  */
+         if (msg != nullptr)
+           gdb_printf (gdb_stderr, "%s\n", msg.get ());
+         exit_arg = 1;
+       }
+
+      quit_force (&exit_arg, 0);
+    }
   else if (! fetched_error.type_matches (gdbpy_gdberror_exc)
           || msg == NULL || *msg == '\0')
     {
diff --git a/gdb/testsuite/gdb.python/sys-exit.exp b/gdb/testsuite/gdb.python/sys-exit.exp
new file mode 100644 (file)
index 0000000..3396b8d
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright 2024 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/>.
+
+# Check that python sys.exit makes gdb exit, with the correct exit status.
+
+require allow_python_tests
+
+# Have this code in a proc avoids clashing with runtest variable exit_status.
+
+proc do_test { n {expected_exit_status ""} {msg ""}} {
+    if { $expected_exit_status == "" } {
+       set expected_exit_status $n
+    }
+
+    with_test_prefix $n {
+       clean_restart
+
+       # Regression test for PR python/31946.
+       set seen_message 0
+       gdb_test_multiple "python sys.exit ($n)" "python sys.exit" {
+           -re "\r\n$msg\r\n" {
+               set seen_message 1
+               exp_continue
+           }
+           eof {
+               set wait_status [wait -i $::gdb_spawn_id]
+               clear_gdb_spawn_id
+
+               verbose -log "GDB process exited with wait status $wait_status"
+
+               set os_error [lindex $wait_status 2]
+               set exit_status [lindex $wait_status 3]
+
+               gdb_assert \
+                   { $os_error == 0 \
+                         && $exit_status == $expected_exit_status } \
+                   $gdb_test_name
+
+               if { $msg != "" } {
+                   gdb_assert { $seen_message }
+               }
+           }
+       }
+    }
+}
+
+# Test sys.exit (<int>).
+do_test 0
+do_test 1
+do_test 2
+
+# Test sys.exit (None).
+do_test None 0
+
+# Test sys.exit (<string>).
+do_test {"Error Message"} 1 "Error Message"
+do_test {""} 1