blob: 5272f5c901aa421077f2ff4a581f5c80aae63bd6 [file] [log] [blame] [edit]
# Copyright 2023-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/>.
# Check that the $_inferior_thread_count convenience variable will
# correctly update its value without passing through a normal stop
# event.
#
# When GDB is using a remote (or extended-remote) target, GDB only
# learns about changes to the thread list be explicitly querying the
# target. This is done as part of a normal stop, but there are some
# situations where the thread list can change, but GDB doesn't pass
# through a normal stop.
#
# For example, when the target is running asynchronously in non-stop
# mode; in this case GDB can query the thread list without the target
# ever stopping.
#
# Or after an inferior function call.
#
# The solution is to ensure that $_inferior_thread_count explicitly
# queries the target to update the thread list. This test checks that
# this is done.
standard_testfile
if {[build_executable "failed to prepare" $testfile $srcfile \
{debug pthreads}] == -1} {
return -1
}
# Start GDB. Ensure we are in non-stop mode as we need to read from
# the inferior while it is running.
save_vars {GDBFLAGS} {
append GDBFLAGS { -ex "set non-stop on"}
clean_restart $::testfile
}
if ![runto_main] {
return -1
}
gdb_breakpoint breakpt
gdb_continue_to_breakpoint "first breakpt call"
set re_var [string_to_regexp "$"]$decimal
# Check we can see a single thread to begin with.
gdb_test {p $_inferior_thread_count} \
"^$re_var = 1" \
{only one thread in $_inferior_thread_count}
# We don't want thread events, it makes it harder to match GDB's
# output.
gdb_test_no_output "set print thread-events off"
# Continue the program in the background.
gdb_test -no-prompt-anchor "continue&" \
[string_to_regexp "Continuing."]
# Read the 'stage' flag from the inferior. This is initially 0, but
# will be set to 1 once the extra thread has been created, and then 2
# once the extra thread has exited.
#
# We catch the case where we can't read from the inferior while the
# inferior is running, this happens if the target hasn't entered
# non-stop mode like we asked. In this case we interrupt the inferior
# and bail.
#
# Otherwise, if we can read from the inferior we try at most 10 times
# to read the flag (with a 1 second delay after each read). If the
# flag isn't set after this then we bail. The inferior is either very
# slow, or the thread hasn't started for some reason.
proc wait_for_stage { num } {
set failure_count 0
set cmd "print /d stage"
set stage_flag 0
set re_int -?$::decimal
set re_msg \
[multi_line \
"Cannot execute this command while the target is running" \
{Use the "interrupt" command to stop the target} \
[string_to_regexp "and then try again."]]
gdb_test_multiple "$cmd" "wait for 'stage' flag to be $num" {
-re -wrap ^$re_msg {
fail "$gdb_test_name (can't read asynchronously)"
gdb_test_no_output "interrupt"
gdb_test_multiple "" "wait for thread to stop" {
-re "Thread .* received signal .*" {
pass $gdb_test_name
gdb_test "p 1 + 2" " = 3"
}
}
}
-re -wrap "^$::re_var = ($re_int).*" {
set stage_flag $expect_out(1,string)
if {$stage_flag != $num} {
set stage_flag 0
incr failure_count
if { $failure_count < 10 } {
sleep 1
send_gdb "$cmd\n"
exp_continue
}
fail $gdb_test_name
} else {
pass $gdb_test_name
}
}
}
return $stage_flag
}
# Wait until we can see that the extra thread has been created.
if {![wait_for_stage 1]} {
unresolved "failed to see thread start"
return -1
}
if {[target_info exists gdb_protocol]
&& ([target_info gdb_protocol] == "remote"
|| [target_info gdb_protocol] == "extended-remote")} {
set new_thread_re {\[New Thread [^\r\n]+\]\r\n}
set exit_thread_re {\[Thread [^\r\n]+ exited\]\r\n}
} else {
set new_thread_re ""
set exit_thread_re ""
}
# This is the test we actually care about. Check that the
# $_inferior_thread_count convenience variable shows the correct
# thread count; the new thread should be visible.
gdb_test {with print thread-events on -- p $_inferior_thread_count} \
"^${new_thread_re}$re_var = 2" \
{second thread visible in $_inferior_thread_count}
# Set a variable in the inferior, this will cause the second thread to
# exit.
gdb_test_no_output "set variable spin = 0" \
"set 'spin' flag to allow worker thread to exit"
# Wait until the extra thread has exited.
if {![wait_for_stage 2]} {
unresolved "failed to see thread start"
return -1
}
# Check that the second thread has gone away.
gdb_test {with print thread-events on -- p $_inferior_thread_count} \
"^${exit_thread_re}$re_var = 1" \
{back to one thread visible in $_inferior_thread_count}
# Set a variable in the inferior, this will cause the second thread to
# exit.
gdb_test_no_output -no-prompt-anchor "set variable spin = 0" \
"set 'spin' flag to allow main thread to exit"
# When the second thread exits, the main thread joins with it, and
# then proceeds to hit the breakpt function again.
set re_breakpt [string_to_regexp "breakpt ()"]
set re \
[multi_line \
"Thread 1 \[^\r\n\]+ hit Breakpoint $decimal, $re_breakpt\[^\r\n\]+" \
"\[^\r\n\]+" \
""]
gdb_test_multiple "" "wait for main thread to stop" {
-re $re {
pass $gdb_test_name
}
}