blob: c048acad3e75932469135706c00a90072bde125f [file] [log] [blame]
# Copyright 2023-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 the =breakpoint-deleted notification for a thread-specific
# breakpoint is sent as soon as the related thread exits, and not when the
# inferior next stops.
#
# This test is based on gdb.threads/thread-bp-deleted.exp.
load_lib mi-support.exp
set MIFLAGS "-i=mi"
# We need to do things a little differently when using the remote protocol.
set is_remote \
[expr [target_info exists gdb_protocol] \
&& ([string equal [target_info gdb_protocol] "remote"] \
|| [string equal [target_info gdb_protocol] "extended-remote"])]
standard_testfile
if { [build_executable "failed to prepare" $testfile $srcfile \
{debug pthreads}] } {
return -1
}
foreach_mi_ui_mode mode {
mi_gdb_exit
if {$mode eq "separate"} {
set start_ops "separate-mi-tty"
} else {
set start_ops ""
}
# Restart, but enable non-stop mode, we need it for background
# execution.
save_vars { GDBFLAGS } {
append GDBFLAGS " -ex \"maint set target-non-stop on\""
append GDBFLAGS " -ex \"set mi-async on\""
mi_clean_restart $binfile $start_ops
}
mi_runto_main
if {![mi_detect_async]} {
unsupported "async-mode is required"
continue
}
mi_delete_breakpoints
# Place a breakpoint on 'breakpt' and run to this breakpoint.
mi_create_breakpoint "breakpt" "place breakpoint on breakpt"
set breakpt_num [mi_get_valueof "/d" "\$bpnum" "INVALID" \
"get number for breakpt breakpoint"]
mi_execute_to "exec-continue" "breakpoint-hit" "breakpt" "" \
".*" ".*" {"" "disp=\"keep\""} \
"continue to breakpoint in breakpt"
# Now drain all the pending output from the CLI if we are using a separate
# UI.
if {$mode eq "separate"} {
with_spawn_id $gdb_main_spawn_id {
gdb_test_multiple "" "drain CLI output upto breakpoint" {
-re "Thread 1 \[^\r\n\]+ hit Breakpoint $decimal,\
breakpt \\(\\) at\
\[^\r\n\]+\r\n$decimal\\s+\[^\r\n\]+\r\n" {
pass $gdb_test_name
}
}
}
}
# This is just for convenience, this refers to the second thread the
# inferior spawns.
set worker_thread 2
# Create a thread-specific breakpoint.
mi_create_breakpoint "-p $worker_thread main" \
"place thread breakpoint on main" \
-thread "$worker_thread"
set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID" \
"get number for thread-specific breakpoint"]
set loc1 [mi_make_breakpoint -number "$breakpt_num"]
set loc2 [mi_make_breakpoint -number "$bpnum" -thread "$worker_thread"]
set table_2_locs [mi_make_breakpoint_table [list $loc1 $loc2]]
set table_1_locs [mi_make_breakpoint_table [list $loc1]]
mi_gdb_test "-break-info" \
"\\^done,$table_2_locs" \
"-break-info, expecting two locations"
# Resume the inferior, at this point the inferior will spin while
# we interact with it.
mi_send_resuming_command "exec-continue" "continue"
# Look for the thread-exited notification and the breakpoint-deleted
# notification. When using a single UI we see both the MI and CLI
# messages. When using a separate MI UI we only see the MI messages.
set saw_cli_thread_exited false
set saw_mi_thread_exited false
set saw_cli_bp_deleted false
set saw_mi_bp_deleted false
# When running with a remote target, the thread-exited event doesn't
# appear to be pushed from the target to GDB; instead GDB has to fetch the
# thread list from the target and spot that a thread exited.
#
# In order to achieve this, when running with a remote target we run the
# '-thread-info 99' command. There isn't a thread 99, but GDB doesn't
# know that until it fetches the thread list. By fetching the thread list
# GDB will spot that the thread we are interested in has exited.
if {$is_remote} {
set cmd "-thread-info 99"
set attempt_count 5
} else {
set cmd ""
set attempt_count 0
}
gdb_test_multiple $cmd "collect thread exited output" \
-prompt "$::mi_gdb_prompt$" {
-re "^~\"\\\[Thread \[^\r\n\]+ exited\\\]\\\\n\"\r\n" {
set saw_cli_thread_exited true
exp_continue
}
-re "^~\"Thread-specific breakpoint $bpnum deleted -\
thread $worker_thread no longer in the thread list\\.\\\\n\"\r\n" {
set saw_cli_bp_deleted true
exp_continue
}
-re "^=thread-exited,id=\"$worker_thread\",group-id=\"i1\"\r\n" {
set saw_mi_thread_exited true
# The order of the MI notifications depends on the order in which
# the observers where registered within GDB. If we have not seen
# the other MI notification yet then keep looking.
#
# Additionally, for remote targets, we're going to wait for the
# output of the '-thread-info 99' command before we check the
# results.
if {!$saw_mi_bp_deleted || $is_remote} {
exp_continue
}
# We get here with a native target; check we saw all the output
# that we expected.
if {$mode eq "separate"} {
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
&& !$saw_cli_thread_exited \
&& !$saw_cli_bp_deleted } \
$gdb_test_name
} else {
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
&& $saw_cli_thread_exited \
&& $saw_cli_bp_deleted } \
$gdb_test_name
}
}
-re "^=breakpoint-deleted,id=\"3\"\r\n" {
set saw_mi_bp_deleted true
# The order of the MI notifications depends on the order in which
# the observers where registered within GDB. If we have not seen
# the other MI notification yet then keep looking.
#
# Additionally, for remote targets, we're going to wait for the
# output of the '-thread-info 99' command before we check the
# results.
if {!$saw_mi_thread_exited || $is_remote} {
exp_continue
}
# We get here with a native target; check we saw all the output
# that we expected.
if {$mode eq "separate"} {
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
&& !$saw_cli_thread_exited \
&& !$saw_cli_bp_deleted } \
$gdb_test_name
} else {
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
&& $saw_cli_thread_exited \
&& $saw_cli_bp_deleted } \
$gdb_test_name
}
}
-re "^-thread-info 99\r\n" {
if {!$is_remote} {
fail "$gdb_test_name (unexpected output)"
}
# This is the command being echoed back, ignore it.
exp_continue
}
-re "^\\^done,threads=\\\[\\\]\r\n$::mi_gdb_prompt$" {
# This is the result of the '-thread-info 99' trick, which is only
# used in remote mode. If we see this in native mode then
# something has gone wrong.
if {!$is_remote} {
fail "$gdb_test_name (unexpected output)"
}
# If we've not seen any of the expected output yet then maybe the
# remote thread just hasn't exited yet. Wait a short while and
# try again.
if { !$saw_mi_thread_exited && !$saw_mi_bp_deleted \
&& !$saw_cli_thread_exited && !$saw_cli_bp_deleted \
&& $attempt_count > 0 } {
sleep 1
incr attempt_count -1
send_gdb "$cmd\n"
exp_continue
}
# The output has arrived! Check how we did. There are other bugs
# that come into play here which change what output we'll see.
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
&& $saw_cli_thread_exited \
&& $saw_cli_bp_deleted } $gdb_test_name
}
}
# When the MI is running on a separate UI the CLI message will be seen
# over there, but only if we are not running remote. When we are running
# remote then the thread-exited event will only be triggered as a result
# of user triggering a refresh of the thread list (hence the '-thread-info
# 99' trick above). By typing a command we change the current UI to the
# terminal we are typing at, as a result these CLI style message will
# actually appear on the MI when using a remote target.
if {$mode eq "separate" && !$is_remote} {
with_spawn_id $gdb_main_spawn_id {
set saw_thread_exited false
gdb_test_multiple "" "collect cli thread exited output" {
-re "\\\[Thread \[^\r\n\]+ exited\\\]\r\n" {
set saw_thread_exited true
exp_continue
}
-re "^Thread-specific breakpoint $bpnum deleted -\
thread $worker_thread no longer in the thread list\\.\r\n" {
gdb_assert { $saw_thread_exited } \
$gdb_test_name
}
}
}
}
mi_gdb_test "-break-info" \
"\\^done,$table_1_locs" \
"-break-info, expecting one location"
# Set 'do_spin' to zero, this allows the inferior to progress again; we
# should then hit the breakpoint in 'breakpt' again.
mi_gdb_test "set var do_spin = 0" \
[multi_line \
".*=memory-changed,thread-group=\"i${decimal}\".addr=\"${hex}\",len=\"${hex}\"" \
"\\^done"] \
"set do_spin variable in inferior, inferior should now finish"
mi_expect_stop "breakpoint-hit" "breakpt" ".*" ".*" "$::decimal" \
{"" "disp=\"keep\""} "stop in breakpt at the end of the test"
}