diff options
-rw-r--r-- | gdb/ChangeLog | 17 | ||||
-rw-r--r-- | gdb/gdbthread.h | 6 | ||||
-rw-r--r-- | gdb/inferior.c | 1 | ||||
-rw-r--r-- | gdb/infrun.c | 31 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 9 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/fork-running-state.exp | 17 | ||||
-rw-r--r-- | gdb/testsuite/gdb.multi/tids-gid-reset.c | 22 | ||||
-rw-r--r-- | gdb/testsuite/gdb.multi/tids-gid-reset.exp | 96 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/async.c | 70 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/async.exp | 98 | ||||
-rw-r--r-- | gdb/thread.c | 19 |
11 files changed, 363 insertions, 23 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 0e5c8a9..541683f 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,20 @@ +2020-01-10 Pedro Alves <palves@redhat.com> + + * gdbthread.h (scoped_restore_current_thread) + <dont_restore, restore, m_dont_restore>: Declare. + * thread.c (thread_alive): Add assertion. Return bool. + (switch_to_thread_if_alive): New. + (prune_threads): Switch inferior/thread. + (print_thread_info_1): Switch thread before calling target methods. + (scoped_restore_current_thread::restore): New, factored out from + ... + (scoped_restore_current_thread::~scoped_restore_current_thread): + ... this. + (scoped_restore_current_thread::scoped_restore_current_thread): + Add assertion. + (thread_apply_all_command, thread_select): Use + switch_to_thread_if_alive. + 2020-01-10 George Barrett <bob@bob131.so> * stap-probe.c (stap_modify_semaphore): Don't check for null diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index dc7e248..5f1e3bb 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -645,7 +645,13 @@ public: DISABLE_COPY_AND_ASSIGN (scoped_restore_current_thread); + /* Cancel restoring on scope exit. */ + void dont_restore () { m_dont_restore = true; } + private: + void restore (); + + bool m_dont_restore = false; /* Use the "class" keyword here, because of a clash with a "thread_info" function in the Darwin API. */ class thread_info *m_thread; diff --git a/gdb/inferior.c b/gdb/inferior.c index e0c2ab2..9882925 100644 --- a/gdb/inferior.c +++ b/gdb/inferior.c @@ -247,6 +247,7 @@ inferior_appeared (struct inferior *inf, int pid) { /* If this is the first inferior with threads, reset the global thread id. */ + delete_exited_threads (); if (!any_thread_p ()) init_thread_list (); diff --git a/gdb/infrun.c b/gdb/infrun.c index 60567aa..d876634 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -3039,6 +3039,11 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal) finish_state.release (); + /* If we've switched threads above, switch back to the previously + current thread. We don't want the user to see a different + selected thread. */ + switch_to_thread (cur_thr); + /* Tell the event loop to wait for it to stop. If the target supports asynchronous execution, it'll do this from within target_resume. */ @@ -3693,14 +3698,11 @@ fetch_inferior_event (void *client_data) set_current_traceframe (-1); } - gdb::optional<scoped_restore_current_thread> maybe_restore_thread; - - if (non_stop) - /* In non-stop mode, the user/frontend should not notice a thread - switch due to internal events. Make sure we reverse to the - user selected thread and frame after handling the event and - running any breakpoint commands. */ - maybe_restore_thread.emplace (); + /* The user/frontend should not notice a thread switch due to + internal events. Make sure we revert to the user selected + thread and frame after handling the event and running any + breakpoint commands. */ + scoped_restore_current_thread restore_thread; overlay_cache_invalid = 1; /* Flush target cache before starting to handle each event. Target @@ -3777,6 +3779,19 @@ fetch_inferior_event (void *client_data) inferior_event_handler (INF_EXEC_COMPLETE, NULL); cmd_done = 1; } + + /* If we got a TARGET_WAITKIND_NO_RESUMED event, then the + previously selected thread is gone. We have two + choices - switch to no thread selected, or restore the + previously selected thread (now exited). We chose the + later, just because that's what GDB used to do. After + this, "info threads" says "The current thread <Thread + ID 2> has terminated." instead of "No thread + selected.". */ + if (!non_stop + && cmd_done + && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED) + restore_thread.dont_restore (); } } diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 29d6af2..4a2591e 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,12 @@ +2020-01-10 Pedro Alves <palves@redhat.com> + + * gdb.base/fork-running-state.exp (do_test): Adjust expected + output. + * gdb.threads/async.c: New. + * gdb.threads/async.exp: New. + * gdb.multi/tids-gid-reset.c: New. + * gdb.multi/tids-gid-reset.exp: New. + 2020-01-10 George Barrett <bob@bob131.so> * gdb.base/stap-probe.c (relocation_marker): Add dummy variable diff --git a/gdb/testsuite/gdb.base/fork-running-state.exp b/gdb/testsuite/gdb.base/fork-running-state.exp index c3bd0b0..f28f18c 100644 --- a/gdb/testsuite/gdb.base/fork-running-state.exp +++ b/gdb/testsuite/gdb.base/fork-running-state.exp @@ -98,30 +98,19 @@ proc do_test { detach_on_fork follow_fork non_stop schedule_multiple } { set not_nl "\[^\r\n\]*" - if {$detach_on_fork == "on" && $non_stop == "on" && $follow_fork == "child"} { + if {$detach_on_fork == "on" && $follow_fork == "child"} { gdb_test "info threads" \ " 2.1 ${not_nl}\\\(running\\\).*No selected thread.*" - } elseif {$detach_on_fork == "on" && $follow_fork == "child"} { - gdb_test "info threads" \ - "\\\* 2.1 ${not_nl}\\\(running\\\)" } elseif {$detach_on_fork == "on"} { gdb_test "info threads" \ "\\\* 1 ${not_nl}\\\(running\\\)" - } elseif {$non_stop == "on" - || ($schedule_multiple == "on" && $follow_fork == "parent")} { + } elseif {$non_stop == "on" || $schedule_multiple == "on"} { # Both parent and child should be marked running, and the # parent should be selected. gdb_test "info threads" \ [multi_line \ "\\\* 1.1 ${not_nl} \\\(running\\\)${not_nl}" \ " 2.1 ${not_nl} \\\(running\\\)"] - } elseif {$schedule_multiple == "on" && $follow_fork == "child"} { - # Both parent and child should be marked running, and the - # child should be selected. - gdb_test "info threads" \ - [multi_line \ - " 1.1 ${not_nl} \\\(running\\\)${not_nl}" \ - "\\\* 2.1 ${not_nl} \\\(running\\\)"] } else { set test "only $follow_fork marked running" gdb_test_multiple "info threads" $test { @@ -131,7 +120,7 @@ proc do_test { detach_on_fork follow_fork non_stop schedule_multiple } { -re "\\\* 1.1 ${not_nl}\\\(running\\\)\r\n 2.1 ${not_nl}\r\n$gdb_prompt $" { gdb_assert [string eq $follow_fork "parent"] $test } - -re "1.1 ${not_nl}\r\n\\\* 2.1 ${not_nl}\\\(running\\\)\r\n$gdb_prompt $" { + -re "\\\* 1.1 ${not_nl}\r\n 2.1 ${not_nl}\\\(running\\\)\r\n$gdb_prompt $" { gdb_assert [string eq $follow_fork "child"] $test } } diff --git a/gdb/testsuite/gdb.multi/tids-gid-reset.c b/gdb/testsuite/gdb.multi/tids-gid-reset.c new file mode 100644 index 0000000..60cde1e --- /dev/null +++ b/gdb/testsuite/gdb.multi/tids-gid-reset.c @@ -0,0 +1,22 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2019-2020 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/>. */ + +int +main (void) +{ + return 0; +} diff --git a/gdb/testsuite/gdb.multi/tids-gid-reset.exp b/gdb/testsuite/gdb.multi/tids-gid-reset.exp new file mode 100644 index 0000000..0a31512 --- /dev/null +++ b/gdb/testsuite/gdb.multi/tids-gid-reset.exp @@ -0,0 +1,96 @@ +# Copyright 2015-2020 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 letting the inferior exit and restarting it again resets +# the global TID counter, and thus the new thread 1.1 should end up +# with global TID == 1. +# +# Also, check the same but with another inferior still running, in +# which case the new thread 1.1 should end up with global TID == 3. + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {pthreads debug}] } { + return -1 +} + +with_test_prefix "single-inferior" { + with_test_prefix "before restart" { + clean_restart ${testfile} + + if { ![runto_main] } then { + return -1 + } + + gdb_test "info threads -gid" "\\* 1 +1 +.*" + } + + with_test_prefix "restart" { + gdb_continue_to_end + if { ![runto_main] } then { + return -1 + } + } + + with_test_prefix "after restart" { + gdb_test "info threads -gid" "\\* 1 +1 +.*" + } +} + +# For the following tests, multiple inferiors are needed, therefore +# non-extended gdbserver is not supported. +if [use_gdb_stub] { + untested "using gdb stub" + return +} + +# Test with multiple inferiors. This time, since we restart inferior +# 1 while inferior 2 still has threads, then the new thread 1.1 should +# end up with GID == 3, since we won't be able to reset the global +# thread ID counter. +with_test_prefix "multi-inferior" { + gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2" + gdb_test "inferior 2" "Switching to inferior 2 .*" "switch to inferior 2" + gdb_load ${binfile} + + if ![runto_main] then { + fail "starting inferior 2" + return + } + + gdb_test "inferior 1" "Switching to inferior 1 .*" \ + "switch back to inferior 1" + + with_test_prefix "before restart" { + gdb_test "info threads -gid" \ + [multi_line \ + "\\* 1\.1 +1 +.*" \ + " 2\.1 +2 +.*"] + } + + with_test_prefix "restart" { + gdb_continue_to_end + if { ![runto_main] } then { + return -1 + } + } + + with_test_prefix "after restart" { + gdb_test "info threads -gid" \ + [multi_line \ + "\\* 1\.1 +3 +.*" \ + " 2\.1 +2 +.*"] + } +} diff --git a/gdb/testsuite/gdb.threads/async.c b/gdb/testsuite/gdb.threads/async.c new file mode 100644 index 0000000..6649270 --- /dev/null +++ b/gdb/testsuite/gdb.threads/async.c @@ -0,0 +1,70 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2019-2020 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/>. */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pthread.h> + +#define NUM 2 + +static pthread_barrier_t threads_started_barrier; + +static void * +thread_function (void *arg) +{ + pthread_barrier_wait (&threads_started_barrier); + + while (1) + { + /* Sleep a bit to give the other threads a chance to run. */ + usleep (1); /* set breakpoint here */ + } + + pthread_exit (NULL); +} + +static void +all_started (void) +{ +} + +int +main () +{ + pthread_t threads[NUM]; + long i; + + pthread_barrier_init (&threads_started_barrier, NULL, NUM + 1); + + for (i = 1; i <= NUM; i++) + { + int res; + + res = pthread_create (&threads[i - 1], + NULL, + thread_function, NULL); + } + + pthread_barrier_wait (&threads_started_barrier); + + all_started (); + + sleep (180); + + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.threads/async.exp b/gdb/testsuite/gdb.threads/async.exp new file mode 100644 index 0000000..d93b6eb --- /dev/null +++ b/gdb/testsuite/gdb.threads/async.exp @@ -0,0 +1,98 @@ +# Copyright (C) 2019-2020 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/>. + +standard_testfile + +if {[build_executable "failed to prepare" $testfile $srcfile {debug pthreads}] == -1} { + return -1 +} + +# At this point GDB will be busy handling the breakpoint hits and +# re-resuming the program. Even if GDB internally switches thread +# context, the user should not notice it. The following part of the +# testcase ensures that. + +# Switch to thread EXPECTED_THR, and then confirm that the thread +# stays selected. + +proc test_current_thread {expected_thr} { + global decimal + global gdb_prompt + global binfile + + clean_restart $binfile + + if {![runto "all_started"]} { + fail "could not run to all_started" + return + } + + # Set a breakpoint that continuously fires but doeesn't cause a stop. + gdb_breakpoint [concat [gdb_get_line_number "set breakpoint here"] " if 0"] + + gdb_test "thread $expected_thr" "Switching to thread $expected_thr .*" \ + "switch to thread $expected_thr" + + # Continue the program in the background. + set test "continue&" + gdb_test_multiple "continue&" $test { + -re "Continuing\\.\r\n$gdb_prompt " { + pass $test + } + } + + set test "current thread is $expected_thr" + set fails 0 + for {set i 0} {$i < 10} {incr i} { + after 200 + + set cur_thread 0 + gdb_test_multiple "thread" $test { + -re "Current thread is ($decimal) .*$gdb_prompt " { + set cur_thread $expect_out(1,string) + } + } + + if {$cur_thread != $expected_thr} { + incr fails + } + } + + gdb_assert {$fails == 0} $test + + # Explicitly interrupt the target, because in all-stop/remote, + # that's all we can do when the target is running. If we don't do + # this, we'd time out trying to kill the target, while bringing + # down gdb & gdbserver. + set test "interrupt" + gdb_test_multiple $test $test { + -re "^interrupt\r\n$gdb_prompt " { + gdb_test_multiple "" $test { + -re "Thread .* received signal SIGINT, Interrupt\\." { + pass $test + } + } + } + } +} + +# Try once with each thread as current, to avoid missing a bug just +# because some part of GDB manages to switch to the right thread by +# chance. +for {set thr 1} {$thr <= 3} {incr thr} { + with_test_prefix "thread $thr" { + test_current_thread $thr + } +} diff --git a/gdb/thread.c b/gdb/thread.c index 4959f93..630899c 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -1375,7 +1375,8 @@ restore_selected_frame (struct frame_id a_frame_id, int frame_level) } } -scoped_restore_current_thread::~scoped_restore_current_thread () +void +scoped_restore_current_thread::restore () { /* If an entry of thread_info was previously selected, it won't be deleted because we've increased its refcount. The thread represented @@ -1402,6 +1403,22 @@ scoped_restore_current_thread::~scoped_restore_current_thread () && target_has_stack && target_has_memory) restore_selected_frame (m_selected_frame_id, m_selected_frame_level); +} + +scoped_restore_current_thread::~scoped_restore_current_thread () +{ + if (!m_dont_restore) + { + try + { + restore (); + } + catch (const gdb_exception &ex) + { + /* We're in a dtor, there's really nothing else we can do + but swallow the exception. */ + } + } if (m_thread != NULL) m_thread->decref (); |