Skip to content

Commit 622e0ee

Browse files
committed
gdb: disable commit resumed in wait_for_inferior
This patch proposes a fix for PR gdb/33147. The bug can be reproduced like this: gdb -q -ex 'file /bin/ls' \ -ex 'run &' \ -ex 'add-inferior' \ -ex 'infer 2' \ -ex 'set sysroot' \ -ex 'target remote | gdbserver - ls' Which will trigger an assertion failure: target.c:3760: internal-error: target_stop: Assertion `!proc_target->commit_resumed_state' failed. The problem is that target_stop is being called for a target when commit_resumed_state is true, the comment on process_stratum_target::commit_resumed_state is pretty clear: To simplify the implementation of targets, the following methods are guaranteed to be called with COMMIT_RESUMED_STATE set to false: - resume - stop - wait So clearly we're breaking a precondition of target_stop. In this example there are two target, the native target (inferior 1), and the remote target (inferior 2). It is the first, the native target, for which commit_resumed_state is set incorrectly. At the point target_stop is called looks like this: bminor#11 0x00000000009a3c19 in target_stop (ptid=...) at ../../src/gdb/target.c:3760 bminor#12 target_stop (ptid=...) at ../../src/gdb/target.c:3756 bminor#13 0x00000000007042f2 in stop_all_threads (reason=<optimized out>, inf=<optimized out>) at ../../src/gdb/infrun.c:5739 bminor#14 0x0000000000711d3a in wait_for_inferior (inf=0x2b90fd0) at ../../src/gdb/infrun.c:4412 bminor#15 start_remote (from_tty=from_tty@entry=1) at ../../src/gdb/infrun.c:3829 bminor#16 0x0000000000897014 in remote_target::start_remote_1 (this=this@entry=0x2c4a520, from_tty=from_tty@entry=1, extended_p=extended_p@entry=0) at ../../src/gdb/remote.c:5350 bminor#17 0x00000000008976e7 in remote_target::start_remote (extended_p=0, from_tty=1, this=0x2c4a520) at ../../src/gdb/remote.c:5441 #18 remote_target::open_1 (name=<optimized out>, from_tty=1, extended_p=0) at ../../src/gdb/remote.c:6312 #19 0x00000000009a815f in open_target (args=0x7fffffffa93c "| gdbserver - ls", from_tty=1, command=<optimized out>) at ../../src/gdb/target.c:838 For new inferiors commit_resumed_state starts set to false, for this reason, if we only start a remote inferior, then when wait_for_inferior is called commit_resumed_state will be false, and everything will work. Further, as target_stop is only called for running threads, if, when the remote inferior is started, all other threads (in other targets) are already stopped, then GDB will never need to call target_stop for the other targets, and so GDB will not notice that commit_resumed_state for those target is set to true. In this case though, as the first (native) inferior is left running in the background while the remote inferior is created, and because GDB is running in all-stop mode (so needs to stop all threads in all targets), then GDB does call target_stop for the other targets, and so spots that commit_resumed_state is not set correctly and asserts. The fix is to add scoped_disable_commit_resumed somewhere in the call stack. Initially I planned to add the scoped_disable_commit_resumed in `wait_for_inferior`, however, this isn't good enough. This location would solve the problem as described in the bug, but when writing the test I extended the problem to also cover non-stop mode, and this runs into a second problem, the same assertion, but triggered from a different call path. For this new case the stack looks like this: #1 0x0000000000fb0e50 in target_stop (ptid=...) at ../../src/gdb/target.c:3771 #2 0x0000000000a7f0ae in stop_all_threads (reason=0x1d0ff74 "remote connect in all-stop", inf=0x0) at ../../src/gdb/infrun.c:5756 #3 0x0000000000d9c028 in remote_target::process_initial_stop_replies (this=0x3e10670, from_tty=1) at ../../src/gdb/remote.c:5017 #4 0x0000000000d9cdf0 in remote_target::start_remote_1 (this=0x3e10670, from_tty=1, extended_p=0) at ../../src/gdb/remote.c:5405 bminor#5 0x0000000000d9d0d4 in remote_target::start_remote (this=0x3e10670, from_tty=1, extended_p=0) at ../../src/gdb/remote.c:5457 bminor#6 0x0000000000d9e8ac in remote_target::open_1 (name=0x7fffffffa931 "| gdbserver - /bin/ls", from_tty=1, extended_p=0) at ../../src/gdb/remote.c:6329 bminor#7 0x0000000000d9d167 in remote_target::open (name=0x7fffffffa931 "| gdbserver - /bin/ls", from_tty=1) at ../../src/gdb/remote.c:5479 bminor#8 0x0000000000f9914d in open_target (args=0x7fffffffa931 "| gdbserver - /bin/ls", from_tty=1, command=0x35d1a40) at ../../src/gdb/target.c:838 So I'm now thinking that stop_all_threads would be the best place for the scoped_disable_commit_resumed. I did leave an assert in wait_for_inferior as, having thought about the assert some, I do still think the logic of it is true, and it doesn't hurt to leave it in place I think. However, it's not quite that simple, the test throws up yet another bug when we 'maint set target-non-stop on', but then 'set non-stop off'. This bug leaves a stopped thread marked as "(running)" in the 'info threads' output. I have a fix for this issue, but I'm leaving that for the next commit. For now I've just disabled part of the test in the problem case. I've also tagged this patch with PR gdb/27322. That bug was created before the above assert was added, but if you follow the steps to reproduce for that bug today you will hit the above assert. The actual issue described in PR gdb/27322 is fixed in the next patch. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27322 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33147
1 parent 55dbaa6 commit 622e0ee

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed

gdb/infrun.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4393,6 +4393,11 @@ wait_for_inferior (inferior *inf)
43934393
scoped_finish_thread_state finish_state
43944394
(inf->process_target (), minus_one_ptid);
43954395

4396+
/* The commit_resumed_state of INF should already be false at this point
4397+
as INF will be a newly started remote target. This might not be true
4398+
for other targets but this will be handled in stop_all_threads. */
4399+
gdb_assert (!inf->process_target ()->commit_resumed_state);
4400+
43964401
while (1)
43974402
{
43984403
execution_control_state ecs;
@@ -5699,6 +5704,8 @@ stop_all_threads (const char *reason, inferior *inf)
56995704
debug_prefixed_printf ("infrun", "stop_all_threads", "done");
57005705
};
57015706

5707+
scoped_disable_commit_resumed disable_commit_resumed ("stop all threads");
5708+
57025709
/* Request threads to stop, and then wait for the stops. Because
57035710
threads we already know about can spawn more threads while we're
57045711
trying to stop them, and we only learn about new threads when we
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* This testcase is part of GDB, the GNU debugger.
2+
3+
Copyright 2025 Free Software Foundation, Inc.
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation; either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>. */
17+
18+
#include <unistd.h>
19+
20+
int global_var = 123;
21+
22+
void
23+
breakpt (void)
24+
{
25+
/* Nothing. */
26+
}
27+
28+
int
29+
main (void)
30+
{
31+
for (int i = 0; i < 30; ++i)
32+
{
33+
sleep (1);
34+
breakpt ();
35+
}
36+
37+
return 0;
38+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# This testcase is part of GDB, the GNU debugger.
2+
#
3+
# Copyright 2025 Free Software Foundation, Inc.
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
# Set an inferior running in the background (using "run&"), then
19+
# connect to gdbserver in a second inferior. When this test was added
20+
# there were two bugs in GDB. First, just connecting to gdbserver
21+
# while an inferior was running on a different connection type
22+
# (e.g. inferior 1 was native, and inferior 2 was gdbserver) would
23+
# trigger an assertion.
24+
#
25+
# Then, once the assertion was fixed, GDB would stop the thread from
26+
# the first inferior, but would fail to update it's state in GDB core,
27+
# this would leave the thread stopped, but GDB core thinking the
28+
# thread was running. Check this is fixed by looking at the 'info
29+
# threads' output after connecting to the remote target.
30+
31+
# This tests the use of native and remote targets, and also depends on use
32+
# of the 'run' command. If we try to run it with a board that forces native
33+
# targets to become remote, then this test isn't going to work, especially
34+
# for 'remote' targets where 'run' is not supported.
35+
require {string equal [target_info gdb_protocol] ""}
36+
37+
load_lib gdbserver-support.exp
38+
39+
require allow_gdbserver_tests
40+
41+
standard_testfile
42+
43+
if { [build_executable "failed to build" $testfile $srcfile] } {
44+
return
45+
}
46+
47+
# Set non-stop mode based on NON_STOP. Start a native inferior running in
48+
# the background, then start a second, remote inferior. Based on the value
49+
# of NON_STOP we might expect the inferior thread to have been stopped.
50+
# Confirm inferior one is in the correct state, and that it can be
51+
# interrupted and/or resumed.
52+
proc run_test { target_non_stop non_stop } {
53+
clean_restart $::testfile
54+
55+
# Setup non-stop settings.
56+
gdb_test_no_output "maint set target-non-stop $target_non_stop"
57+
gdb_test_no_output "set non-stop $non_stop"
58+
59+
# Start the first inferior running in the background.
60+
gdb_test -no-prompt-anchor "run&" "Starting program: .*" "start background inferior"
61+
62+
# Add a second inferior.
63+
gdb_test "add-inferior" "Added inferior 2.*"
64+
gdb_test "inferior 2" "Switching to inferior 2.*"
65+
66+
# Setup the sysroot if possible. This will make connecting to
67+
# gdbserver quicker.
68+
if { ![is_remote host] && ![is_remote target] } {
69+
gdb_test "set sysroot"
70+
}
71+
72+
# Setup, and connect to, a remote target.
73+
set target_exec [gdbserver_download_current_prog]
74+
set res [gdbserver_start "" $target_exec]
75+
set gdbserver_protocol [lindex $res 0]
76+
set gdbserver_gdbport [lindex $res 1]
77+
set res [gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport]
78+
gdb_assert {$res == 0} "connect to remote target"
79+
80+
# FIXME: A bug in GDB means that the state of thread 1.1 will be wrong,
81+
# GDB's frontend thinks that the thread is running when really the
82+
# thread is stopped. This will be fixed in the next commit, at which
83+
# point this whole 'if' will be removed.
84+
if { $target_non_stop == "on" && $non_stop == "off" } {
85+
gdb_test "p 1 + 2" " = 3" "check GDB is still alive"
86+
return
87+
}
88+
89+
# Check the info threads output. We're checking that we see the two
90+
# threads we expect, that the correct thread (inferior two's thread)
91+
# is current, and that none of the threads are running.
92+
set state_inferior_1 ""
93+
set state_inferior_2 ""
94+
gdb_test_multiple "info threads" "" {
95+
-re "^info threads\r\n" {
96+
exp_continue
97+
}
98+
99+
-re "^\\s+Id\\s+Target Id\\s+Frame\\s*\r\n" {
100+
exp_continue
101+
}
102+
103+
-re "^\\s+1\\.1\\s+\[^\r\n\]+\\(running\\)\r\n" {
104+
set state_inferior_1 "running"
105+
exp_continue
106+
}
107+
108+
-re "^\\*\\s+2\\.1\\s+\[^\r\n\]+\\(running\\)\r\n" {
109+
set state_inferior_2 "running"
110+
exp_continue
111+
}
112+
113+
-re "^\\s+1\\.1\\s+\[^\r\n\]+\r\n" {
114+
set state_inferior_1 "stopped"
115+
exp_continue
116+
}
117+
118+
-re "^\\*\\s+2\\.1\\s+\[^\r\n\]+\r\n" {
119+
set state_inferior_2 "stopped"
120+
exp_continue
121+
}
122+
123+
-re "^$::gdb_prompt $" {
124+
if { $non_stop } {
125+
gdb_assert { $state_inferior_1 == "running" \
126+
&& $state_inferior_2 == "stopped" } \
127+
$gdb_test_name
128+
} else {
129+
gdb_assert { $state_inferior_1 == "stopped" \
130+
&& $state_inferior_2 == "stopped" } \
131+
$gdb_test_name
132+
}
133+
}
134+
}
135+
136+
# Allow inferior 2 to reach main. The confirms that inferior 2 can be
137+
# set running again.
138+
gdb_breakpoint main
139+
gdb_continue_to_breakpoint "breakpoint in main"
140+
gdb_test "bt 1" \
141+
"#0\\s+main \\(\\) at\[^\r\n\]+" \
142+
"check inferior 2 is in main"
143+
144+
# Switch to inferior 1 and allow it to continue. This is a
145+
# critical part of the test. When the test was added a bug (in
146+
# all-stop mode) would leave inferior 1 stopped, but GDB code
147+
# would think the thread was running. As such. the thread
148+
# couldn't be resumed again.
149+
gdb_test "inferior 1" "Switching to inferior 1.*"
150+
151+
# In non-stop mode, thread 1.1 is correctly left running, so we
152+
# need to stop it now.
153+
if { $non_stop } {
154+
gdb_test -no-prompt-anchor "interrupt"
155+
gdb_test "p 1 + 1" " = 2" \
156+
"simple print to resync output"
157+
}
158+
159+
gdb_breakpoint breakpt
160+
gdb_continue_to_breakpoint "continue to breakpoint in breakpt"
161+
gdb_test "bt 1" \
162+
[multi_line \
163+
"#0\\s+breakpt \\(\\) at\[^\r\n\]+" \
164+
"\\(More stack frames follow\\.\\.\\.\\)"] \
165+
"check inferior 1 is in breakpt"
166+
167+
# Switch back to inferior 2. The testing infrastructure will try to
168+
# use 'monitor exit' to close gdbserver. It helps if we are in the
169+
# gdbserver inferior when the script finishes.
170+
gdb_test "inferior 2" "Switching to inferior 2.*" \
171+
"switch back to inferior 2"
172+
}
173+
174+
# Multi-inferior support requires non-stop targets.
175+
foreach_with_prefix target_non_stop { auto on } {
176+
# But it's OK if we're emulating all-stop mode on top of non-stop.
177+
foreach_with_prefix non_stop { on off } {
178+
run_test $target_non_stop $non_stop
179+
}
180+
}

0 commit comments

Comments
 (0)