From: Andrew Burgess Date: Wed, 16 Jul 2025 14:31:28 +0000 (+0100) Subject: gdb: clear proceed status before starting a new inferior X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=765ce064774d4070ea4452227516cecfb4377b97;p=thirdparty%2Fbinutils-gdb.git gdb: clear proceed status before starting a new inferior 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 --- diff --git a/gdb/infcmd.c b/gdb/infcmd.c index d20b6464eeb..bf68a95fa01 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -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 index 00000000000..9344c800ae2 --- /dev/null +++ b/gdb/testsuite/gdb.multi/sched-multi-add-inferior.exp @@ -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 . + +# 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