]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: clear proceed status before starting a new inferior
authorAndrew Burgess <aburgess@redhat.com>
Wed, 16 Jul 2025 14:31:28 +0000 (15:31 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Fri, 12 Sep 2025 16:57:08 +0000 (17:57 +0100)
This patch fixes a bug when 'set schedule-multiple on' is in use and a
second inferior is started using the 'run' command (or 'start' or
'starti').  This bug was reported as PR gdb/28777.

The problem appears as the first inferior terminating with an
unexpected SIGTRAP.  The bug can be reproduced like this:

  gdb -ex 'set schedule-multiple on' \
      -ex 'file /tmp/spin' \
      -ex 'break main' \
      -ex 'run' \
      -ex 'add-inferior' \
      -ex 'inferior 2' \
      -ex 'file /tmp/spin' \
      -ex 'break main' \
      -ex 'run'

The final 'run' can be replaced with 'start' or 'starti'.  The output
is different in the 'starti' case, but the cause is the same.  For the
'run' and 'start' cases the final output is:

  Starting program: /tmp/spin

  Program terminated with signal SIGTRAP, Trace/breakpoint trap.
  The program no longer exists.

In the 'starti' case the output is:

  Starting program: /tmp/spin

  Thread 2.1 "spin" stopped.
  Cannot remove breakpoints because program is no longer writable.
  Further execution is probably impossible.
  0x00007ffff7fd3110 in _start () from /lib64/ld-linux-x86-64.so.2

What's happening is that GDB is failing to clear the previous proceed
status from inferior 1 before starting inferior 2.  Normally when
schedule-multiple is off, this isn't a problem as 'run' only starts
the new inferior, and the new inferior will have no previous proceed
status that needs clearing.

But when schedule-multiple is on, starting a new inferior, with 'run'
and friends, will actually start all inferiors, including those that
previous stopped at a breakpoint with a SIGTRAP signal.

By failing to clear out the proceed status for those threads, when GDB
restarts inferior 1 it arranges for the thread to receive the SIGTRAP,
which is delivered, and, as GDB isn't expecting a SIGTRAP, is allowed
to kill the process.

Fix this by calling clear_proceed_status from run_command_1.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28777

gdb/infcmd.c
gdb/testsuite/gdb.multi/sched-multi-add-inferior.exp [new file with mode: 0644]

index d20b6464eebf486bc73b0b22773955cbadf0b216..bf68a95fa01a9e9880e9d783607e9c036e5990f1 100644 (file)
@@ -495,6 +495,11 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
       thr->set_pending_waitstatus (ws);
     }
 
+  /* Still call clear_proceed_status; in schedule multiple mode the proceed
+     can resume threads from other inferiors, which might need clearing
+     prior to a proceed call.  */
+  clear_proceed_status (0);
+
   /* Start the target running.  Do not use -1 continuation as it would skip
      breakpoint right at the entry point.  */
   proceed (regcache_read_pc (get_thread_regcache (inferior_thread ())),
diff --git a/gdb/testsuite/gdb.multi/sched-multi-add-inferior.exp b/gdb/testsuite/gdb.multi/sched-multi-add-inferior.exp
new file mode 100644 (file)
index 0000000..9344c80
--- /dev/null
@@ -0,0 +1,109 @@
+# Copyright 2025 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/>.
+
+# Start multiple inferiors while schedule-multiple is on.  Make use of
+# the multi-target.exp.tcl support library for the helper procs, but
+# don't use its 'setup' proc as that only turns on schedule-multiple
+# after starting all the inferiors, and that is too late for this test.
+#
+# The specific bug that this test guards against was that, when a
+# native target starts and runs upto a breakpoint, the thread would
+# have its last signal recorded as SIGTRAP.
+#
+# When starting a gdbserver inferior (with schedule-multiple on) the
+# native target would also be resumed.
+#
+# However, GDBs 'run' command was failing to clear out the old signal
+# state from the native inferior, and so GDB would deliver a SIGTRAP
+# to that inferior, killing it.
+
+source $srcdir/$subdir/multi-target.exp.tcl
+
+if {![multi_target_prepare]} {
+    return
+}
+
+# Some breakpoint locations.
+set line1 [gdb_get_line_number "set break 1 here"]
+set line2 [gdb_get_line_number "set break 2 here"]
+
+# Start two inferiors using TARGET_TYPE_1 and TARGET_TYPE_2 (either
+# 'extended-remote' or 'native').  Due to the way that inferior 1 is
+# special (existing once GDB starts up), we just use the existing
+# helper functions to create inferiors 2 and 3 using these types, and
+# we leave inferior 1 unused.
+proc run_test { target_type_1 target_type_2 } {
+    cleanup_gdbservers
+
+    clean_restart
+
+    gdb_test_no_output "set sysroot"
+
+    # The schedule-multiple setting relies on all targets running in
+    # non-stop mode.  Force it on for remote targets, until this is
+    # the default.
+    gdb_test_no_output "maint set target-non-stop on"
+
+    # Run in all-stop mode.
+    gdb_test_no_output "set non-stop off"
+
+    # Turn on schedule-multiple before starting any inferiors.
+    gdb_test_no_output "set schedule-multiple on"
+
+    if {![add_inferior 2 $target_type_1 $::binfile]} {
+       return 0
+    }
+
+    if {![add_inferior 3 $target_type_2 $::binfile]} {
+       return 0
+    }
+
+    # Check we see all the expected threads.
+    gdb_test "info threads" \
+       [multi_line \
+            "\\s+Id\\s+Target Id\\s+Frame\\s*" \
+            "\\s+2\\.1\\s+\[^\r\n\]+" \
+            "\\s+2\\.2\\s+\[^\r\n\]+" \
+            "\\*\\s+3\\.1\\s+\[^\r\n\]+" \
+            "\\s+3\\.2\\s+\[^\r\n\]+"]
+
+    # Ensure that all inferiors can be set running again.
+    gdb_test "break ${::srcfile}:${::line1} thread 3.1"
+    gdb_test "break ${::srcfile}:${::line2} thread 2.1"
+    gdb_test "continue" \
+       [multi_line \
+            "Thread 3\\.1 \[^\r\n\]+, main \\(\[^\r\n\]+\\) at \[^\r\n\]+" \
+            "$::decimal\\s+function1 \\(\\); /\\* set break 1 here \\*/"] \
+       "continue to function1"
+
+    # Unblock thread 2.1 and continue again.  This time, thread 2.1
+    # will hit a breakpoint.
+    gdb_test "thread apply 2.1 set wait_for_gdb = 0" ".*"
+    gdb_test "continue" \
+       [multi_line \
+            "Thread 2\\.1 \[^\r\n\]+, main \\(\[^\r\n\]+\\) at \[^\r\n\]+" \
+            "$::decimal\\s+function2 \\(\\); /\\* set break 2 here \\*/"] \
+       "continue to function2"
+}
+
+set all_target_types { extended-remote native }
+
+foreach_with_prefix target_type_1 $all_target_types {
+    foreach_with_prefix target_type_2 $all_target_types {
+       run_test $target_type_1 $target_type_2
+    }
+}
+
+multi_target_cleanup