| # Copyright (C) 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 '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 |
| } |
| } |