aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.threads
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2020-01-10 20:05:41 +0000
committerPedro Alves <palves@redhat.com>2020-01-10 20:05:41 +0000
commit873657b9e824943ae44c12966c29cbbcd21c986f (patch)
tree151b26a7c25ecb41375900ae9cdf52eeef701a4f /gdb/testsuite/gdb.threads
parent7f0ae84c80aae6fe8f7573343fe1ab455d18b5d8 (diff)
downloadgdb-873657b9e824943ae44c12966c29cbbcd21c986f.zip
gdb-873657b9e824943ae44c12966c29cbbcd21c986f.tar.gz
gdb-873657b9e824943ae44c12966c29cbbcd21c986f.tar.bz2
Preserve selected thread in all-stop w/ background execution
In non-stop mode, if you resume the program in the background (with "continue&", for example), then gdb makes sure to not switch the current thread behind your back. That means that you can be sure that the commands you type apply to the thread you selected, even if some other thread that was running in the background hits some event just while you're typing. In all-stop mode, however, if you resume the program in the background, gdb let's the current thread switch behind your back. This is bogus, of course. All-stop and non-stop background resumptions should behave the same. This patch fixes that, and adds a testcase that exposes the bad behavior in current master. The fork-running-state.exp changes are necessary because that preexisting testcase was expecting the old behavior: Before: continue & Continuing. (gdb) [Attaching after process 8199 fork to child process 8203] [New inferior 2 (process 8203)] info threads Id Target Id Frame 1.1 process 8199 "fork-running-st" (running) * 2.1 process 8203 "fork-running-st" (running) (gdb) After: continue & Continuing. (gdb) [Attaching after process 24660 fork to child process 24664] [New inferior 2 (process 24664)] info threads Id Target Id Frame * 1.1 process 24660 "fork-running-st" (running) 2.1 process 24664 "fork-running-st" (running) (gdb) Here we see that before this patch GDB switches current inferior to the new inferior behind the user's back, as a side effect of handling the fork. The delete_exited_threads call in inferior_appeared is there to fix an issue that Baris found in a previous version of this patch. The fetch_inferior_event change increases the refcount of the current thread, and in case the fetched inferior event denotes a thread exit, the thread will not be deleted right away. A non-deleted but exited thread stays in the inferior's thread list. This, in turn, causes the "init_thread_list" call in inferior.c to be skipped. A consequence is that the global thread ID counter is not restarted if the current thread exits, and then the inferior is restarted: (gdb) start Temporary breakpoint 1 at 0x4004d6: file main.c, line 21. Starting program: /tmp/main Temporary breakpoint 1, main () at main.c:21 21 foo (); (gdb) info threads -gid Id GId Target Id Frame * 1 1 process 16106 "main" main () at main.c:21 (gdb) c Continuing. [Inferior 1 (process 16106) exited normally] (gdb) start Temporary breakpoint 2 at 0x4004d6: file main.c, line 21. Starting program: /tmp/main Temporary breakpoint 2, main () at main.c:21 21 foo (); (gdb) info threads -gid Id GId Target Id Frame * 1 2 process 16138 "main" main () at main.c:21 ^^^ Notice that GId == 2 above. It should have been "1" instead. The new tids-git-reset.exp testcase exercises the problem above. gdb/ChangeLog: 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. gdb/testsuite/ChangeLog: 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.
Diffstat (limited to 'gdb/testsuite/gdb.threads')
-rw-r--r--gdb/testsuite/gdb.threads/async.c70
-rw-r--r--gdb/testsuite/gdb.threads/async.exp98
2 files changed, 168 insertions, 0 deletions
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
+ }
+}