Files
binutils-gdb/gdb/testsuite/gdb.threads/thread-bp-deleted.exp
Pedro Alves 9d7d58e726 gdb: centralize "[Thread ...exited]" notifications
Currently, each target backend is responsible for printing "[Thread
...exited]" before deleting a thread.  This leads to unnecessary
differences between targets, like e.g. with the remote target, we
never print such messages, even though we do print "[New Thread ...]".

E.g., debugging the gdb.threads/attach-many-short-lived-threads.exp
with gdbserver, letting it run for a bit, and then pressing Ctrl-C, we
currently see:

 (gdb) c
 Continuing.
 ^C[New Thread 3850398.3887449]
 [New Thread 3850398.3887500]
 [New Thread 3850398.3887551]
 [New Thread 3850398.3887602]
 [New Thread 3850398.3887653]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb)

Above, we only see "New Thread" notifications, even though threads
were deleted.

After this patch, we'll see:

 (gdb) c
 Continuing.
 ^C[Thread 3558643.3577053 exited]
 [Thread 3558643.3577104 exited]
 [Thread 3558643.3577155 exited]
 [Thread 3558643.3579603 exited]
 ...
 [New Thread 3558643.3597415]
 [New Thread 3558643.3600015]
 [New Thread 3558643.3599965]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb) q

This commit fixes this by moving the thread exit printing to common
code instead, triggered from within delete_thread (or rather,
set_thread_exited).

There's one wrinkle, though.  While most targest want to print:

 [Thread ... exited]

the Windows target wants to print:

 [Thread ... exited with code <exit_code>]

... and sometimes wants to suppress the notification for the main
thread.  To address that, this commits adds a delete_thread_with_code
function, only used by that target (so far).

This fix was originally posted as part of a larger series:

  https://inbox.sourceware.org/gdb-patches/20221212203101.1034916-1-pedro@palves.net/

But didn't really need to be part of that series.  In order to get
this fix merged sooner, I (Andrew Burgess) have rebased this commit
outside of the original series.  Any bugs introduced while splitting
this patch out and rebasing, are entirely my own.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30129
Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
2023-08-23 09:57:38 +01:00

201 lines
7.2 KiB
Plaintext

# Copyright (C) 2023 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 'maint info breakpoints' succeeds in the period of time
# between a thread-specific breakpoint being deleted, and GDB next
# stopping.
#
# There used to be a bug where GDB would try to lookup the thread for
# the thread-specific breakpoint, the thread of course having been
# deleted, couldn't be found, and GDB would end up dereferencing a
# nullptr.
standard_testfile
if {[build_executable "failed to prepare" $testfile $srcfile \
{debug pthreads}] == -1} {
return -1
}
# 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"])]
# This test requires background execution, which relies on non-stop mode.
save_vars { GDBFLAGS } {
append GDBFLAGS " -ex \"maint set target-non-stop on\""
clean_restart ${binfile}
}
if {![runto_main]} {
return -1
}
# Check we hace non-stop mode. We do try to force this on above, but maybe
# the target doesn't support non-stop mode, in which case (hopefully)
# non-stop mode will still show as off, and this test should not be run.
if {![is_target_non_stop]} {
unsupported "required non-stop mode"
return -1
}
delete_breakpoints
gdb_breakpoint "breakpt"
gdb_continue_to_breakpoint "continue to first breakpt call"
set breakpt_num [get_integer_valueof "\$bpnum" "INVALID" \
"get number for breakpoint in breakpt"]
# Check info threads just to confirm the thread numbering. The rest
# of this script just assumes we have threads numbered 1 and 2.
gdb_test "info threads" \
[multi_line \
"\\* 1\\s+Thread \[^\r\n\]+" \
" 2\\s+Thread \[^\r\n\]+"]
set main_thread 1
set worker_thread 2
# Check the 'info breakpoints' output for the thread-specific breakpoint
# numbered BPNUM. If EXPECTED is true then the breakpoint is expected to be
# present, otherwise, the breakpoint is expected not to be present.
proc check_for_thread_specific_breakpoint { testname bpnum expected } {
set saw_thread_specific_bp false
gdb_test_multiple "info breakpoints" $testname {
-re "^(\[^\r\n\]+)\r\n" {
set line $expect_out(1,string)
if { [regexp "$bpnum\\s+breakpoint\[^\r\n\]+ $::hex in main\
at \[^\r\n\]+" $line] } {
set saw_thread_specific_bp true
}
exp_continue
}
-re "^$::gdb_prompt $" {
set result [expr $expected ? $saw_thread_specific_bp \
: !$saw_thread_specific_bp]
gdb_assert { $result } $gdb_test_name
}
}
}
# Create a thread-specific breakpoint. This will never actually be hit; we
# don't care, we just want to see GDB auto-delete this breakpoint.
gdb_breakpoint "main thread $worker_thread" \
"create a thread-specific breakpoint"
set bpnum [get_integer_valueof "\$bpnum" "INVALID" \
"get number for thread-specific breakpoint"]
# Check the thread-specific breakpoint is present in 'info breakpoints'.
check_for_thread_specific_breakpoint \
"check for thread-specific b/p before thread exit" $bpnum true
# Continue in async mode. After this the worker thread will exit.
# The -no-prompt-anchor is needed here as sometimes the exit of the
# worker thread will happen so quickly that expect will see the
# 'thread exited' message immediately after the prompt, which breaks
# the normal gdb_test prompt anchoring.
gdb_test -no-prompt-anchor "continue&" "Continuing\\."
if {$is_remote} {
# Collect the output from GDB telling us that the thread exited.
# Unfortunately in the remote protocol the thread-exited event doesn't
# appear to be pushed to GDB, instead we rely on GDB asking about the
# threads (which isn't great).
#
# So, what we do here is ask about thread 99, which hopefully shouldn't
# exist, however, in order to answer that question GDB has to grab the
# thread list from the remote, at which point GDB will spot that one of
# the threads has exited, and will tell us about it.
#
# However, we might be too quick sending the 'info threads 99' command,
# so, if we see the output of that command without any thread exited
# text, we wait for a short while and try again. We wait for upto 5
# seconds (5 tries). However, this might mean on a _really_ slow
# machine that the thread still hasn't exited. I guess if we start
# seeing that then we can just update ATTEMPT_COUNT below.
set saw_thread_exited false
set saw_bp_deleted false
set attempt_count 5
gdb_test_multiple "info threads 99" "collect thread exited output" {
-re "info threads 99\r\n" {
exp_continue
}
-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" {
set saw_bp_deleted true
exp_continue
}
-re "No threads match '99'\\.\r\n$gdb_prompt $" {
if {!$saw_thread_exited && !$saw_bp_deleted && $attempt_count > 0} {
sleep 1
incr attempt_count -1
send_gdb "info threads 99\n"
exp_continue
}
gdb_assert { $saw_thread_exited && $saw_bp_deleted } $gdb_test_name
}
}
} else {
# Collect the output from GDB telling us that the thread exited.
set saw_thread_exited false
gdb_test_multiple "" "collect 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
}
}
}
# Check the thread-specific breakpoint is no longer present in 'info
# breakpoints' output.
check_for_thread_specific_breakpoint \
"check for thread-specific b/p before after exit" $bpnum false
# Check the thread-specific breakpoint doesn't show up in the 'maint
# info breakpoints' output. And also that this doesn't cause GDB to
# crash, which it did at one point.
gdb_test_lines "maint info breakpoints" "" ".*" \
-re-not "breakpoint\\s+keep\\s+y\\s+$hex\\s+in main at "
# Set the do_spin variable in the inferior. This will cause it to drop out
# of its spin loop and hit the next breakpoint. Remember, at this point the
# inferior is still executing.
gdb_test "print do_spin = 0" "\\\$$decimal = 0"
# Collect the notification that the inferior has stopped.
gdb_test_multiple "" "wait for stop" {
-re "Thread $main_thread \[^\r\n\]+ hit Breakpoint ${breakpt_num},\
breakpt \\(\\) \[^\r\n\]+\r\n$decimal\\s+\[^\r\n\]+\r\n" {
pass $gdb_test_name
}
}