diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/breakpoint.c | 6 | ||||
-rw-r--r-- | gdb/python/py-breakpoint.c | 3 | ||||
-rw-r--r-- | gdb/python/py-finishbreakpoint.c | 33 | ||||
-rw-r--r-- | gdb/python/python-internal.h | 1 | ||||
-rw-r--r-- | gdb/testsuite/gdb.mi/mi-thread-bp-deleted.c | 88 | ||||
-rw-r--r-- | gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp | 290 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/thread-bp-deleted.c | 88 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/thread-bp-deleted.exp | 210 | ||||
-rw-r--r-- | gdb/thread.c | 2 |
9 files changed, 709 insertions, 12 deletions
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 36c53e5..a42d26f 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -3249,14 +3249,10 @@ remove_threaded_breakpoints (struct thread_info *tp, int silent) { if (b->thread == tp->global_num && user_breakpoint_p (b)) { - b->disposition = disp_del_at_next_stop; - gdb_printf (_("\ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\n"), b->number, print_thread_id (tp)); - - /* Hide it from the user. */ - b->number = 0; + delete_breakpoint (b); } } } diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index ecf52a4..880f1b5 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -1199,6 +1199,9 @@ gdbpy_breakpoint_deleted (struct breakpoint *b) gdbpy_ref<gdbpy_breakpoint_object> bp_obj (bp->py_bp_object); if (bp_obj != NULL) { + if (bp_obj->is_finish_bp) + bpfinishpy_pre_delete_hook (bp_obj.get ()); + if (!evregpy_no_listeners_p (gdb_py_events.breakpoint_deleted)) { if (evpy_emit_event ((PyObject *) bp_obj.get (), diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index 159164e..7122fa8 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -349,16 +349,19 @@ bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj) if (meth_result == NULL) gdbpy_print_stack (); } - - delete_breakpoint (bpfinish_obj->py_bp.bp); } /* Callback for `bpfinishpy_detect_out_scope'. Triggers Python's - `B->out_of_scope' function if B is a FinishBreakpoint out of its scope. */ + `B->out_of_scope' function if B is a FinishBreakpoint out of its scope. + + When DELETE_BP is true then breakpoint B will be deleted if B is a + FinishBreakpoint and it is out of scope, otherwise B will not be + deleted. */ static void bpfinishpy_detect_out_scope_cb (struct breakpoint *b, - struct breakpoint *bp_stopped) + struct breakpoint *bp_stopped, + bool delete_bp) { PyObject *py_bp = (PyObject *) b->py_bp_object; @@ -379,7 +382,11 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, if (b->pspace == current_inferior ()->pspace && (!target_has_registers () || frame_find_by_id (initiating_frame) == NULL)) - bpfinishpy_out_of_scope (finish_bp); + { + bpfinishpy_out_of_scope (finish_bp); + if (delete_bp) + delete_breakpoint (finish_bp->py_bp.bp); + } } catch (const gdb_exception &except) { @@ -390,6 +397,17 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, } } +/* Called when gdbpy_breakpoint_deleted is about to delete a breakpoint. A + chance to trigger the out_of_scope callback (if appropriate) for the + associated Python object. */ + +void +bpfinishpy_pre_delete_hook (struct gdbpy_breakpoint_object *bp_obj) +{ + breakpoint *bp = bp_obj->bp; + bpfinishpy_detect_out_scope_cb (bp, nullptr, false); +} + /* Attached to `stop' notifications, check if the execution has run out of the scope of any FinishBreakpoint before it has been hit. */ @@ -399,7 +417,8 @@ bpfinishpy_handle_stop (struct bpstat *bs, int print_frame) gdbpy_enter enter_py; for (breakpoint *bp : all_breakpoints_safe ()) - bpfinishpy_detect_out_scope_cb (bp, bs == NULL ? NULL : bs->breakpoint_at); + bpfinishpy_detect_out_scope_cb + (bp, bs == NULL ? NULL : bs->breakpoint_at, true); } /* Attached to `exit' notifications, triggers all the necessary out of @@ -411,7 +430,7 @@ bpfinishpy_handle_exit (struct inferior *inf) gdbpy_enter enter_py (target_gdbarch ()); for (breakpoint *bp : all_breakpoints_safe ()) - bpfinishpy_detect_out_scope_cb (bp, nullptr); + bpfinishpy_detect_out_scope_cb (bp, nullptr, true); } /* Initialize the Python finish breakpoint code. */ diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 55bbe78..258f5c4 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -777,6 +777,7 @@ extern const struct value_print_options *gdbpy_current_print_options; void bpfinishpy_pre_stop_hook (struct gdbpy_breakpoint_object *bp_obj); void bpfinishpy_post_stop_hook (struct gdbpy_breakpoint_object *bp_obj); +void bpfinishpy_pre_delete_hook (struct gdbpy_breakpoint_object *bp_obj); extern PyObject *gdbpy_doc_cst; extern PyObject *gdbpy_children_cst; diff --git a/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.c b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.c new file mode 100644 index 0000000..5ac23b4 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.c @@ -0,0 +1,88 @@ +/* Copyright 2023 Free Software Foundation, Inc. + + This file is part of GDB. + + 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/>. */ + +#include <pthread.h> +#include <unistd.h> +#include <stdlib.h> + +#define NTHREAD 1 + +/* Barrier for synchronising threads. */ +static pthread_barrier_t barrier; + +/* Wrapper around pthread_barrier_wait that aborts if the wait returns an + error. */ +static void +barrier_wait (pthread_barrier_t *barrier) +{ + int res = pthread_barrier_wait (barrier); + if (res != PTHREAD_BARRIER_SERIAL_THREAD && res != 0) + abort (); +} + +/* GDB can set this to 0 to force the 'spin' function to return. */ +volatile int do_spin = 1; + +void +breakpt () +{ + /* Nothing. */ +} + +/* Spin for a reasonably long while (this lets GDB run some commands in + async mode), but return early if/when do_spin is set to 0 (which is done + by GDB). */ + +void +spin () +{ + int i; + + for (i = 0; i < 300 && do_spin; ++i) + sleep (1); +} + +void * +thread_worker (void *arg) +{ + barrier_wait (&barrier); + return NULL; +} + +int +main () +{ + pthread_t thr[NTHREAD]; + int i; + + if (pthread_barrier_init (&barrier, NULL, NTHREAD + 1) != 0) + abort (); + + for (i = 0; i < NTHREAD; ++i) + pthread_create (&thr[i], NULL, thread_worker, NULL); + + breakpt (); /* First breakpoint. */ + + barrier_wait (&barrier); + + for (i = 0; i < NTHREAD; ++i) + pthread_join (thr[i], NULL); + + spin (); + + breakpt (); +} diff --git a/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp new file mode 100644 index 0000000..0ebca92 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp @@ -0,0 +1,290 @@ +# Copyright 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 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. + if { $saw_mi_thread_exited && $saw_mi_bp_deleted \ + && $saw_cli_thread_exited \ + && $saw_cli_bp_deleted } { + kpass "gdb/30129" $gdb_test_name + } elseif { $saw_mi_thread_exited && $saw_mi_bp_deleted \ + && !$saw_cli_thread_exited \ + && $saw_cli_bp_deleted } { + kfail "gdb/30129" $gdb_test_name + } else { + fail "$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" +} diff --git a/gdb/testsuite/gdb.threads/thread-bp-deleted.c b/gdb/testsuite/gdb.threads/thread-bp-deleted.c new file mode 100644 index 0000000..5ac23b4 --- /dev/null +++ b/gdb/testsuite/gdb.threads/thread-bp-deleted.c @@ -0,0 +1,88 @@ +/* Copyright 2023 Free Software Foundation, Inc. + + This file is part of GDB. + + 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/>. */ + +#include <pthread.h> +#include <unistd.h> +#include <stdlib.h> + +#define NTHREAD 1 + +/* Barrier for synchronising threads. */ +static pthread_barrier_t barrier; + +/* Wrapper around pthread_barrier_wait that aborts if the wait returns an + error. */ +static void +barrier_wait (pthread_barrier_t *barrier) +{ + int res = pthread_barrier_wait (barrier); + if (res != PTHREAD_BARRIER_SERIAL_THREAD && res != 0) + abort (); +} + +/* GDB can set this to 0 to force the 'spin' function to return. */ +volatile int do_spin = 1; + +void +breakpt () +{ + /* Nothing. */ +} + +/* Spin for a reasonably long while (this lets GDB run some commands in + async mode), but return early if/when do_spin is set to 0 (which is done + by GDB). */ + +void +spin () +{ + int i; + + for (i = 0; i < 300 && do_spin; ++i) + sleep (1); +} + +void * +thread_worker (void *arg) +{ + barrier_wait (&barrier); + return NULL; +} + +int +main () +{ + pthread_t thr[NTHREAD]; + int i; + + if (pthread_barrier_init (&barrier, NULL, NTHREAD + 1) != 0) + abort (); + + for (i = 0; i < NTHREAD; ++i) + pthread_create (&thr[i], NULL, thread_worker, NULL); + + breakpt (); /* First breakpoint. */ + + barrier_wait (&barrier); + + for (i = 0; i < NTHREAD; ++i) + pthread_join (thr[i], NULL); + + spin (); + + breakpt (); +} diff --git a/gdb/testsuite/gdb.threads/thread-bp-deleted.exp b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp new file mode 100644 index 0000000..019bddde --- /dev/null +++ b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp @@ -0,0 +1,210 @@ +# 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 + } + + # When PR gdb/30129 is fixed then this can all be collapsed down + # into a single gdb_assert call. This is split out like this + # because the SAW_BP_DELETED part is working, and we want to + # spot if that stops working. + if { $saw_thread_exited && $saw_bp_deleted } { + kpass "gdb/30129" $gdb_test_name + } elseif {!$saw_thread_exited && $saw_bp_deleted} { + kfail "gdb/30129" $gdb_test_name + } else { + fail $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 + } +} diff --git a/gdb/thread.c b/gdb/thread.c index 1a852f7..5b47215 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -1431,6 +1431,8 @@ show_inferior_qualified_tids (void) const char * print_thread_id (struct thread_info *thr) { + gdb_assert (thr != nullptr); + char *s = get_print_cell (); if (show_inferior_qualified_tids ()) |