Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Pedro Alves <palves@redhat.com>
To: gdb-patches@sourceware.org
Subject: [PATCH 01/23] Preserve selected thread in all-stop w/ background execution
Date: Fri, 06 Sep 2019 23:28:00 -0000	[thread overview]
Message-ID: <20190906232807.6191-2-palves@redhat.com> (raw)
In-Reply-To: <20190906232807.6191-1-palves@redhat.com>

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.

gdb/ChangeLog:
yyyy-mm-dd  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:
yyyy-mm-dd  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/gdbthread.h                               |  6 ++
 gdb/infrun.c                                  | 31 ++++++---
 gdb/testsuite/gdb.base/fork-running-state.exp | 17 +----
 gdb/testsuite/gdb.threads/async.c             | 70 +++++++++++++++++++
 gdb/testsuite/gdb.threads/async.exp           | 98 +++++++++++++++++++++++++++
 gdb/thread.c                                  | 19 +++++-
 6 files changed, 218 insertions(+), 23 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/async.c
 create mode 100644 gdb/testsuite/gdb.threads/async.exp

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 04230d3c17..bb7a71af0b 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -646,7 +646,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/infrun.c b/gdb/infrun.c
index a9588f896a..9c888aa72f 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3048,6 +3048,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.  */
@@ -3702,14 +3707,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
@@ -3786,6 +3788,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/gdb.base/fork-running-state.exp b/gdb/testsuite/gdb.base/fork-running-state.exp
index 98733a6cc1..143b5ce714 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.threads/async.c b/gdb/testsuite/gdb.threads/async.c
new file mode 100644
index 0000000000..6bffca9c8d
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/async.c
@@ -0,0 +1,70 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019 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 0000000000..7047bef2bd
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/async.exp
@@ -0,0 +1,98 @@
+# Copyright (C) 2019 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 4a7fe689db..f79507716b 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 ();
-- 
2.14.5


  parent reply	other threads:[~2019-09-06 23:28 UTC|newest]

Thread overview: 67+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-09-06 23:28 [PATCH 00/23] Multi-target support Pedro Alves
2019-09-06 23:28 ` [PATCH 06/23] Don't check target is running in remote_target::mourn_inferior Pedro Alves
2019-09-06 23:28 ` [PATCH 20/23] Revert 'Remove unused struct serial::name field' Pedro Alves
2019-09-06 23:47   ` Christian Biesinger via gdb-patches
2019-09-08 19:30     ` Pedro Alves
2019-09-06 23:28 ` [PATCH 19/23] gdbarch-selftests.c: No longer error out if debugging something Pedro Alves
2019-09-06 23:28 ` [PATCH 16/23] Fix reconnecting to a gdbserver already debugging multiple processes, II Pedro Alves
2019-09-06 23:28 ` [PATCH 03/23] Make "show remote exec-file" inferior-aware Pedro Alves
2019-09-06 23:28 ` [PATCH 09/23] switch inferior/thread before calling target methods Pedro Alves
2019-09-06 23:28 ` [PATCH 18/23] Add multi-target tests Pedro Alves
2019-10-09 16:01   ` Aktemur, Tankut Baris
2019-10-17  0:55     ` Pedro Alves
2019-09-06 23:28 ` [PATCH 08/23] Introduce switch_to_inferior_no_thread Pedro Alves
2019-09-09 18:42   ` Tom Tromey
2019-10-17  1:07     ` Pedro Alves
2019-09-06 23:28 ` [PATCH 11/23] tfile_target::close: trace_fd can't be -1 Pedro Alves
2019-09-06 23:28 ` [PATCH 17/23] Multi-target support Pedro Alves
2019-09-11 17:11   ` Tom Tromey
2019-10-17  1:54     ` Pedro Alves
2019-09-06 23:28 ` [PATCH 02/23] Don't rely on inferior_ptid in record_full_wait Pedro Alves
2020-07-31  3:17   ` Tom Tromey
2020-08-01 16:14     ` Simon Marchi
2020-08-01 19:32       ` John Baldwin
2020-08-01 20:47         ` Tom Tromey
2020-08-01 20:46       ` Tom Tromey
2020-08-01 22:56         ` Simon Marchi
2020-08-02 17:52           ` Tom Tromey
2020-08-03  0:08             ` Simon Marchi
2019-09-06 23:28 ` [PATCH 13/23] Delete exit_inferior_silent(int pid) Pedro Alves
2019-09-06 23:28 ` [PATCH 15/23] Fix reconnecting to a gdbserver already debugging multiple processes, I Pedro Alves
2019-09-06 23:28 ` [PATCH 10/23] Some get_last_target_status tweaks Pedro Alves
2019-09-09 18:53   ` Tom Tromey
2019-10-17  1:14     ` Pedro Alves
2019-09-06 23:28 ` Pedro Alves [this message]
2019-10-09  9:36   ` [PATCH 01/23] Preserve selected thread in all-stop w/ background execution Aktemur, Tankut Baris
2019-10-16 23:54     ` [PATCH v1.1 " Pedro Alves
2019-10-17 10:21       ` Aktemur, Tankut Baris
2019-09-06 23:33 ` [PATCH 23/23] Multi-target: NEWS and user manual Pedro Alves
2019-09-07  6:33   ` Eli Zaretskii
2019-10-17  2:08     ` Pedro Alves
2019-10-17  7:55       ` Eli Zaretskii
2019-10-17  2:42     ` Pedro Alves
2019-10-17  8:14       ` Eli Zaretskii
2019-10-17 15:31         ` Pedro Alves
2019-09-06 23:34 ` [PATCH 04/23] exceptions.c:print_flush: Remove obsolete check Pedro Alves
2019-09-09 18:07   ` Tom Tromey
2019-09-06 23:35 ` [PATCH 05/23] Make target_ops::has_execution take an 'inferior *' instead of a ptid_t Pedro Alves
2019-09-09 18:12   ` Tom Tromey
2019-09-06 23:36 ` [PATCH 07/23] Delete unnecessary code from kill_command Pedro Alves
2019-10-01 10:19   ` Aktemur, Tankut Baris
2019-10-01 13:28     ` Aktemur, Tankut Baris
2019-09-06 23:36 ` [PATCH 14/23] Tweak handling of remote errors in response to resumption packet Pedro Alves
2019-10-09 13:35   ` Aktemur, Tankut Baris
2019-10-17  0:54     ` [PATCH 14.5/23] Avoid another inferior_ptid reference in gdb/remote.c (Re: [PATCH 14/23] Tweak handling of remote errors in response to resumption packet) Pedro Alves
2019-09-06 23:36 ` [PATCH 12/23] Use all_non_exited_inferiors in infrun.c Pedro Alves
2019-09-06 23:37 ` [PATCH 21/23] Add "info connections" command, "info inferiors" connection number/string Pedro Alves
2019-09-09 20:18   ` Tom Tromey
2019-10-17  2:21     ` Pedro Alves
2019-10-17 14:23       ` Tom Tromey
2019-09-06 23:37 ` [PATCH 22/23] Require always-non-stop for multi-target resumptions Pedro Alves
2019-09-07 11:19 ` [PATCH 00/23] Multi-target support Philippe Waroquiers
2019-09-08 20:06   ` Pedro Alves
2019-09-08 20:50     ` Philippe Waroquiers
2019-10-16 19:08       ` Pedro Alves
2019-10-16 19:14       ` [PATCH] Avoid inferior_ptid reference in gdb/remote.c (Re: [PATCH 00/23] Multi-target support) Pedro Alves
2019-09-09 19:09 ` [PATCH 00/23] Multi-target support Tom Tromey
2019-09-09 20:22 ` Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190906232807.6191-2-palves@redhat.com \
    --to=palves@redhat.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox