aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog17
-rw-r--r--gdb/gdbthread.h6
-rw-r--r--gdb/inferior.c1
-rw-r--r--gdb/infrun.c31
-rw-r--r--gdb/testsuite/ChangeLog9
-rw-r--r--gdb/testsuite/gdb.base/fork-running-state.exp17
-rw-r--r--gdb/testsuite/gdb.multi/tids-gid-reset.c22
-rw-r--r--gdb/testsuite/gdb.multi/tids-gid-reset.exp96
-rw-r--r--gdb/testsuite/gdb.threads/async.c70
-rw-r--r--gdb/testsuite/gdb.threads/async.exp98
-rw-r--r--gdb/thread.c19
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 ();