public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCHv3 0/3] Restore thread and frame patches
@ 2020-09-25 22:24 Andrew Burgess
  2020-09-25 22:24 ` [PATCHv3 1/3] gdb: unify two copies of restore_selected_frame Andrew Burgess
                   ` (3 more replies)
  0 siblings, 4 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-09-25 22:24 UTC (permalink / raw)
  To: gdb-patches

This is a third attempt to get some, or maybe all of this work merged.
Or to get some idea on what might be seen as an acceptable direction
to take this work.

I originally posted this here (v2):

  https://sourceware.org/pipermail/gdb-patches/2020-April/167984.html

and (v1):

  https://sourceware.org/pipermail/gdb-patches/2020-February/166202.html
  https://sourceware.org/pipermail/gdb-patches/2020-April/167215.html

Changes since V2:

 - Rebase to current master.

 - Fixed minor coding style issues, and improved a comment as pointed
   out by Baris.

 - Reordered the patches so #2 is now #1.  I think that current #1
   which is really a restructure might be worth merging even if #2 and
   #3 never get merged, hence placing it first.

 - A few minor tweaks to take acount of general code changes since v2.

There was some initial positive feedback on some of the v1 patches,
but Pedro was not convinced:

  https://sourceware.org/pipermail/gdb-patches/2020-April/167223.html

I'll quote his feedback here, and reply to it inline:

> Frankly, I'm not really sure I like this.  It seems like a can of worms to
> me...  It's going to cause us to have to decide whether it's a good idea to
> save/restore all kinds of state in the objects hierarchy.  E.g., if this is
> reasonable, then it would also be reasonable to restore the selected
> Ada task.  And frames.  And maybe the selected source file and line for
> list.  Once we gain support for fibers, coroutines, etc. we'll
> then need to apply the same logic.  Etc.  And then maybe we'll need
> some way to query the selected thread of a given inferior.  Etc.

This feedback was on v1 of the patch where I changed the default
behaviour to be restore thread/frame, since v2 the default is to
maintain GDB's current behaviour and have the restore be a feature a
user must turn on.

I think this addresses the concern Pedro raised as we no longer have
an inconsistent position, some things are restored while others are
not, instead we have a switch to allow somethings to be restored while
we lack a switch to allow other things to be restored.

While I agree with Pedro's original feedback that a user might be
confused, "why is XXX restored, but not YYY", now they will simply be
left wondering, "Why is there no switch to restore YYY?".  Though they
might wonder why the switch doesn't exist, and might be disapointed
even, I don't think it will leave them confused with the actual
behaviour of GDB.

Further, though "restoring stuff" as a broad category clearly covers
all the things Pedro mentions, restoring of each thing will require
its own piece of work.  I don't think we should prevent merging this
just because there might be some other (similar, but unrelated) part
of GDB that could also be saved and restored.

> 
> The current rule is quite simple.  If you select a object then its container
> is implicitly selected.  So if you select a thread, you implicitly select
> its inferior, and implicitly select its target.

OK, but that's looking upward, a frame is in a thread, a thread is in
an inferior, an inferior is in a target.  These changes are about lookig down.

>                                                   And the first/initial container
> object is selected.

I'm confused!  Above you talk about containers as looking upward
thread -> inferior -> target, but I think you're now talking about
things as looking down, in which case talking about containers seems
like poor terminology.

When we switch to an inferior then its containing target is
automatically selected, but there's only one of those to select.

>                        It seems very natural to me that "inferior 2" ends up
> selecting the initial thread of inferior 2.  I.e., normally, thread 2.1.

I'd certainly never want to suggest you're wrong for preferring a
particular behaviour.  For me though the choice of the first thread
seems pretty arbitrary, instead the idea of restoring the selected
object within a container seems more consistent.

However, I feel I've addressed this concern by making both of the
save/restore features being off by default.

Thoughts, or feedback on any or all of these patches is always
welcome.

Thanks,
Andrew

---

Andrew Burgess (3):
  gdb: unify two copies of restore_selected_frame
  gdb: Restore previously selected thread when switching inferior
  gdb: Track the current frame for each thread

 gdb/ChangeLog                                 |  53 +++
 gdb/NEWS                                      |  21 ++
 gdb/doc/ChangeLog                             |  14 +
 gdb/doc/gdb.texinfo                           |  42 ++-
 gdb/frame.c                                   |  84 +++++
 gdb/frame.h                                   |  85 +++++
 gdb/gdbthread.h                               |  15 +-
 gdb/inferior.c                                |  58 ++-
 gdb/inferior.h                                |  10 +
 gdb/infrun.c                                  |  25 +-
 gdb/testsuite/ChangeLog                       |  10 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
 gdb/thread.c                                  | 128 +++----
 16 files changed, 1331 insertions(+), 102 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv3 1/3] gdb: unify two copies of restore_selected_frame
  2020-09-25 22:24 [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
@ 2020-09-25 22:24 ` Andrew Burgess
  2020-09-25 22:24 ` [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior Andrew Burgess
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-09-25 22:24 UTC (permalink / raw)
  To: gdb-patches

GDB currently has two copies of restore_selected_frame, this commit
unifies these two, and moves the implementation into frame.c.

The only possible user visible change after this commit is if GDB is
unable to restore the currently selected stack after performing an
inferior call.

Previously, in any situation, if GDB failed to find a frame with a
matching frame id, then the following warning would be issued, and GDB
would be left with the innermost stack frame selected:

  Unable to restore previously selected frame.

After this commit, then GDB will give a slightly different warning:

  Couldn't restore frame #%d in current thread.  Bottom (innermost) frame selected:
  ....

Where '....' is a summary of the frame that was selected, and '%d' is
the frame level GDB was looking for.  Additionally, this warning is
not given if the previously selected frame was at level 0, in that
case GDB will just silently selected the innermost frame.

If this difference is a problem then it should be easy enough to have
restore_selected_frame take an 'always-warn' flag parameter, but I
haven't done that in this commit.

Beyond this there should be no other user visible changes with this
commit.

gdb/ChangeLog:

	* frame.c (restore_selected_frame): Moved from thread.c.
	* frame.h (restore_selected_frame): Declare.
	(struct frame_id_and_level): New struct.
	* gdbthread.h (scoped_restore_current_thread)
	<m_selected_frame_id>: Delete.  <m_selected_frame_level>: Delete.
	<m_selected_frame_info>: New member variable.
	* infrun.c (infcall_control_state) <selected_frame_id>: Delete.
	<selected_frame_info>: New member variable.
	(save_infcall_control_state): Update to use selected_frame_info.
	(restore_selected_frame): Delete.
	(restore_infcall_control_state): Update to use selected_frame_info.
	* thread.c (restore_selected_frame): Delete.
	(scoped_restore_current_thread::restore): Update for changes to
	member variables.
	(scoped_restore_current_thread::scoped_restore_current_thread):
	Likewise.
---
 gdb/ChangeLog   | 20 ++++++++++++
 gdb/frame.c     | 58 +++++++++++++++++++++++++++++++++
 gdb/frame.h     | 85 +++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/gdbthread.h |  3 +-
 gdb/infrun.c    | 25 +++------------
 gdb/thread.c    | 67 ++------------------------------------
 6 files changed, 171 insertions(+), 87 deletions(-)

diff --git a/gdb/frame.c b/gdb/frame.c
index 0b708e66827..eba383f9877 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2972,6 +2972,64 @@ frame_prepare_for_sniffer (struct frame_info *frame,
   frame->unwind = unwind;
 }
 
+/* See frame.h.  */
+
+void
+restore_selected_frame (frame_id a_frame_id, int frame_level)
+{
+  /* This means there was no selected frame.  */
+  if (frame_level == -1)
+    {
+      select_frame (NULL);
+      return;
+    }
+
+  gdb_assert (frame_level >= 0);
+
+  /* Restore by level first, check if the frame id is the same as
+     expected.  If that fails, try restoring by frame id.  If that
+     fails, nothing to do, just warn the user.  */
+
+  int count = frame_level;
+  frame_info *frame = find_relative_frame (get_current_frame (), &count);
+  if (count == 0
+      && frame != NULL
+      /* The frame ids must match - either both valid or both outer_frame_id.
+	 The latter case is not failsafe, but since it's highly unlikely
+	 the search by level finds the wrong frame, it's 99.9(9)% of
+	 the time (for all practical purposes) safe.  */
+      && frame_id_eq (get_frame_id (frame), a_frame_id))
+    {
+      /* Cool, all is fine.  */
+      select_frame (frame);
+      return;
+    }
+
+  frame = frame_find_by_id (a_frame_id);
+  if (frame != NULL)
+    {
+      /* Cool, refound it.  */
+      select_frame (frame);
+      return;
+    }
+
+  /* Nothing else to do, the frame layout really changed.  Select the
+     innermost stack frame.  */
+  select_frame (get_current_frame ());
+
+  /* Warn the user.  */
+  if (frame_level > 0 && !current_uiout->is_mi_like_p ())
+    {
+      warning (_("Couldn't restore frame #%d in "
+		 "current thread.  Bottom (innermost) frame selected:"),
+	       frame_level);
+      /* For MI, we should probably have a notification about
+	 current frame change.  But this error is not very
+	 likely, so don't bother for now.  */
+      print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
+    }
+}
+
 static struct cmd_list_element *set_backtrace_cmdlist;
 static struct cmd_list_element *show_backtrace_cmdlist;
 
diff --git a/gdb/frame.h b/gdb/frame.h
index 3ceb7b32eff..0e668f00be8 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -960,5 +960,90 @@ extern void set_frame_previous_pc_masked (struct frame_info *frame);
 
 extern bool get_frame_pc_masked (const struct frame_info *frame);
 
+/* When GDB needs to backup and then later restore the currently selected
+   frame this is done by storing the frame id, and then looking up a frame
+   with that stored frame id.
+
+   However, if the previously selected frame can't be restored then GDB
+   should give the user a warning in most cases.  If the previously
+   selected frame was level 0 then GDB will just reselect the innermost
+   frame silently without a warning.
+
+   And so, when we backup and restore the currently selected frame we need
+   to track both the frame id, and the frame level, so GDB knows if a
+   warning should be given or not.  */
+
+struct frame_id_and_level
+{
+  /* Setup this structure to track FI as the previously selected frame.
+     Any errors thrown while collecting the frame_id or the frame level
+     are rethrown from this function.  */
+
+  void reset (struct frame_info *fi)
+  {
+    try
+      {
+	m_id = get_frame_id (fi);
+	m_level = frame_relative_level (fi);
+      }
+    catch (const gdb_exception_error &ex)
+      {
+	/* Place the object into a known state.  */
+	m_id = null_frame_id;
+	m_level = -1;
+
+	/* And rethrow the exception.  */
+	throw;
+      }
+  }
+
+  /* Reset this structure to indicate there was no previously selected
+     frame.  */
+  void reset ()
+  {
+    m_id = null_frame_id;
+    m_level = -1;
+  }
+
+  /* The frame id of the previously selected frame.  This value is only
+     defined when LEVEL() is greater than -1.  */
+  frame_id id () const
+  {
+    return m_id;
+  }
+
+  /* The level of the previously selected frame, or -1 if no frame was
+     previously selected.  */
+  int level () const
+  {
+    return m_level;
+  }
+
+private:
+  /* The frame id.  */
+  frame_id m_id;
+
+  /* The level at which ID was found.  Set to -1 to indicate that this
+     structure is uninitialised.  */
+  int m_level = -1;
+};
+
+/* Try to find a previously selected frame described by A_FRAME_ID and
+   select it.  If no matching frame can be found then the innermost frame
+   will be selected and a warning printed.  FRAME_LEVEL is the level at
+   which A_FRAME_ID was previously found, and can be -1 to indicate no
+   frame was previously selected, in which case the innermost frame will
+   be selected (without a warning).  */
+
+extern void restore_selected_frame (struct frame_id a_frame_id, int frame_level);
+
+/* A wrapper that unpacks a frame_id_and_level and calls the version of
+   restore_selected_frame above.  */
+
+inline void
+restore_selected_frame (const frame_id_and_level &fal)
+{
+  restore_selected_frame (fal.id (), fal.level ());
+}
 
 #endif /* !defined (FRAME_H)  */
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index ab5771fdb47..a814227b378 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -670,8 +670,7 @@ class scoped_restore_current_thread
   thread_info_ref m_thread;
   inferior_ref m_inf;
 
-  frame_id m_selected_frame_id;
-  int m_selected_frame_level;
+  frame_id_and_level m_selected_frame_info;
   bool m_was_stopped;
 };
 
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 6532b06ae52..b1069ea7f76 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -9002,8 +9002,9 @@ struct infcall_control_state
   enum stop_stack_kind stop_stack_dummy = STOP_NONE;
   int stopped_by_random_signal = 0;
 
-  /* ID if the selected frame when the inferior function call was made.  */
-  struct frame_id selected_frame_id {};
+  /* ID and level of the selected frame when the inferior function call was
+     made.  */
+  struct frame_id_and_level selected_frame_info;
 };
 
 /* Save all of the information associated with the inferior<==>gdb
@@ -9032,27 +9033,11 @@ save_infcall_control_state ()
   inf_status->stop_stack_dummy = stop_stack_dummy;
   inf_status->stopped_by_random_signal = stopped_by_random_signal;
 
-  inf_status->selected_frame_id = get_frame_id (get_selected_frame (NULL));
+  inf_status->selected_frame_info.reset (get_selected_frame (NULL));
 
   return inf_status;
 }
 
-static void
-restore_selected_frame (const frame_id &fid)
-{
-  frame_info *frame = frame_find_by_id (fid);
-
-  /* If inf_status->selected_frame_id is NULL, there was no previously
-     selected frame.  */
-  if (frame == NULL)
-    {
-      warning (_("Unable to restore previously selected frame."));
-      return;
-    }
-
-  select_frame (frame);
-}
-
 /* Restore inferior session state to INF_STATUS.  */
 
 void
@@ -9085,7 +9070,7 @@ restore_infcall_control_state (struct infcall_control_state *inf_status)
          error() trying to dereference it.  */
       try
 	{
-	  restore_selected_frame (inf_status->selected_frame_id);
+	  restore_selected_frame (inf_status->selected_frame_info);
 	}
       catch (const gdb_exception_error &ex)
 	{
diff --git a/gdb/thread.c b/gdb/thread.c
index 0217f3b54f7..9124fbf56d2 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1325,65 +1325,6 @@ switch_to_thread (process_stratum_target *proc_target, ptid_t ptid)
   switch_to_thread (thr);
 }
 
-static void
-restore_selected_frame (struct frame_id a_frame_id, int frame_level)
-{
-  struct frame_info *frame = NULL;
-  int count;
-
-  /* This means there was no selected frame.  */
-  if (frame_level == -1)
-    {
-      select_frame (NULL);
-      return;
-    }
-
-  gdb_assert (frame_level >= 0);
-
-  /* Restore by level first, check if the frame id is the same as
-     expected.  If that fails, try restoring by frame id.  If that
-     fails, nothing to do, just warn the user.  */
-
-  count = frame_level;
-  frame = find_relative_frame (get_current_frame (), &count);
-  if (count == 0
-      && frame != NULL
-      /* The frame ids must match - either both valid or both outer_frame_id.
-	 The latter case is not failsafe, but since it's highly unlikely
-	 the search by level finds the wrong frame, it's 99.9(9)% of
-	 the time (for all practical purposes) safe.  */
-      && frame_id_eq (get_frame_id (frame), a_frame_id))
-    {
-      /* Cool, all is fine.  */
-      select_frame (frame);
-      return;
-    }
-
-  frame = frame_find_by_id (a_frame_id);
-  if (frame != NULL)
-    {
-      /* Cool, refound it.  */
-      select_frame (frame);
-      return;
-    }
-
-  /* Nothing else to do, the frame layout really changed.  Select the
-     innermost stack frame.  */
-  select_frame (get_current_frame ());
-
-  /* Warn the user.  */
-  if (frame_level > 0 && !current_uiout->is_mi_like_p ())
-    {
-      warning (_("Couldn't restore frame #%d in "
-		 "current thread.  Bottom (innermost) frame selected:"),
-	       frame_level);
-      /* For MI, we should probably have a notification about
-	 current frame change.  But this error is not very
-	 likely, so don't bother for now.  */
-      print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
-    }
-}
-
 void
 scoped_restore_current_thread::restore ()
 {
@@ -1408,7 +1349,7 @@ scoped_restore_current_thread::restore ()
       && target_has_registers
       && target_has_stack
       && target_has_memory)
-    restore_selected_frame (m_selected_frame_id, m_selected_frame_level);
+    restore_selected_frame (m_selected_frame_info);
 }
 
 scoped_restore_current_thread::~scoped_restore_current_thread ()
@@ -1455,14 +1396,10 @@ scoped_restore_current_thread::scoped_restore_current_thread ()
 
       try
 	{
-	  m_selected_frame_id = get_frame_id (frame);
-	  m_selected_frame_level = frame_relative_level (frame);
+	  m_selected_frame_info.reset (frame);
 	}
       catch (const gdb_exception_error &ex)
 	{
-	  m_selected_frame_id = null_frame_id;
-	  m_selected_frame_level = -1;
-
 	  /* Better let this propagate.  */
 	  if (ex.error == TARGET_CLOSE_ERROR)
 	    throw;
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior
  2020-09-25 22:24 [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
  2020-09-25 22:24 ` [PATCHv3 1/3] gdb: unify two copies of restore_selected_frame Andrew Burgess
@ 2020-09-25 22:24 ` Andrew Burgess
  2020-09-26  6:09   ` Eli Zaretskii
  2020-09-25 22:24 ` [PATCHv3 3/3] gdb: Track the current frame for each thread Andrew Burgess
  2020-10-08  9:59 ` [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
  3 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-09-25 22:24 UTC (permalink / raw)
  To: gdb-patches

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I did consider, but eventually didn't implemented, adding a warning
when switching inferiors if the previously selected thread is no
longer available.  My reasoning here is that GDB should already have
informed the user that the thread has exited, and there is already a
message indicating which thread has been switched too, so adding an
extra warning felt like unneeded clutter.

In order to store the thread within the inferior I store a pointer to
the thread_info object of the previously selected thread.  When
fetching the thread_info it is important that we do actually have a
current thread otherwise this happens:

  $ gdb
  (gdb) add-inferior
  (gdb) inferior 2
  ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.

To avoid this I added a check that inferior_ptid is not null_ptid.
Though it is not always the case, there are plenty of places in GDB
where a call to inferior_thread () is guarded by such a check.

There's a new test for this functionality.

gdb/ChangeLog:

	* inferior.c (inferior_command): Store current thread_info before
	switching inferiors.  Reselect the previous thread_info if
	possible after switching to the new inferior.
	(initialize_inferiors): Register restore-selected-thread option.
	* inferior.h (class inferior) <previous_thread_info>: New member
	variable.
	* NEWS: Mention new feature.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
---
 gdb/ChangeLog                                |  10 +
 gdb/NEWS                                     |  11 +
 gdb/doc/ChangeLog                            |   7 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/inferior.c                               |  58 ++++-
 gdb/inferior.h                               |  10 +
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 9 files changed, 585 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index f30d7183312..79f0e5ab888 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -15,6 +15,17 @@
 
 * GDB now supports core file debugging for x86_64 Cygwin programs.
 
+* New commands
+
+set restore-selected-thread on|off
+show restore-selected-thread
+  This new option is off by default.  When turned on GDB will record
+  the currently selected thread in each inferior.  When switching
+  between inferiors GDB will attempt to restore the previously
+  selected thread in the inferior being switched too.  If the
+  previously selected thread is no longer available then GDB falls
+  back to selecting the first non-exited thread.
+
 *** Changes in GDB 10
 
 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core"
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 8bff27c940d..bc73b78b526 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3195,11 +3195,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads
+(@pxref{Threads}) @value{GDBN} will select the first non-exited thread
+in the inferior being switched to and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferior @value{GDBN}
+will try to restore the previously selected thread in the inferior
+being switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3581,7 +3595,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command} for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/inferior.c b/gdb/inferior.c
index f775938721d..97191d0238f 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -627,6 +627,23 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* When this is true GDB restores the inferiors previously selected thread
+   each time the inferior is changed (where possible).  */
+
+static bool restore_selected_thread_per_inferior = false;
+
+/* Implement 'show restore-selected-thread'.  */
+
+static void
+show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
+					   struct cmd_list_element *c,
+					   const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected thread is currently %s.\n"),
+		    value);
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
@@ -639,11 +656,38 @@ inferior_command (const char *args, int from_tty)
   if (inf == NULL)
     error (_("Inferior ID %d not known."), num);
 
+  /* We can only call INFERIOR_THREAD if the inferior is known to have an
+     active thread, which it wont if the inferior is currently exited.  So,
+     first check if we currently have a thread selected.  */
+  if (inferior_ptid != null_ptid)
+    {
+      /* Now take a strong reference to the current thread_info and store
+	 it within the inferior, this prevents the thread_info from being
+	 deleted until the inferior has released the reference.  */
+      thread_info *tp = inferior_thread ();
+      tp->incref ();
+      current_inferior ()->previous_thread_info.reset (tp);
+    }
+
   if (inf->pid != 0)
     {
       if (inf != current_inferior ())
 	{
-	  thread_info *tp = any_thread_of_inferior (inf);
+	  thread_info *tp = nullptr;
+
+	  if (restore_selected_thread_per_inferior
+	      && inf->previous_thread_info != nullptr)
+	    {
+	      /* Release the reference to the previous thread.  We don't
+		 switch back to this thread if it is already exited
+		 though.  */
+	      tp = inf->previous_thread_info.release ();
+	      tp->decref ();
+	      if (tp->state == THREAD_EXITED)
+		tp = nullptr;
+	    }
+	  if (tp == nullptr)
+	    tp = any_thread_of_inferior (inf);
 	  if (tp == NULL)
 	    error (_("Inferior has no threads."));
 
@@ -1021,5 +1065,17 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
          show_print_inferior_events,
          &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-thread",
+			   no_class, &restore_selected_thread_per_inferior,
+                          _("\
+Set whether GDB restores the selected thread when switching inferiors."), _("\
+Show whether GDB restores the selected thread when switching inferiors."), _("\
+When this option is on GDB will record the currently selected thread for\n\
+each inferior, and restore the selected thread whenever GDB switches inferiors."),
+                          nullptr,
+                          show_restore_selected_thread_per_inferior,
+                          &setlist,
+                          &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 606cece6c0b..d8ab2412906 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -543,6 +543,16 @@ class inferior : public refcounted_object
   /* Data related to displaced stepping.  */
   displaced_step_inferior_state displaced_step_state;
 
+  /* This field is updated when GDB switches away from this inferior to
+     some other inferior, a reference to a thread_info is stored in here,
+     the ref count for the thread_info should be non-zero to prevent the
+     thread_info being deleted.
+
+     When the user switches back to this inferior the thread_info is taken
+     out of this reference and used to (possibly) switch back to this
+     thread.  */
+  thread_info_ref previous_thread_info;
+
   /* Per inferior data-pointers required by other GDB modules.  */
   REGISTRY_FIELDS;
 
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv3 3/3] gdb: Track the current frame for each thread
  2020-09-25 22:24 [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
  2020-09-25 22:24 ` [PATCHv3 1/3] gdb: unify two copies of restore_selected_frame Andrew Burgess
  2020-09-25 22:24 ` [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2020-09-25 22:24 ` Andrew Burgess
  2020-09-26  6:16   ` Eli Zaretskii
  2020-10-08  9:59 ` [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
  3 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-09-25 22:24 UTC (permalink / raw)
  To: gdb-patches

Currently in GDB, each time a user switches between threads, the inner
most frame of the thread being switched to is selected.  In some
situations however, it might be helpful for a user to have GDB
remember which frame was selected in each thread, and restore this
frame as the user switches between threads.

This commit the following two commands:

  set restore-selected-frame on|off
  show restore-selected-frame

This new option is off by default, so the default behaviour of GDB is
unchanged.

However, with this option turned on GDB will remember, and restore the
selected frame for each thread.

My initial motivation for this change was to have the thread restored
when switching threads with 'thread <num>', however, as I started to
work on this feature I realised that there were a couple of other
places where the sticky frame would naturally appear.  These are 'info
threads' and 'thread apply all'.

With 'info threads' the output contains a 'Frame' column.  Previously,
this was always the innermost frame, the info threads output was
created by switching to each thread in turn and collecting information
about the thread, this naturally placed us at the innermost frame.
Now, the 'Frame' column displays the _selected_ frame for each
thread.

I struggled to decide if this change was good or not.  In the end I
felt that having 'info threads' display the selected frame would feel
more natural, that's the frame you'll end up in if you switch to that
thread, so if seemed to make sense.  However, it would be easy enough
to force the old behaviour if people would prefer.  Alternatively I
could even investigate adding a switch to 'info threads' that allows
the user to select displaying either the selected frame, or the inner
most frame.

For 'thread apply all', again, we used to always apply to the
innermost frame.  Now it's possible for a user to adjust which frame
will be current when the 'thread apply all' runs - this feels like a
useful change to me.  It's easy enough to quickly restore the inner
most frame if required ('thread apply all -- frame 0') and having the
flexibility to tweak the selected frame in just some threads feels
like a nice advantage.  Again, I could potentially add a command flag
here to force the inner most frame.

gdb/ChangeLog:

	* NEWS: Describe new feature.
	* frame.c (cache_selected_frame_on_thread): New function.
	(select_frame): Call new function.
	* gdbthread.h (class thread_info) <selected_frame_info>: New
	member variable.
	(switch_to_thread): Extra parameter.
	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
	switch_to_thread.
	(scoped_restore_current_thread::restore): Restore the frame either
	from the thread, or from the local object.
	(set_executing_thread): Reset the currently selected frame.
	(restore_selected_frame_per_thread): New file level static variable.
	(show_restore_selected_frame_per_thread): New function.
	(print_thread_info_1): Pass extra parameter to switch_to_thread.
	(switch_to_thread): Take extra parameter, restore the previous
	frame if appropriate.
	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
	(thread_apply_command): Likewise.
	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
	(_initialize_thread): Add new set/show variable.

gdb/doc/ChangeLog:

	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
	the Frame column of 'info threads' more.  Describe which frame is
	selected when switching threads, and document the new option for
	restoring the previously selected frame.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-selected-frame.c: New file.
	* gdb.threads/restore-selected-frame.exp: New file.
---
 gdb/ChangeLog                                 |  23 ++
 gdb/NEWS                                      |  10 +
 gdb/doc/ChangeLog                             |   7 +
 gdb/doc/gdb.texinfo                           |  23 +-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  12 +-
 gdb/testsuite/ChangeLog                       |   5 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/thread.c                                  |  61 +++-
 10 files changed, 575 insertions(+), 13 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 79f0e5ab888..388b913671d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -26,6 +26,16 @@ show restore-selected-thread
   previously selected thread is no longer available then GDB falls
   back to selecting the first non-exited thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+  This option is off by default.  When turned on GDB will record the
+  currently selected frame in each thread.  When switching between
+  threads GDB will attempt to restore the previously selected frame in
+  the thread being switched too.  Executing a thread will cause the
+  GDB to discard any previously selected frame (GDB will select the
+  inner most frame the next time the thread stops).  The 'info
+  threads' command will show the selected frame in its 'frame' field.
+
 *** Changes in GDB 10
 
 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core"
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index bc73b78b526..dba69f24254 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3546,6 +3546,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3573,7 +3574,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the inner
+most frame for the thread, see @ref{set restore-selected-frame} to
+display the threads selected frame instead.
 @end enumerate
 
 @noindent
@@ -3645,6 +3648,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads @value{GDBN} will select the inner most
+frame in the thread being switched too, see @ref{set
+restore-selected-frame} to change this behaviour.
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running then when it stops the previously
+selected frame is discarded, and the inner most frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index eba383f9877..a19bf979ad8 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1756,12 +1756,38 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* We only record the selected frame if the level is greater than 0,
+	 this avoids having to calculate the frame id when selecting the
+	 innermost frame.  When the cached selected frame is cleared then
+	 we select the innermost frame anyway, so calculating the frame id
+	 for frame #0 adds no value.  */
+      if (frame_relative_level (fi) > 0)
+	tp->selected_frame_info.reset (fi);
+      else
+	tp->selected_frame_info.reset ();
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the current frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
+
   /* NOTE: cagney/2002-05-04: FI can be NULL.  This occurs when the
      frame is being invalidated.  */
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index a814227b378..918d6a4fecc 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -369,6 +369,11 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id_and_level selected_frame_info;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -571,8 +576,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 9124fbf56d2..6c0478128e9 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -665,7 +665,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -675,7 +675,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -847,7 +847,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_info.reset ();
+    }
 }
 
 void
@@ -1010,6 +1013,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true GDB restore the threads previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1122,7 +1142,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1304,7 +1325,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1314,6 +1335,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_info.level () > -1)
+    restore_selected_frame (thr->selected_frame_info.id (),
+			    thr->selected_frame_info.level ());
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1337,13 +1362,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers
@@ -1612,7 +1640,7 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info *thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr))
+	if (switch_to_thread_if_alive (thr, restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr, cmd, from_tty, flags);
     }
 }
@@ -1769,7 +1797,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1943,7 +1971,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2210,6 +2238,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior
  2020-09-25 22:24 ` [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2020-09-26  6:09   ` Eli Zaretskii
  0 siblings, 0 replies; 28+ messages in thread
From: Eli Zaretskii @ 2020-09-26  6:09 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <andrew.burgess@embecosm.com>
> Date: Fri, 25 Sep 2020 23:24:08 +0100
> 
> This commit adds a new option that allows the user to control how GDB
> behaves when switching between multi-threaded inferiors.

Thanks.  Some commas are missing, see below.

> +set restore-selected-thread on|off
> +show restore-selected-thread
> +  This new option is off by default.

This sentence is redundant in NEWS, and IMO should be removed.

> +                                      When turned on GDB will record
                                                       ^
Please add a comma there.

> +  the currently selected thread in each inferior.  When switching
> +  between inferiors GDB will attempt to restore the previously
                      ^
And here.

> +  selected thread in the inferior being switched too.  If the
> +  previously selected thread is no longer available then GDB falls
                                                      ^
And there.

> +When switching between inferiors with multiple threads
> +(@pxref{Threads}) @value{GDBN} will select the first non-exited thread
                    ^
Likewise.

> +in the inferior being switched to and make this the current thread.
                                    ^
Same.

> +When this option is on @value{GDBN} will record the currently selected
                         ^
Here as well.

> +thread in each inferior.  When switching between inferior @value{GDBN}
                                                            ^
And here.  Also, I think you meant "between inferiors", plural.

> -Otherwise, only @var{thread-num} is shown.
> +Otherwise, only @var{thread-num} is shown.  When switching between
> +inferiors @value{GDBN} will select a suitable thread in the inferior
            ^
Please add a comma here.

> +being switched to, see @ref{inferior command,,the @code{inferior}
> +command} for further details on how to control this behaviour.
           ^
And here.

The documentation parts are okay with these fixed.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv3 3/3] gdb: Track the current frame for each thread
  2020-09-25 22:24 ` [PATCHv3 3/3] gdb: Track the current frame for each thread Andrew Burgess
@ 2020-09-26  6:16   ` Eli Zaretskii
  2020-09-26  8:31     ` Andreas Schwab
  0 siblings, 1 reply; 28+ messages in thread
From: Eli Zaretskii @ 2020-09-26  6:16 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <andrew.burgess@embecosm.com>
> Date: Fri, 25 Sep 2020 23:24:09 +0100
> 
> +set restore-selected-frame [on|off]
> +show restore-selected-frame
> +  This option is off by default.  When turned on GDB will record the
                                                   ^
Comma here, please.

> +  currently selected frame in each thread.  When switching between
> +  threads GDB will attempt to restore the previously selected frame in
            ^
And here.

> +  the thread being switched too.  Executing a thread will cause the
> +  GDB to discard any previously selected frame (GDB will select the

"the GDB" sounds awkward; please remove "the".

>  @item
> -the current stack frame summary for that thread
> +the current stack frame summary for that thread, this is the inner
> +most frame for the thread, see @ref{set restore-selected-frame} to

Here, it is best to use @pxref, like this:

  most frame for the thread (@pxref{set restore-selected-frame}) to

> +When switching between threads @value{GDBN} will select the inner most
                                 ^
Comma.

> +frame in the thread being switched too, see @ref{set
> +restore-selected-frame} to change this behaviour.

@pxref again.

> +If a thread has been running then when it stops the previously
                               ^                  ^
Two commas missing.

> +  add_setshow_boolean_cmd ("restore-selected-frame",
> +			   class_stack, &restore_selected_frame_per_thread,
> +			   _("\
> +Set whether GDB restores the selected frame when switching threads."), _("\
> +Show whether GDB restores the selected frame when switching threads."), _("\
> +When this option is on GDB will record the currently selected frame for\n\
                         ^
A comma is missing.

Documentation parts are OK with those nits fixed.  Thanks.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv3 3/3] gdb: Track the current frame for each thread
  2020-09-26  6:16   ` Eli Zaretskii
@ 2020-09-26  8:31     ` Andreas Schwab
  2020-09-26  8:54       ` Eli Zaretskii
  0 siblings, 1 reply; 28+ messages in thread
From: Andreas Schwab @ 2020-09-26  8:31 UTC (permalink / raw)
  To: Eli Zaretskii via Gdb-patches

On Sep 26 2020, Eli Zaretskii via Gdb-patches wrote:

>> From: Andrew Burgess <andrew.burgess@embecosm.com>
>> Date: Fri, 25 Sep 2020 23:24:09 +0100
>> 
>> +set restore-selected-frame [on|off]
>> +show restore-selected-frame
>> +  This option is off by default.  When turned on GDB will record the

Shouldn't GDB be written as @value{GDBN}?

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv3 3/3] gdb: Track the current frame for each thread
  2020-09-26  8:31     ` Andreas Schwab
@ 2020-09-26  8:54       ` Eli Zaretskii
  0 siblings, 0 replies; 28+ messages in thread
From: Eli Zaretskii @ 2020-09-26  8:54 UTC (permalink / raw)
  To: Andreas Schwab; +Cc: gdb-patches, andrew.burgess

> From: Andreas Schwab <schwab@linux-m68k.org>
> Cc: Andrew Burgess <andrew.burgess@embecosm.com>,  Eli Zaretskii <eliz@gnu.org>
> Date: Sat, 26 Sep 2020 10:31:10 +0200
> 
> On Sep 26 2020, Eli Zaretskii via Gdb-patches wrote:
> 
> >> From: Andrew Burgess <andrew.burgess@embecosm.com>
> >> Date: Fri, 25 Sep 2020 23:24:09 +0100
> >> 
> >> +set restore-selected-frame [on|off]
> >> +show restore-selected-frame
> >> +  This option is off by default.  When turned on GDB will record the
> 
> Shouldn't GDB be written as @value{GDBN}?

Not in NEWS, AFAIU.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv3 0/3] Restore thread and frame patches
  2020-09-25 22:24 [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
                   ` (2 preceding siblings ...)
  2020-09-25 22:24 ` [PATCHv3 3/3] gdb: Track the current frame for each thread Andrew Burgess
@ 2020-10-08  9:59 ` Andrew Burgess
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
  3 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-10-08  9:59 UTC (permalink / raw)
  To: gdb-patches

Ping!

Pedro - I do feel I've addressed your initial feedback, I'd love to
hear what your position is on this new version, even if it's just to
confirm you are still opposed to this patch.

Many thanks,
Andrew

* Andrew Burgess <andrew.burgess@embecosm.com> [2020-09-25 23:24:06 +0100]:

> This is a third attempt to get some, or maybe all of this work merged.
> Or to get some idea on what might be seen as an acceptable direction
> to take this work.
> 
> I originally posted this here (v2):
> 
>   https://sourceware.org/pipermail/gdb-patches/2020-April/167984.html
> 
> and (v1):
> 
>   https://sourceware.org/pipermail/gdb-patches/2020-February/166202.html
>   https://sourceware.org/pipermail/gdb-patches/2020-April/167215.html
> 
> Changes since V2:
> 
>  - Rebase to current master.
> 
>  - Fixed minor coding style issues, and improved a comment as pointed
>    out by Baris.
> 
>  - Reordered the patches so #2 is now #1.  I think that current #1
>    which is really a restructure might be worth merging even if #2 and
>    #3 never get merged, hence placing it first.
> 
>  - A few minor tweaks to take acount of general code changes since v2.
> 
> There was some initial positive feedback on some of the v1 patches,
> but Pedro was not convinced:
> 
>   https://sourceware.org/pipermail/gdb-patches/2020-April/167223.html
> 
> I'll quote his feedback here, and reply to it inline:
> 
> > Frankly, I'm not really sure I like this.  It seems like a can of worms to
> > me...  It's going to cause us to have to decide whether it's a good idea to
> > save/restore all kinds of state in the objects hierarchy.  E.g., if this is
> > reasonable, then it would also be reasonable to restore the selected
> > Ada task.  And frames.  And maybe the selected source file and line for
> > list.  Once we gain support for fibers, coroutines, etc. we'll
> > then need to apply the same logic.  Etc.  And then maybe we'll need
> > some way to query the selected thread of a given inferior.  Etc.
> 
> This feedback was on v1 of the patch where I changed the default
> behaviour to be restore thread/frame, since v2 the default is to
> maintain GDB's current behaviour and have the restore be a feature a
> user must turn on.
> 
> I think this addresses the concern Pedro raised as we no longer have
> an inconsistent position, some things are restored while others are
> not, instead we have a switch to allow somethings to be restored while
> we lack a switch to allow other things to be restored.
> 
> While I agree with Pedro's original feedback that a user might be
> confused, "why is XXX restored, but not YYY", now they will simply be
> left wondering, "Why is there no switch to restore YYY?".  Though they
> might wonder why the switch doesn't exist, and might be disapointed
> even, I don't think it will leave them confused with the actual
> behaviour of GDB.
> 
> Further, though "restoring stuff" as a broad category clearly covers
> all the things Pedro mentions, restoring of each thing will require
> its own piece of work.  I don't think we should prevent merging this
> just because there might be some other (similar, but unrelated) part
> of GDB that could also be saved and restored.
> 
> > 
> > The current rule is quite simple.  If you select a object then its container
> > is implicitly selected.  So if you select a thread, you implicitly select
> > its inferior, and implicitly select its target.
> 
> OK, but that's looking upward, a frame is in a thread, a thread is in
> an inferior, an inferior is in a target.  These changes are about lookig down.
> 
> >                                                   And the first/initial container
> > object is selected.
> 
> I'm confused!  Above you talk about containers as looking upward
> thread -> inferior -> target, but I think you're now talking about
> things as looking down, in which case talking about containers seems
> like poor terminology.
> 
> When we switch to an inferior then its containing target is
> automatically selected, but there's only one of those to select.
> 
> >                        It seems very natural to me that "inferior 2" ends up
> > selecting the initial thread of inferior 2.  I.e., normally, thread 2.1.
> 
> I'd certainly never want to suggest you're wrong for preferring a
> particular behaviour.  For me though the choice of the first thread
> seems pretty arbitrary, instead the idea of restoring the selected
> object within a container seems more consistent.
> 
> However, I feel I've addressed this concern by making both of the
> save/restore features being off by default.
> 
> Thoughts, or feedback on any or all of these patches is always
> welcome.
> 
> Thanks,
> Andrew
> 
> ---
> 
> Andrew Burgess (3):
>   gdb: unify two copies of restore_selected_frame
>   gdb: Restore previously selected thread when switching inferior
>   gdb: Track the current frame for each thread
> 
>  gdb/ChangeLog                                 |  53 +++
>  gdb/NEWS                                      |  21 ++
>  gdb/doc/ChangeLog                             |  14 +
>  gdb/doc/gdb.texinfo                           |  42 ++-
>  gdb/frame.c                                   |  84 +++++
>  gdb/frame.h                                   |  85 +++++
>  gdb/gdbthread.h                               |  15 +-
>  gdb/inferior.c                                |  58 ++-
>  gdb/inferior.h                                |  10 +
>  gdb/infrun.c                                  |  25 +-
>  gdb/testsuite/ChangeLog                       |  10 +
>  .../gdb.threads/restore-selected-frame.c      |  85 +++++
>  .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
>  gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
>  gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
>  gdb/thread.c                                  | 128 +++----
>  16 files changed, 1331 insertions(+), 102 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp
> 
> -- 
> 2.25.4
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv4 0/2] Restore thread and frame patches
  2020-10-08  9:59 ` [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
@ 2020-11-06 23:02   ` Andrew Burgess
  2020-11-06 23:02     ` [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
                       ` (4 more replies)
  0 siblings, 5 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-11-06 23:02 UTC (permalink / raw)
  To: gdb-patches

V4 is a rebase of v3 to take account of a recent commit by Pedro.  As
a result I've dropped patch #1 from the v3 series.  The remaining two
patches have a couple of minor updates as a result, but Pedro's recent
commit basically replaced my patch #1.

[ I think the v3 patch #1 did add value that wasn't included in
  Pedro's commit, but I'd rather this series just focus on the
  remaining two patches for now.  I might resurrect the old #1 at a
  later date. ]

No changes to documentation in this iteration.

Retested, with no regressions.

Thanks,
Andrew

---

Andrew Burgess (2):
  gdb: Restore previously selected thread when switching inferior
  gdb: Track the current frame for each thread

 gdb/ChangeLog                                 |  34 ++
 gdb/NEWS                                      |  19 +
 gdb/doc/ChangeLog                             |  14 +
 gdb/doc/gdb.texinfo                           |  42 ++-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/inferior.c                                |  58 ++-
 gdb/inferior.h                                |  10 +
 gdb/testsuite/ChangeLog                       |  10 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
 gdb/thread.c                                  |  61 +++-
 14 files changed, 1160 insertions(+), 15 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
@ 2020-11-06 23:02     ` Andrew Burgess
  2020-11-09 15:02       ` Aktemur, Tankut Baris
  2020-11-06 23:02     ` [PATCHv4 2/2] gdb: Track the current frame for each thread Andrew Burgess
                       ` (3 subsequent siblings)
  4 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-11-06 23:02 UTC (permalink / raw)
  To: gdb-patches

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I did consider, but eventually didn't implemented, adding a warning
when switching inferiors if the previously selected thread is no
longer available.  My reasoning here is that GDB should already have
informed the user that the thread has exited, and there is already a
message indicating which thread has been switched too, so adding an
extra warning felt like unneeded clutter.

In order to store the thread within the inferior I store a pointer to
the thread_info object of the previously selected thread.  When
fetching the thread_info it is important that we do actually have a
current thread otherwise this happens:

  $ gdb
  (gdb) add-inferior
  (gdb) inferior 2
  ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.

To avoid this I added a check that inferior_ptid is not null_ptid.
Though it is not always the case, there are plenty of places in GDB
where a call to inferior_thread () is guarded by such a check.

There's a new test for this functionality.

gdb/ChangeLog:

	* inferior.c (inferior_command): Store current thread_info before
	switching inferiors.  Reselect the previous thread_info if
	possible after switching to the new inferior.
	(initialize_inferiors): Register restore-selected-thread option.
	* inferior.h (class inferior) <previous_thread_info>: New member
	variable.
	* NEWS: Mention new feature.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
---
 gdb/ChangeLog                                |  10 +
 gdb/NEWS                                     |   9 +
 gdb/doc/ChangeLog                            |   7 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/inferior.c                               |  58 ++++-
 gdb/inferior.h                               |  10 +
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 9 files changed, 583 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 3e08aee7c6f..5a900b8a678 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -27,6 +27,15 @@ set debug event-loop
 show debug event-loop
   Control the display of debug output about GDB's event loop.
 
+set restore-selected-thread on|off
+show restore-selected-thread
+  This new option is off by default.  When turned on GDB will record
+  the currently selected thread in each inferior.  When switching
+  between inferiors GDB will attempt to restore the previously
+  selected thread in the inferior being switched too.  If the
+  previously selected thread is no longer available then GDB falls
+  back to selecting the first non-exited thread.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 52701560006..c5819bde7ae 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3247,11 +3247,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads
+(@pxref{Threads}) @value{GDBN} will select the first non-exited thread
+in the inferior being switched to and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferior @value{GDBN}
+will try to restore the previously selected thread in the inferior
+being switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3633,7 +3647,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command} for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/inferior.c b/gdb/inferior.c
index d4a783b3e6d..4961bc467fd 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -631,6 +631,23 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* When this is true GDB restores the inferiors previously selected thread
+   each time the inferior is changed (where possible).  */
+
+static bool restore_selected_thread_per_inferior = false;
+
+/* Implement 'show restore-selected-thread'.  */
+
+static void
+show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
+					   struct cmd_list_element *c,
+					   const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected thread is currently %s.\n"),
+		    value);
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
@@ -643,11 +660,38 @@ inferior_command (const char *args, int from_tty)
   if (inf == NULL)
     error (_("Inferior ID %d not known."), num);
 
+  /* We can only call INFERIOR_THREAD if the inferior is known to have an
+     active thread, which it wont if the inferior is currently exited.  So,
+     first check if we currently have a thread selected.  */
+  if (inferior_ptid != null_ptid)
+    {
+      /* Now take a strong reference to the current thread_info and store
+	 it within the inferior, this prevents the thread_info from being
+	 deleted until the inferior has released the reference.  */
+      thread_info *tp = inferior_thread ();
+      tp->incref ();
+      current_inferior ()->previous_thread_info.reset (tp);
+    }
+
   if (inf->pid != 0)
     {
       if (inf != current_inferior ())
 	{
-	  thread_info *tp = any_thread_of_inferior (inf);
+	  thread_info *tp = nullptr;
+
+	  if (restore_selected_thread_per_inferior
+	      && inf->previous_thread_info != nullptr)
+	    {
+	      /* Release the reference to the previous thread.  We don't
+		 switch back to this thread if it is already exited
+		 though.  */
+	      tp = inf->previous_thread_info.release ();
+	      tp->decref ();
+	      if (tp->state == THREAD_EXITED)
+		tp = nullptr;
+	    }
+	  if (tp == nullptr)
+	    tp = any_thread_of_inferior (inf);
 	  if (tp == NULL)
 	    error (_("Inferior has no threads."));
 
@@ -1025,5 +1069,17 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-thread",
+			   no_class, &restore_selected_thread_per_inferior,
+                          _("\
+Set whether GDB restores the selected thread when switching inferiors."), _("\
+Show whether GDB restores the selected thread when switching inferiors."), _("\
+When this option is on GDB will record the currently selected thread for\n\
+each inferior, and restore the selected thread whenever GDB switches inferiors."),
+                          nullptr,
+                          show_restore_selected_thread_per_inferior,
+                          &setlist,
+                          &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index d016161fb05..517180e48e2 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -543,6 +543,16 @@ class inferior : public refcounted_object
   /* Data related to displaced stepping.  */
   displaced_step_inferior_state displaced_step_state;
 
+  /* This field is updated when GDB switches away from this inferior to
+     some other inferior, a reference to a thread_info is stored in here,
+     the ref count for the thread_info should be non-zero to prevent the
+     thread_info being deleted.
+
+     When the user switches back to this inferior the thread_info is taken
+     out of this reference and used to (possibly) switch back to this
+     thread.  */
+  thread_info_ref previous_thread_info;
+
   /* Per inferior data-pointers required by other GDB modules.  */
   REGISTRY_FIELDS;
 
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv4 2/2] gdb: Track the current frame for each thread
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
  2020-11-06 23:02     ` [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2020-11-06 23:02     ` Andrew Burgess
  2020-11-12 11:59     ` [PATCHv5 0/2] Restore thread and frame patches Andrew Burgess
                       ` (2 subsequent siblings)
  4 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-11-06 23:02 UTC (permalink / raw)
  To: gdb-patches

Currently in GDB, each time a user switches between threads, the inner
most frame of the thread being switched to is selected.  In some
situations however, it might be helpful for a user to have GDB
remember which frame was selected in each thread, and restore this
frame as the user switches between threads.

This commit the following two commands:

  set restore-selected-frame on|off
  show restore-selected-frame

This new option is off by default, so the default behaviour of GDB is
unchanged.

However, with this option turned on GDB will remember, and restore the
selected frame for each thread.

My initial motivation for this change was to have the thread restored
when switching threads with 'thread <num>', however, as I started to
work on this feature I realised that there were a couple of other
places where the sticky frame would naturally appear.  These are 'info
threads' and 'thread apply all'.

With 'info threads' the output contains a 'Frame' column.  Previously,
this was always the innermost frame, the info threads output was
created by switching to each thread in turn and collecting information
about the thread, this naturally placed us at the innermost frame.
Now, the 'Frame' column displays the _selected_ frame for each
thread.

I struggled to decide if this change was good or not.  In the end I
felt that having 'info threads' display the selected frame would feel
more natural, that's the frame you'll end up in if you switch to that
thread, so if seemed to make sense.  However, it would be easy enough
to force the old behaviour if people would prefer.  Alternatively I
could even investigate adding a switch to 'info threads' that allows
the user to select displaying either the selected frame, or the inner
most frame.

For 'thread apply all', again, we used to always apply to the
innermost frame.  Now it's possible for a user to adjust which frame
will be current when the 'thread apply all' runs - this feels like a
useful change to me.  It's easy enough to quickly restore the inner
most frame if required ('thread apply all -- frame 0') and having the
flexibility to tweak the selected frame in just some threads feels
like a nice advantage.  Again, I could potentially add a command flag
here to force the inner most frame.

gdb/ChangeLog:

	* NEWS: Describe new feature.
	* frame.c (cache_selected_frame_on_thread): New function.
	(select_frame): Call new function.
	* gdbthread.h (class thread_info) <selected_frame_id>: New
	member variable.
	<selected_frame_level>: Likewise.
	(switch_to_thread): Extra parameter.
	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
	switch_to_thread.
	(scoped_restore_current_thread::restore): Restore the frame either
	from the thread, or from the local object.
	(set_executing_thread): Reset the currently selected frame.
	(restore_selected_frame_per_thread): New file level static variable.
	(show_restore_selected_frame_per_thread): New function.
	(print_thread_info_1): Pass extra parameter to switch_to_thread.
	(switch_to_thread): Take extra parameter, restore the previous
	frame if appropriate.
	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
	(thread_apply_command): Likewise.
	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
	(_initialize_thread): Add new set/show variable.

gdb/doc/ChangeLog:

	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
	the Frame column of 'info threads' more.  Describe which frame is
	selected when switching threads, and document the new option for
	restoring the previously selected frame.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-selected-frame.c: New file.
	* gdb.threads/restore-selected-frame.exp: New file.
---
 gdb/ChangeLog                                 |  24 ++
 gdb/NEWS                                      |  10 +
 gdb/doc/ChangeLog                             |   7 +
 gdb/doc/gdb.texinfo                           |  23 +-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/testsuite/ChangeLog                       |   5 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/thread.c                                  |  61 +++-
 10 files changed, 577 insertions(+), 13 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 5a900b8a678..8beddcc18e7 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -36,6 +36,16 @@ show restore-selected-thread
   previously selected thread is no longer available then GDB falls
   back to selecting the first non-exited thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+  This option is off by default.  When turned on GDB will record the
+  currently selected frame in each thread.  When switching between
+  threads GDB will attempt to restore the previously selected frame in
+  the thread being switched too.  Executing a thread will cause the
+  GDB to discard any previously selected frame (GDB will select the
+  inner most frame the next time the thread stops).  The 'info
+  threads' command will show the selected frame in its 'frame' field.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index c5819bde7ae..e93e1df169e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3598,6 +3598,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3625,7 +3626,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the inner
+most frame for the thread, see @ref{set restore-selected-frame} to
+display the threads selected frame instead.
 @end enumerate
 
 @noindent
@@ -3697,6 +3700,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads @value{GDBN} will select the inner most
+frame in the thread being switched too, see @ref{set
+restore-selected-frame} to change this behaviour.
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running then when it stops the previously
+selected frame is discarded, and the inner most frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index e783638e0d1..f3bb0abc8e4 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1862,12 +1862,38 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* We only record the selected frame if the level is greater than 0,
+	 this avoids having to calculate the frame id when selecting the
+	 innermost frame.  When the cached selected frame is cleared then
+	 we select the innermost frame anyway, so calculating the frame id
+	 for frame #0 adds no value.  */
+      if (frame_relative_level (fi) > 0)
+	save_selected_frame (&tp->selected_frame_id,
+			     &tp->selected_frame_level);
+      else
+	tp->selected_frame_level = -1;
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the selected frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
   selected_frame_level = frame_relative_level (fi);
   if (selected_frame_level == 0)
     {
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 630727e2fb5..9a7371c923b 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -369,6 +369,12 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id selected_frame_id {};
+  int selected_frame_level = -1;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -571,8 +577,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 32d14e8662c..222a1cd6ce0 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -665,7 +665,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -675,7 +675,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -847,7 +847,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_level = -1;
+    }
 }
 
 void
@@ -1010,6 +1013,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true GDB restore the threads previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1122,7 +1142,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1304,7 +1325,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1314,6 +1335,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_level > -1)
+    restore_selected_frame (thr->selected_frame_id,
+			    thr->selected_frame_level);
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1339,13 +1364,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers ()
@@ -1582,7 +1610,7 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info *thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr))
+	if (switch_to_thread_if_alive (thr, restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr, cmd, from_tty, flags);
     }
 }
@@ -1739,7 +1767,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1913,7 +1941,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2180,6 +2208,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* RE: [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior
  2020-11-06 23:02     ` [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2020-11-09 15:02       ` Aktemur, Tankut Baris
  0 siblings, 0 replies; 28+ messages in thread
From: Aktemur, Tankut Baris @ 2020-11-09 15:02 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Saturday, November 7, 2020 12:02 AM, Andrew Burgess wrote:
> This commit adds a new option that allows the user to control how GDB
> behaves when switching between multi-threaded inferiors.
> 
> Currently (and this remains the default after this commit) when
> switching between inferiors GDB would select the first non-exited
> thread from the inferior being switched to.
> 
> This commit adds the following new commands:
> 
>      set restore-selected-thread on|off
>      show restore-selected-thread
> 
> This option is off by default in order to retain the existing
> behaviour, but, when switched on GDB will remember which thread was
> selected in each inferior.  As the user switches between inferiors GDB
> will attempt to restore the previously selected thread.
> 
> If the previously selected thread is no longer available, for example,
> if the thread has exited, then GDB will fall back on the old
> behaviour.
> 
> I did consider, but eventually didn't implemented, adding a warning
> when switching inferiors if the previously selected thread is no
> longer available.  My reasoning here is that GDB should already have
> informed the user that the thread has exited, and there is already a
> message indicating which thread has been switched too, so adding an
> extra warning felt like unneeded clutter.
> 
> In order to store the thread within the inferior I store a pointer to
> the thread_info object of the previously selected thread.  When
> fetching the thread_info it is important that we do actually have a
> current thread otherwise this happens:
> 
>   $ gdb
>   (gdb) add-inferior
>   (gdb) inferior 2
>   ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion
> `current_thread_ != nullptr' failed.
> 
> To avoid this I added a check that inferior_ptid is not null_ptid.
> Though it is not always the case, there are plenty of places in GDB
> where a call to inferior_thread () is guarded by such a check.
> 
> There's a new test for this functionality.
> 
> gdb/ChangeLog:
> 
> 	* inferior.c (inferior_command): Store current thread_info before
> 	switching inferiors.  Reselect the previous thread_info if
> 	possible after switching to the new inferior.
> 	(initialize_inferiors): Register restore-selected-thread option.
> 	* inferior.h (class inferior) <previous_thread_info>: New member
> 	variable.
> 	* NEWS: Mention new feature.
> 
> gdb/testsuite/ChangeLog:
> 
> 	* gdb.threads/restore-thread.c: New file.
> 	* gdb.threads/restore-thread.exp: New file.
> 
> gdb/doc/ChangeLog:
> 
> 	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
> 	tracking within the inferior command.
> 	(Threads): Mention thread tracking in the general thread
> 	discussion.
> ---
>  gdb/ChangeLog                                |  10 +
>  gdb/NEWS                                     |   9 +
>  gdb/doc/ChangeLog                            |   7 +
>  gdb/doc/gdb.texinfo                          |  19 +-
>  gdb/inferior.c                               |  58 ++++-
>  gdb/inferior.h                               |  10 +
>  gdb/testsuite/ChangeLog                      |   5 +
>  gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
>  gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
>  9 files changed, 583 insertions(+), 2 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 3e08aee7c6f..5a900b8a678 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -27,6 +27,15 @@ set debug event-loop
>  show debug event-loop
>    Control the display of debug output about GDB's event loop.
> 
> +set restore-selected-thread on|off
> +show restore-selected-thread
> +  This new option is off by default.  When turned on GDB will record
> +  the currently selected thread in each inferior.  When switching
> +  between inferiors GDB will attempt to restore the previously
> +  selected thread in the inferior being switched too.  If the
> +  previously selected thread is no longer available then GDB falls
> +  back to selecting the first non-exited thread.
> +
>  * Changed commands
> 
>  break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 52701560006..c5819bde7ae 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -3247,11 +3247,25 @@
>  To switch focus between inferiors, use the @code{inferior} command:
> 
>  @table @code
> +@anchor{inferior command}
>  @kindex inferior @var{infno}
>  @item inferior @var{infno}
>  Make inferior number @var{infno} the current inferior.  The argument
>  @var{infno} is the inferior number assigned by @value{GDBN}, as shown
>  in the first field of the @samp{info inferiors} display.
> +
> +When switching between inferiors with multiple threads
> +(@pxref{Threads}) @value{GDBN} will select the first non-exited thread
> +in the inferior being switched to and make this the current thread.
> +
> +@kindex set restore-selected-thread
> +@kindex show restore-selected-thread
> +@item set restore-selected-thread @r{[}on|off@r{]}
> +@item show restore-selected-thread
> +When this option is on @value{GDBN} will record the currently selected
> +thread in each inferior.  When switching between inferior @value{GDBN}
> +will try to restore the previously selected thread in the inferior
> +being switched to.  This option is off by default.
>  @end table
> 
>  @vindex $_inferior@r{, convenience variable}
> @@ -3633,7 +3647,10 @@
> 
>  If you're debugging multiple inferiors, @value{GDBN} displays thread
>  IDs using the qualified @var{inferior-num}.@var{thread-num} format.
> -Otherwise, only @var{thread-num} is shown.
> +Otherwise, only @var{thread-num} is shown.  When switching between
> +inferiors @value{GDBN} will select a suitable thread in the inferior
> +being switched to, see @ref{inferior command,,the @code{inferior}
> +command} for further details on how to control this behaviour.
> 
>  If you specify the @samp{-gid} option, @value{GDBN} displays a column
>  indicating each thread's global thread ID:
> diff --git a/gdb/inferior.c b/gdb/inferior.c
> index d4a783b3e6d..4961bc467fd 100644
> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -631,6 +631,23 @@ switch_to_inferior_no_thread (inferior *inf)
>    set_current_program_space (inf->pspace);
>  }
> 
> +/* When this is true GDB restores the inferiors previously selected thread

"inferiors" -> "inferior's".
I also think that a comma is need after "true".

> +   each time the inferior is changed (where possible).  */
> +
> +static bool restore_selected_thread_per_inferior = false;
> +
> +/* Implement 'show restore-selected-thread'.  */
> +
> +static void
> +show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
> +					   struct cmd_list_element *c,
> +					   const char *value)
> +{
> +  fprintf_filtered (file,
> +		    _("Restoring the selected thread is currently %s.\n"),
> +		    value);
> +}
> +
>  static void
>  inferior_command (const char *args, int from_tty)
>  {
> @@ -643,11 +660,38 @@ inferior_command (const char *args, int from_tty)
>    if (inf == NULL)
>      error (_("Inferior ID %d not known."), num);
> 
> +  /* We can only call INFERIOR_THREAD if the inferior is known to have an
> +     active thread, which it wont if the inferior is currently exited.  So,
> +     first check if we currently have a thread selected.  */
> +  if (inferior_ptid != null_ptid)
> +    {
> +      /* Now take a strong reference to the current thread_info and store
> +	 it within the inferior, this prevents the thread_info from being
> +	 deleted until the inferior has released the reference.  */
> +      thread_info *tp = inferior_thread ();
> +      tp->incref ();
> +      current_inferior ()->previous_thread_info.reset (tp);
> +    }
> +
>    if (inf->pid != 0)
>      {
>        if (inf != current_inferior ())
>  	{
> -	  thread_info *tp = any_thread_of_inferior (inf);
> +	  thread_info *tp = nullptr;
> +
> +	  if (restore_selected_thread_per_inferior
> +	      && inf->previous_thread_info != nullptr)
> +	    {
> +	      /* Release the reference to the previous thread.  We don't
> +		 switch back to this thread if it is already exited
> +		 though.  */
> +	      tp = inf->previous_thread_info.release ();
> +	      tp->decref ();
> +	      if (tp->state == THREAD_EXITED)
> +		tp = nullptr;
> +	    }
> +	  if (tp == nullptr)
> +	    tp = any_thread_of_inferior (inf);
>  	  if (tp == NULL)
>  	    error (_("Inferior has no threads."));
> 
> @@ -1025,5 +1069,17 @@ Show printing of inferior events (such as inferior start and
> exit)."), NULL,
>  	 show_print_inferior_events,
>  	 &setprintlist, &showprintlist);
> 
> +  add_setshow_boolean_cmd ("restore-selected-thread",
> +			   no_class, &restore_selected_thread_per_inferior,
> +                          _("\
> +Set whether GDB restores the selected thread when switching inferiors."), _("\
> +Show whether GDB restores the selected thread when switching inferiors."), _("\
> +When this option is on GDB will record the currently selected thread for\n\

A comma after "on" could improve the sentence, IMHO.

> +each inferior, and restore the selected thread whenever GDB switches inferiors."),
> +                          nullptr,
> +                          show_restore_selected_thread_per_inferior,
> +                          &setlist,
> +                          &showlist);
> +
>    create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
>  }
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index d016161fb05..517180e48e2 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -543,6 +543,16 @@ class inferior : public refcounted_object
>    /* Data related to displaced stepping.  */
>    displaced_step_inferior_state displaced_step_state;
> 
> +  /* This field is updated when GDB switches away from this inferior to
> +     some other inferior, a reference to a thread_info is stored in here,

It feels like there should be a period after "inferior", instead of a comma, to
end the sentence.

Also, the comment "a reference to a thread_info is stored in here..." is possibly
redundant, because the type "thread_info_ref" implies this.

> +     the ref count for the thread_info should be non-zero to prevent the
> +     thread_info being deleted.
> +
> +     When the user switches back to this inferior the thread_info is taken
> +     out of this reference and used to (possibly) switch back to this
> +     thread.  */
> +  thread_info_ref previous_thread_info;
> +
>    /* Per inferior data-pointers required by other GDB modules.  */
>    REGISTRY_FIELDS;
> 

Thanks.
-Baris


Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Gary Kershaw
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv5 0/2] Restore thread and frame patches
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
  2020-11-06 23:02     ` [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
  2020-11-06 23:02     ` [PATCHv4 2/2] gdb: Track the current frame for each thread Andrew Burgess
@ 2020-11-12 11:59     ` Andrew Burgess
  2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
  2020-11-12 11:59     ` [PATCHv5 1/2] " Andrew Burgess
  2020-11-12 11:59     ` [PATCHv5 2/2] gdb: Track the current frame for each thread Andrew Burgess
  4 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-11-12 11:59 UTC (permalink / raw)
  To: gdb-patches

Compared to v4:

  - Fixed all the documentation issues raised by Eli and Baris.
  - Rebased the code onto current master.

Thanks,
Andrew

---

Andrew Burgess (2):
  gdb: Restore previously selected thread when switching inferior
  gdb: Track the current frame for each thread

 gdb/ChangeLog                                 |  34 ++
 gdb/NEWS                                      |  20 ++
 gdb/doc/ChangeLog                             |  14 +
 gdb/doc/gdb.texinfo                           |  42 ++-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/inferior.c                                |  58 ++-
 gdb/inferior.h                                |   9 +
 gdb/testsuite/ChangeLog                       |  10 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
 gdb/thread.c                                  |  61 +++-
 14 files changed, 1160 insertions(+), 15 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv5 1/2] gdb: Restore previously selected thread when switching inferior
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
                       ` (2 preceding siblings ...)
  2020-11-12 11:59     ` [PATCHv5 0/2] Restore thread and frame patches Andrew Burgess
@ 2020-11-12 11:59     ` Andrew Burgess
  2020-11-12 11:59     ` [PATCHv5 2/2] gdb: Track the current frame for each thread Andrew Burgess
  4 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-11-12 11:59 UTC (permalink / raw)
  To: gdb-patches

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I did consider, but eventually didn't implemented, adding a warning
when switching inferiors if the previously selected thread is no
longer available.  My reasoning here is that GDB should already have
informed the user that the thread has exited, and there is already a
message indicating which thread has been switched too, so adding an
extra warning felt like unneeded clutter.

In order to store the thread within the inferior I store a pointer to
the thread_info object of the previously selected thread.  When
fetching the thread_info it is important that we do actually have a
current thread otherwise this happens:

  $ gdb
  (gdb) add-inferior
  (gdb) inferior 2
  ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.

To avoid this I added a check that inferior_ptid is not null_ptid.
Though it is not always the case, there are plenty of places in GDB
where a call to inferior_thread () is guarded by such a check.

There's a new test for this functionality.

gdb/ChangeLog:

	* inferior.c (inferior_command): Store current thread_info before
	switching inferiors.  Reselect the previous thread_info if
	possible after switching to the new inferior.
	(initialize_inferiors): Register restore-selected-thread option.
	* inferior.h (class inferior) <previous_thread_info>: New member
	variable.
	* NEWS: Mention new feature.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
---
 gdb/ChangeLog                                |  10 +
 gdb/NEWS                                     |   9 +
 gdb/doc/ChangeLog                            |   7 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/inferior.c                               |  58 ++++-
 gdb/inferior.h                               |   9 +
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 9 files changed, 582 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 3e08aee7c6f..196aa041bd9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -27,6 +27,15 @@ set debug event-loop
 show debug event-loop
   Control the display of debug output about GDB's event loop.
 
+set restore-selected-thread on|off
+show restore-selected-thread
+  When turned on, GDB will record the currently selected thread in
+  each inferior.  When switching between inferiors, GDB will attempt
+  to restore the previously selected thread in the inferior being
+  switched too.  If the previously selected thread is no longer
+  available, then GDB falls back to selecting the first non-exited
+  thread.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 7092331de4e..42e22d20a7f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3247,11 +3247,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads (@pxref{Threads}),
+@value{GDBN} will select the first non-exited thread in the inferior being
+switched to, and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on, @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferiors, @value{GDBN}
+will try to restore the previously selected thread in the inferior being
+switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3633,7 +3647,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors, @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command}, for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/inferior.c b/gdb/inferior.c
index d4a783b3e6d..96638c915d5 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -631,6 +631,23 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* When this is true, GDB restores the inferior's previously selected
+   thread each time the inferior is changed (where possible).  */
+
+static bool restore_selected_thread_per_inferior = false;
+
+/* Implement 'show restore-selected-thread'.  */
+
+static void
+show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
+					   struct cmd_list_element *c,
+					   const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected thread is currently %s.\n"),
+		    value);
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
@@ -643,11 +660,38 @@ inferior_command (const char *args, int from_tty)
   if (inf == NULL)
     error (_("Inferior ID %d not known."), num);
 
+  /* We can only call INFERIOR_THREAD if the inferior is known to have an
+     active thread, which it wont if the inferior is currently exited.  So,
+     first check if we currently have a thread selected.  */
+  if (inferior_ptid != null_ptid)
+    {
+      /* Now take a strong reference to the current thread_info and store
+	 it within the inferior, this prevents the thread_info from being
+	 deleted until the inferior has released the reference.  */
+      thread_info *tp = inferior_thread ();
+      tp->incref ();
+      current_inferior ()->previous_thread_info.reset (tp);
+    }
+
   if (inf->pid != 0)
     {
       if (inf != current_inferior ())
 	{
-	  thread_info *tp = any_thread_of_inferior (inf);
+	  thread_info *tp = nullptr;
+
+	  if (restore_selected_thread_per_inferior
+	      && inf->previous_thread_info != nullptr)
+	    {
+	      /* Release the reference to the previous thread.  We don't
+		 switch back to this thread if it is already exited
+		 though.  */
+	      tp = inf->previous_thread_info.release ();
+	      tp->decref ();
+	      if (tp->state == THREAD_EXITED)
+		tp = nullptr;
+	    }
+	  if (tp == nullptr)
+	    tp = any_thread_of_inferior (inf);
 	  if (tp == NULL)
 	    error (_("Inferior has no threads."));
 
@@ -1025,5 +1069,17 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-thread",
+			   no_class, &restore_selected_thread_per_inferior,
+                          _("\
+Set whether GDB restores the selected thread when switching inferiors."), _("\
+Show whether GDB restores the selected thread when switching inferiors."), _("\
+When this option is on, GDB will record the currently selected thread for\n\
+each inferior, and restore the selected thread whenever GDB switches inferiors."),
+                          nullptr,
+                          show_restore_selected_thread_per_inferior,
+                          &setlist,
+                          &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index d016161fb05..eb97c036446 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -543,6 +543,15 @@ class inferior : public refcounted_object
   /* Data related to displaced stepping.  */
   displaced_step_inferior_state displaced_step_state;
 
+  /* This field is updated when GDB switches away from this inferior to
+     some other inferior.  Holding a reference to the previously selected
+     thread prevents GDB from deleting the thread_info (until the inferior
+     itself is deleted).
+
+     When the user switches back to this inferior this reference is used to
+     (possibly) restore the selected thread.  */
+  thread_info_ref previous_thread_info;
+
   /* Per inferior data-pointers required by other GDB modules.  */
   REGISTRY_FIELDS;
 
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv5 2/2] gdb: Track the current frame for each thread
  2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
                       ` (3 preceding siblings ...)
  2020-11-12 11:59     ` [PATCHv5 1/2] " Andrew Burgess
@ 2020-11-12 11:59     ` Andrew Burgess
  4 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-11-12 11:59 UTC (permalink / raw)
  To: gdb-patches

Currently in GDB, each time a user switches between threads, the inner
most frame of the thread being switched to is selected.  In some
situations however, it might be helpful for a user to have GDB
remember which frame was selected in each thread, and restore this
frame as the user switches between threads.

This commit the following two commands:

  set restore-selected-frame on|off
  show restore-selected-frame

This new option is off by default, so the default behaviour of GDB is
unchanged.

However, with this option turned on GDB will remember, and restore the
selected frame for each thread.

My initial motivation for this change was to have the thread restored
when switching threads with 'thread <num>', however, as I started to
work on this feature I realised that there were a couple of other
places where the sticky frame would naturally appear.  These are 'info
threads' and 'thread apply all'.

With 'info threads' the output contains a 'Frame' column.  Previously,
this was always the innermost frame, the info threads output was
created by switching to each thread in turn and collecting information
about the thread, this naturally placed us at the innermost frame.
Now, the 'Frame' column displays the _selected_ frame for each
thread.

I struggled to decide if this change was good or not.  In the end I
felt that having 'info threads' display the selected frame would feel
more natural, that's the frame you'll end up in if you switch to that
thread, so if seemed to make sense.  However, it would be easy enough
to force the old behaviour if people would prefer.  Alternatively I
could even investigate adding a switch to 'info threads' that allows
the user to select displaying either the selected frame, or the inner
most frame.

For 'thread apply all', again, we used to always apply to the
innermost frame.  Now it's possible for a user to adjust which frame
will be current when the 'thread apply all' runs - this feels like a
useful change to me.  It's easy enough to quickly restore the inner
most frame if required ('thread apply all -- frame 0') and having the
flexibility to tweak the selected frame in just some threads feels
like a nice advantage.  Again, I could potentially add a command flag
here to force the inner most frame.

gdb/ChangeLog:

	* NEWS: Describe new feature.
	* frame.c (cache_selected_frame_on_thread): New function.
	(select_frame): Call new function.
	* gdbthread.h (class thread_info) <selected_frame_id>: New
	member variable.
	<selected_frame_level>: Likewise.
	(switch_to_thread): Extra parameter.
	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
	switch_to_thread.
	(scoped_restore_current_thread::restore): Restore the frame either
	from the thread, or from the local object.
	(set_executing_thread): Reset the currently selected frame.
	(restore_selected_frame_per_thread): New file level static variable.
	(show_restore_selected_frame_per_thread): New function.
	(print_thread_info_1): Pass extra parameter to switch_to_thread.
	(switch_to_thread): Take extra parameter, restore the previous
	frame if appropriate.
	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
	(thread_apply_command): Likewise.
	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
	(_initialize_thread): Add new set/show variable.

gdb/doc/ChangeLog:

	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
	the Frame column of 'info threads' more.  Describe which frame is
	selected when switching threads, and document the new option for
	restoring the previously selected frame.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-selected-frame.c: New file.
	* gdb.threads/restore-selected-frame.exp: New file.
---
 gdb/ChangeLog                                 |  24 ++
 gdb/NEWS                                      |  11 +
 gdb/doc/ChangeLog                             |   7 +
 gdb/doc/gdb.texinfo                           |  23 +-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/testsuite/ChangeLog                       |   5 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/thread.c                                  |  61 +++-
 10 files changed, 578 insertions(+), 13 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 196aa041bd9..b45159a7b04 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -36,6 +36,17 @@ show restore-selected-thread
   available, then GDB falls back to selecting the first non-exited
   thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+
+  When turned on, GDB will record the currently selected frame in each
+  thread.  When switching between threads, GDB will attempt to restore
+  the previously selected frame in the thread being switched too.
+  Executing a thread will cause GDB to discard any previously selected
+  frame (GDB will select the inner most frame the next time the thread
+  stops).  The 'info threads' command will show the selected frame in
+  its 'frame' field.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 42e22d20a7f..79eadeda989 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3598,6 +3598,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3625,7 +3626,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the inner most
+frame for the thread (@pxref{set restore-selected-frame} to display the
+threads selected frame instead).
 @end enumerate
 
 @noindent
@@ -3697,6 +3700,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads, @value{GDBN} will select the inner most
+frame in the thread being switched too (@pxref{set restore-selected-frame}
+to change this behaviour).
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running, then when it stops, the previously
+selected frame is discarded, and the inner most frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index e783638e0d1..f3bb0abc8e4 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1862,12 +1862,38 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* We only record the selected frame if the level is greater than 0,
+	 this avoids having to calculate the frame id when selecting the
+	 innermost frame.  When the cached selected frame is cleared then
+	 we select the innermost frame anyway, so calculating the frame id
+	 for frame #0 adds no value.  */
+      if (frame_relative_level (fi) > 0)
+	save_selected_frame (&tp->selected_frame_id,
+			     &tp->selected_frame_level);
+      else
+	tp->selected_frame_level = -1;
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the selected frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
   selected_frame_level = frame_relative_level (fi);
   if (selected_frame_level == 0)
     {
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 630727e2fb5..9a7371c923b 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -369,6 +369,12 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id selected_frame_id {};
+  int selected_frame_level = -1;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -571,8 +577,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 32d14e8662c..b9b4f66764c 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -665,7 +665,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -675,7 +675,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -847,7 +847,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_level = -1;
+    }
 }
 
 void
@@ -1010,6 +1013,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true GDB restore the threads previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1122,7 +1142,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1304,7 +1325,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1314,6 +1335,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_level > -1)
+    restore_selected_frame (thr->selected_frame_id,
+			    thr->selected_frame_level);
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1339,13 +1364,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers ()
@@ -1582,7 +1610,7 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info *thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr))
+	if (switch_to_thread_if_alive (thr, restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr, cmd, from_tty, flags);
     }
 }
@@ -1739,7 +1767,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1913,7 +1941,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2180,6 +2208,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on, GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv6 0/2] Restore thread and frame patches
  2020-11-12 11:59     ` [PATCHv5 0/2] Restore thread and frame patches Andrew Burgess
@ 2020-12-10 11:39       ` Andrew Burgess
  2020-12-10 11:39         ` [PATCHv6 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
                           ` (3 more replies)
  0 siblings, 4 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-12-10 11:39 UTC (permalink / raw)
  To: gdb-patches

Compared to v5:

  + Minor tweak to one comment in patch #2 (added a comma).
  + Rebased onto current master.

Still hoping for Pedro to review this series in comparison to the
original patches he objected to.

Thanks,
Andrew


---

Andrew Burgess (2):
  gdb: Restore previously selected thread when switching inferior
  gdb: Track the current frame for each thread

 gdb/ChangeLog                                 |  34 ++
 gdb/NEWS                                      |  20 ++
 gdb/doc/ChangeLog                             |  14 +
 gdb/doc/gdb.texinfo                           |  42 ++-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/inferior.c                                |  58 ++-
 gdb/inferior.h                                |   9 +
 gdb/testsuite/ChangeLog                       |  10 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
 gdb/thread.c                                  |  61 +++-
 14 files changed, 1160 insertions(+), 15 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv6 1/2] gdb: Restore previously selected thread when switching inferior
  2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
@ 2020-12-10 11:39         ` Andrew Burgess
  2020-12-10 11:39         ` [PATCHv6 2/2] gdb: Track the current frame for each thread Andrew Burgess
                           ` (2 subsequent siblings)
  3 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2020-12-10 11:39 UTC (permalink / raw)
  To: gdb-patches

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I did consider, but eventually didn't implemented, adding a warning
when switching inferiors if the previously selected thread is no
longer available.  My reasoning here is that GDB should already have
informed the user that the thread has exited, and there is already a
message indicating which thread has been switched too, so adding an
extra warning felt like unneeded clutter.

In order to store the thread within the inferior I store a pointer to
the thread_info object of the previously selected thread.  When
fetching the thread_info it is important that we do actually have a
current thread otherwise this happens:

  $ gdb
  (gdb) add-inferior
  (gdb) inferior 2
  ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.

To avoid this I added a check that inferior_ptid is not null_ptid.
Though it is not always the case, there are plenty of places in GDB
where a call to inferior_thread () is guarded by such a check.

There's a new test for this functionality.

gdb/ChangeLog:

	* inferior.c (inferior_command): Store current thread_info before
	switching inferiors.  Reselect the previous thread_info if
	possible after switching to the new inferior.
	(initialize_inferiors): Register restore-selected-thread option.
	* inferior.h (class inferior) <previous_thread_info>: New member
	variable.
	* NEWS: Mention new feature.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
---
 gdb/ChangeLog                                |  10 +
 gdb/NEWS                                     |   9 +
 gdb/doc/ChangeLog                            |   7 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/inferior.c                               |  58 ++++-
 gdb/inferior.h                               |   9 +
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 9 files changed, 582 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index d75992e78ef..cc23bcf1ffd 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -39,6 +39,15 @@ set debug event-loop
 show debug event-loop
   Control the display of debug output about GDB's event loop.
 
+set restore-selected-thread on|off
+show restore-selected-thread
+  When turned on, GDB will record the currently selected thread in
+  each inferior.  When switching between inferiors, GDB will attempt
+  to restore the previously selected thread in the inferior being
+  switched too.  If the previously selected thread is no longer
+  available, then GDB falls back to selecting the first non-exited
+  thread.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 01dcac941c2..41d73b347a6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3247,11 +3247,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads (@pxref{Threads}),
+@value{GDBN} will select the first non-exited thread in the inferior being
+switched to, and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on, @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferiors, @value{GDBN}
+will try to restore the previously selected thread in the inferior being
+switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3633,7 +3647,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors, @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command}, for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/inferior.c b/gdb/inferior.c
index d4a783b3e6d..96638c915d5 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -631,6 +631,23 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* When this is true, GDB restores the inferior's previously selected
+   thread each time the inferior is changed (where possible).  */
+
+static bool restore_selected_thread_per_inferior = false;
+
+/* Implement 'show restore-selected-thread'.  */
+
+static void
+show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
+					   struct cmd_list_element *c,
+					   const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected thread is currently %s.\n"),
+		    value);
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
@@ -643,11 +660,38 @@ inferior_command (const char *args, int from_tty)
   if (inf == NULL)
     error (_("Inferior ID %d not known."), num);
 
+  /* We can only call INFERIOR_THREAD if the inferior is known to have an
+     active thread, which it wont if the inferior is currently exited.  So,
+     first check if we currently have a thread selected.  */
+  if (inferior_ptid != null_ptid)
+    {
+      /* Now take a strong reference to the current thread_info and store
+	 it within the inferior, this prevents the thread_info from being
+	 deleted until the inferior has released the reference.  */
+      thread_info *tp = inferior_thread ();
+      tp->incref ();
+      current_inferior ()->previous_thread_info.reset (tp);
+    }
+
   if (inf->pid != 0)
     {
       if (inf != current_inferior ())
 	{
-	  thread_info *tp = any_thread_of_inferior (inf);
+	  thread_info *tp = nullptr;
+
+	  if (restore_selected_thread_per_inferior
+	      && inf->previous_thread_info != nullptr)
+	    {
+	      /* Release the reference to the previous thread.  We don't
+		 switch back to this thread if it is already exited
+		 though.  */
+	      tp = inf->previous_thread_info.release ();
+	      tp->decref ();
+	      if (tp->state == THREAD_EXITED)
+		tp = nullptr;
+	    }
+	  if (tp == nullptr)
+	    tp = any_thread_of_inferior (inf);
 	  if (tp == NULL)
 	    error (_("Inferior has no threads."));
 
@@ -1025,5 +1069,17 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-thread",
+			   no_class, &restore_selected_thread_per_inferior,
+                          _("\
+Set whether GDB restores the selected thread when switching inferiors."), _("\
+Show whether GDB restores the selected thread when switching inferiors."), _("\
+When this option is on, GDB will record the currently selected thread for\n\
+each inferior, and restore the selected thread whenever GDB switches inferiors."),
+                          nullptr,
+                          show_restore_selected_thread_per_inferior,
+                          &setlist,
+                          &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index c5257ac1e64..9942df57140 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -544,6 +544,15 @@ class inferior : public refcounted_object
   /* Data related to displaced stepping.  */
   displaced_step_inferior_state displaced_step_state;
 
+  /* This field is updated when GDB switches away from this inferior to
+     some other inferior.  Holding a reference to the previously selected
+     thread prevents GDB from deleting the thread_info (until the inferior
+     itself is deleted).
+
+     When the user switches back to this inferior this reference is used to
+     (possibly) restore the selected thread.  */
+  thread_info_ref previous_thread_info;
+
   /* Per inferior data-pointers required by other GDB modules.  */
   REGISTRY_FIELDS;
 
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv6 2/2] gdb: Track the current frame for each thread
  2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
  2020-12-10 11:39         ` [PATCHv6 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2020-12-10 11:39         ` Andrew Burgess
  2020-12-18  8:43           ` Aktemur, Tankut Baris
  2021-01-07 10:25         ` [PATCHv6 0/2] Restore thread and frame patches Andrew Burgess
  2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
  3 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2020-12-10 11:39 UTC (permalink / raw)
  To: gdb-patches

Currently in GDB, each time a user switches between threads, the inner
most frame of the thread being switched to is selected.  In some
situations however, it might be helpful for a user to have GDB
remember which frame was selected in each thread, and restore this
frame as the user switches between threads.

This commit the following two commands:

  set restore-selected-frame on|off
  show restore-selected-frame

This new option is off by default, so the default behaviour of GDB is
unchanged.

However, with this option turned on GDB will remember, and restore the
selected frame for each thread.

My initial motivation for this change was to have the thread restored
when switching threads with 'thread <num>', however, as I started to
work on this feature I realised that there were a couple of other
places where the sticky frame would naturally appear.  These are 'info
threads' and 'thread apply all'.

With 'info threads' the output contains a 'Frame' column.  Previously,
this was always the innermost frame, the info threads output was
created by switching to each thread in turn and collecting information
about the thread, this naturally placed us at the innermost frame.
Now, the 'Frame' column displays the _selected_ frame for each
thread.

I struggled to decide if this change was good or not.  In the end I
felt that having 'info threads' display the selected frame would feel
more natural, that's the frame you'll end up in if you switch to that
thread, so if seemed to make sense.  However, it would be easy enough
to force the old behaviour if people would prefer.  Alternatively I
could even investigate adding a switch to 'info threads' that allows
the user to select displaying either the selected frame, or the inner
most frame.

For 'thread apply all', again, we used to always apply to the
innermost frame.  Now it's possible for a user to adjust which frame
will be current when the 'thread apply all' runs - this feels like a
useful change to me.  It's easy enough to quickly restore the inner
most frame if required ('thread apply all -- frame 0') and having the
flexibility to tweak the selected frame in just some threads feels
like a nice advantage.  Again, I could potentially add a command flag
here to force the inner most frame.

gdb/ChangeLog:

	* NEWS: Describe new feature.
	* frame.c (cache_selected_frame_on_thread): New function.
	(select_frame): Call new function.
	* gdbthread.h (class thread_info) <selected_frame_id>: New
	member variable.
	<selected_frame_level>: Likewise.
	(switch_to_thread): Extra parameter.
	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
	switch_to_thread.
	(scoped_restore_current_thread::restore): Restore the frame either
	from the thread, or from the local object.
	(set_executing_thread): Reset the currently selected frame.
	(restore_selected_frame_per_thread): New file level static variable.
	(show_restore_selected_frame_per_thread): New function.
	(print_thread_info_1): Pass extra parameter to switch_to_thread.
	(switch_to_thread): Take extra parameter, restore the previous
	frame if appropriate.
	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
	(thread_apply_command): Likewise.
	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
	(_initialize_thread): Add new set/show variable.

gdb/doc/ChangeLog:

	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
	the Frame column of 'info threads' more.  Describe which frame is
	selected when switching threads, and document the new option for
	restoring the previously selected frame.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-selected-frame.c: New file.
	* gdb.threads/restore-selected-frame.exp: New file.
---
 gdb/ChangeLog                                 |  24 ++
 gdb/NEWS                                      |  11 +
 gdb/doc/ChangeLog                             |   7 +
 gdb/doc/gdb.texinfo                           |  23 +-
 gdb/frame.c                                   |  26 ++
 gdb/gdbthread.h                               |  13 +-
 gdb/testsuite/ChangeLog                       |   5 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/thread.c                                  |  61 +++-
 10 files changed, 578 insertions(+), 13 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index cc23bcf1ffd..fbd1cb52889 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -48,6 +48,17 @@ show restore-selected-thread
   available, then GDB falls back to selecting the first non-exited
   thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+
+  When turned on, GDB will record the currently selected frame in each
+  thread.  When switching between threads, GDB will attempt to restore
+  the previously selected frame in the thread being switched too.
+  Executing a thread will cause GDB to discard any previously selected
+  frame (GDB will select the inner most frame the next time the thread
+  stops).  The 'info threads' command will show the selected frame in
+  its 'frame' field.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 41d73b347a6..fdf91f0e179 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3598,6 +3598,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3625,7 +3626,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the inner most
+frame for the thread (@pxref{set restore-selected-frame} to display the
+threads selected frame instead).
 @end enumerate
 
 @noindent
@@ -3697,6 +3700,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads, @value{GDBN} will select the inner most
+frame in the thread being switched too (@pxref{set restore-selected-frame}
+to change this behaviour).
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running, then when it stops, the previously
+selected frame is discarded, and the inner most frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index 4618da6c81e..a8e96aa534b 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1862,12 +1862,38 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* We only record the selected frame if the level is greater than 0,
+	 this avoids having to calculate the frame id when selecting the
+	 innermost frame.  When the cached selected frame is cleared then
+	 we select the innermost frame anyway, so calculating the frame id
+	 for frame #0 adds no value.  */
+      if (frame_relative_level (fi) > 0)
+	save_selected_frame (&tp->selected_frame_id,
+			     &tp->selected_frame_level);
+      else
+	tp->selected_frame_level = -1;
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the selected frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
   selected_frame_level = frame_relative_level (fi);
   if (selected_frame_level == 0)
     {
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index e5484ac54ca..686a463d5f7 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -370,6 +370,12 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id selected_frame_id {};
+  int selected_frame_level = -1;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -575,8 +581,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 856bdee97b3..63261247e19 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -721,7 +721,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -731,7 +731,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -903,7 +903,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_level = -1;
+    }
 }
 
 void
@@ -1066,6 +1069,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true, GDB restore the thread's previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1178,7 +1198,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1360,7 +1381,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1370,6 +1391,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_level > -1)
+    restore_selected_frame (thr->selected_frame_id,
+			    thr->selected_frame_level);
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1395,13 +1420,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers ()
@@ -1638,7 +1666,7 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info *thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr))
+	if (switch_to_thread_if_alive (thr, restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr, cmd, from_tty, flags);
     }
 }
@@ -1795,7 +1823,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1969,7 +1997,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2236,6 +2264,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on, GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* RE: [PATCHv6 2/2] gdb: Track the current frame for each thread
  2020-12-10 11:39         ` [PATCHv6 2/2] gdb: Track the current frame for each thread Andrew Burgess
@ 2020-12-18  8:43           ` Aktemur, Tankut Baris
  2021-01-04 15:07             ` Andrew Burgess
  0 siblings, 1 reply; 28+ messages in thread
From: Aktemur, Tankut Baris @ 2020-12-18  8:43 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

On Thursday, December 10, 2020 12:39 PM, Andrew Burgess wrote:
> Currently in GDB, each time a user switches between threads, the inner
> most frame of the thread being switched to is selected.  In some
> situations however, it might be helpful for a user to have GDB
> remember which frame was selected in each thread, and restore this
> frame as the user switches between threads.
> 
> This commit the following two commands:

I think the verb is missing here.  Maybe "adds" or "introduces".

> 
>   set restore-selected-frame on|off
>   show restore-selected-frame
> 
> This new option is off by default, so the default behaviour of GDB is
> unchanged.
> 
> However, with this option turned on GDB will remember, and restore the
> selected frame for each thread.
> 
> My initial motivation for this change was to have the thread restored
> when switching threads with 'thread <num>', however, as I started to
> work on this feature I realised that there were a couple of other
> places where the sticky frame would naturally appear.  These are 'info
> threads' and 'thread apply all'.
> 
> With 'info threads' the output contains a 'Frame' column.  Previously,
> this was always the innermost frame, the info threads output was
> created by switching to each thread in turn and collecting information
> about the thread, this naturally placed us at the innermost frame.
> Now, the 'Frame' column displays the _selected_ frame for each
> thread.
> 
> I struggled to decide if this change was good or not.  In the end I
> felt that having 'info threads' display the selected frame would feel
> more natural, that's the frame you'll end up in if you switch to that
> thread, so if seemed to make sense.  However, it would be easy enough

"if" -> "it".

> to force the old behaviour if people would prefer.  Alternatively I
> could even investigate adding a switch to 'info threads' that allows
> the user to select displaying either the selected frame, or the inner
> most frame.

"innermost" is one word, AFAIK.

> For 'thread apply all', again, we used to always apply to the
> innermost frame.  Now it's possible for a user to adjust which frame
> will be current when the 'thread apply all' runs - this feels like a
> useful change to me.  It's easy enough to quickly restore the inner

Here, too: "inner most" -> "innermost"
There are a couple more instances below, in the texinfo file.

> most frame if required ('thread apply all -- frame 0') and having the
> flexibility to tweak the selected frame in just some threads feels
> like a nice advantage.  Again, I could potentially add a command flag
> here to force the inner most frame.
> 
> gdb/ChangeLog:
> 
> 	* NEWS: Describe new feature.
> 	* frame.c (cache_selected_frame_on_thread): New function.
> 	(select_frame): Call new function.
> 	* gdbthread.h (class thread_info) <selected_frame_id>: New
> 	member variable.
> 	<selected_frame_level>: Likewise.
> 	(switch_to_thread): Extra parameter.
> 	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
> 	switch_to_thread.
> 	(scoped_restore_current_thread::restore): Restore the frame either
> 	from the thread, or from the local object.
> 	(set_executing_thread): Reset the currently selected frame.
> 	(restore_selected_frame_per_thread): New file level static variable.
> 	(show_restore_selected_frame_per_thread): New function.
> 	(print_thread_info_1): Pass extra parameter to switch_to_thread.
> 	(switch_to_thread): Take extra parameter, restore the previous
> 	frame if appropriate.
> 	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
> 	(thread_apply_command): Likewise.
> 	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
> 	(_initialize_thread): Add new set/show variable.
> 
> gdb/doc/ChangeLog:
> 
> 	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
> 	the Frame column of 'info threads' more.  Describe which frame is
> 	selected when switching threads, and document the new option for
> 	restoring the previously selected frame.
> 
> gdb/testsuite/ChangeLog:
> 
> 	* gdb.threads/restore-selected-frame.c: New file.
> 	* gdb.threads/restore-selected-frame.exp: New file.

There are some related FIXME comments in frame.h:

   292  /* For every stopped thread, GDB tracks two frames: current and
   293     selected.  Current frame is the inner most frame of the selected
   294     thread.  Selected frame is the one being examined by the GDB
   295     CLI (selected using `up', `down', ...).  The frames are created
   296     on-demand (via get_prev_frame()) and then held in a frame cache.  */
   297  /* FIXME: cagney/2002-11-28: Er, there is a lie here.  If you do the
   298     sequence: `thread 1; up; thread 2; thread 1' you lose thread 1's
   299     selected frame.  At present GDB only tracks the selected frame of
   300     the current thread.  But be warned, that might change.  */
   301  /* FIXME: cagney/2002-11-14: At any time, only one thread's selected
   302     and current frame can be active.  Switching threads causes gdb to
   303     discard all that cached frame information.  Ulgh!  Instead, current
   304     and selected frame should be bound to a thread.  */
   305
   306  /* On demand, create the inner most frame using information found in
   307     the inferior.  If the inner most frame can't be created, throw an
   308     error.  */
   309  extern struct frame_info *get_current_frame (void);
...
   325  /* Return the selected frame.  Always returns non-NULL.  If there
   326     isn't an inferior sufficient for creating a frame, an error is
   327     thrown.  When MESSAGE is non-NULL, use it for the error message,
   328     otherwise use a generic error message.  */
   329  /* FIXME: cagney/2002-11-28: At present, when there is no selected
   330     frame, this function always returns the current (inner most) frame.
   331     It should instead, when a thread has previously had its frame
   332     selected (but not resumed) and the frame cache invalidated, find
   333     and then return that thread's previously selected frame.  */
   334  extern struct frame_info *get_selected_frame (const char *message = nullptr);

It might be worth updating them, too.


> ---
>  gdb/ChangeLog                                 |  24 ++
>  gdb/NEWS                                      |  11 +
>  gdb/doc/ChangeLog                             |   7 +
>  gdb/doc/gdb.texinfo                           |  23 +-
>  gdb/frame.c                                   |  26 ++
>  gdb/gdbthread.h                               |  13 +-
>  gdb/testsuite/ChangeLog                       |   5 +
>  .../gdb.threads/restore-selected-frame.c      |  85 +++++
>  .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
>  gdb/thread.c                                  |  61 +++-
>  10 files changed, 578 insertions(+), 13 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index cc23bcf1ffd..fbd1cb52889 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -48,6 +48,17 @@ show restore-selected-thread
>    available, then GDB falls back to selecting the first non-exited
>    thread.
> 
> +set restore-selected-frame [on|off]
> +show restore-selected-frame
> +
> +  When turned on, GDB will record the currently selected frame in each
> +  thread.  When switching between threads, GDB will attempt to restore
> +  the previously selected frame in the thread being switched too.
> +  Executing a thread will cause GDB to discard any previously selected
> +  frame (GDB will select the inner most frame the next time the thread
> +  stops).  The 'info threads' command will show the selected frame in
> +  its 'frame' field.
> +
>  * Changed commands
> 
>  break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 41d73b347a6..fdf91f0e179 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -3598,6 +3598,7 @@
>  @end smallexample
> 
>  @table @code
> +@anchor{info threads}
>  @kindex info threads
>  @item info threads @r{[}@var{thread-id-list}@r{]}
> 
> @@ -3625,7 +3626,9 @@
>  program itself.
> 
>  @item
> -the current stack frame summary for that thread
> +the current stack frame summary for that thread, this is the inner most
> +frame for the thread (@pxref{set restore-selected-frame} to display the
> +threads selected frame instead).
>  @end enumerate
> 
>  @noindent
> @@ -3697,6 +3700,24 @@
>  @samp{Switching to} depends on your system's conventions for identifying
>  threads.
> 
> +When switching between threads, @value{GDBN} will select the inner most
> +frame in the thread being switched too (@pxref{set restore-selected-frame}
> +to change this behaviour).
> +
> +@anchor{set restore-selected-frame}
> +@item set restore-selected-frame @r{[}on|off@r{]}
> +@itemx show restore-selected-frame
> +When @code{restore-selected-frame} is on, @value{GDBN} will restore
> +the previously selected frame when switching to a different thread.
> +Also the @code{info threads} command (@pxref{info threads}) will display the
> +currently selected frame for each thread.
> +
> +If a thread has been running, then when it stops, the previously
> +selected frame is discarded, and the inner most frame is again
> +selected.
> +
> +This option is @code{off} by default.
> +
>  @anchor{thread apply all}
>  @kindex thread apply
>  @cindex apply command to several threads
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 4618da6c81e..a8e96aa534b 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -1862,12 +1862,38 @@ deprecated_safe_get_selected_frame (void)
>    return get_selected_frame (NULL);
>  }
> 
> +/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
> +   current thread the information required to identify frame FI so the
> +   frame can be selected again later if we switch threads.  */
> +
> +static void
> +cache_selected_frame_on_thread ()
> +{
> +  struct frame_info *fi = selected_frame;
> +  struct thread_info *tp
> +    = find_thread_ptid (current_inferior (), inferior_ptid);
> +  if (fi != nullptr && tp != nullptr)
> +    {
> +      /* We only record the selected frame if the level is greater than 0,
> +	 this avoids having to calculate the frame id when selecting the
> +	 innermost frame.  When the cached selected frame is cleared then
> +	 we select the innermost frame anyway, so calculating the frame id
> +	 for frame #0 adds no value.  */
> +      if (frame_relative_level (fi) > 0)
> +	save_selected_frame (&tp->selected_frame_id,
> +			     &tp->selected_frame_level);
> +      else
> +	tp->selected_frame_level = -1;
> +    }
> +}
> +
>  /* Select frame FI (or NULL - to invalidate the selected frame).  */
> 
>  void
>  select_frame (struct frame_info *fi)
>  {
>    selected_frame = fi;
> +  cache_selected_frame_on_thread ();
>    selected_frame_level = frame_relative_level (fi);
>    if (selected_frame_level == 0)
>      {
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index e5484ac54ca..686a463d5f7 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -370,6 +370,12 @@ class thread_info : public refcounted_object
>       bp_longjmp_call_dummy.  */
>    struct frame_id initiating_frame = null_frame_id;
> 
> +  /* Information for the last frame successfully selected in this thread.
> +     If the user configurable setting is on then GDB will try to reselect
> +     this frame when switching threads.  */
> +  struct frame_id selected_frame_id {};
> +  int selected_frame_level = -1;
> +
>    /* Private data used by the target vector implementation.  */
>    std::unique_ptr<private_thread_info> priv;
> 
> @@ -575,8 +581,11 @@ extern int thread_count (process_stratum_target *proc_target);
>  /* Return true if we have any thread in any inferior.  */
>  extern bool any_thread_p ();
> 
> -/* Switch context to thread THR.  Also sets the STOP_PC global.  */
> -extern void switch_to_thread (struct thread_info *thr);
> +/* Switch context to thread THR.  Also sets the STOP_PC global.  When
> +   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
> +   selected frame cached, the previous frame is restored.  */
> +extern void switch_to_thread (struct thread_info *thr,
> +			      bool restore_previous_frame = false);
> 
>  /* Switch context to no thread selected.  */
>  extern void switch_to_no_thread ();
> diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c
> b/gdb/testsuite/gdb.threads/restore-selected-frame.c
> new file mode 100644
> index 00000000000..c72b0b8b54b
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
> @@ -0,0 +1,85 @@
> +#include <sys/types.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <pthread.h>
> +
> +volatile int loop_count = 10;
> +volatile int thread_count = 3;
> +
> +static void
> +thread_level_5 (int id, int count)
> +{
> +  printf ("Thread %d reached %s, #%d\n",
> +	  id, __PRETTY_FUNCTION__, count);
> +}
> +
> +static void
> +thread_level_4 (int id, int count)
> +{
> +  thread_level_5 (id, count);
> +}
> +
> +static void
> +thread_level_3 (int id, int count)
> +{
> +  thread_level_4 (id, count);
> +}
> +
> +static void
> +thread_level_2 (int id, int count)
> +{
> +  thread_level_3 (id, count);
> +}
> +
> +static void
> +thread_level_1 (int id, int count)
> +{
> +  thread_level_2 (id, count);
> +}
> +
> +static void *
> +thread_worker (void *arg)
> +{
> +  int i, max, id;
> +
> +  id = *((int *) arg);
> +  max = loop_count;
> +  for (i = 0; i < max; ++i)
> +    thread_level_1 (id, (i + 1));
> +
> +  return NULL;
> +}
> +
> +struct thread_info
> +{
> +  pthread_t thread;
> +  int id;
> +};
> +
> +int
> +main ()
> +{
> +  int i, max = thread_count;
> +
> +  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
> +  if (info == NULL)
> +    abort ();
> +
> +  for (i = 0; i < max; ++i)
> +    {
> +      struct thread_info *thr = &info[i];
> +      thr->id = i + 1;
> +      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
> +	abort ();
> +    }
> +
> +  for (i = 0; i < max; ++i)
> +    {
> +      struct thread_info *thr = &info[i];
> +      if (pthread_join (thr->thread, NULL) != 0)
> +	abort ();
> +    }
> +
> +  free (info);
> +}
> diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp
> b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
> new file mode 100644
> index 00000000000..b40386fc58e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
> @@ -0,0 +1,336 @@
> +# Copyright 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/>.
> +
> +# This tests GDB's tracking of the currently selected frame on a
> +# per-thread basis.
> +#
> +# We setup a couple of inferiors, each with mutliple theads, we then
> +# switch between threads and modify the current frame.  We use 'info
> +# threads' to check that GDB is correctly tracking the current frame.
> +#
> +# Toward the end of the test we check that when a thread executes the
> +# currently selected frame is reset.
> +#
> +# Finally we disable tracking of the currently selected frame and
> +# ensure GDB no longer restores the current frame.
> +
> +standard_testfile
> +
> +set options { debug pthreads }
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	 $options] == -1} {
> +    return -1
> +}
> +
> +# Run the 'info threads' command, and check that the frame part of
> +# each threads output matches the corresponding pattern in FRAME_INFO,
> +# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
> +# and so on.
> +proc test_info_threads { testname frame_info } {
> +    global decimal hex gdb_prompt
> +
> +    set thread_count 0
> +    gdb_test_multiple "info threads" ${testname} {
> +	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
> +	    # Discard the info threads header line as well as any
> +	    # output before it in the expect buffer.
> +	    exp_continue
> +	}
> +
> +	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\)
> \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
> +	    if {[info exists expect_out(2,string)]} {
> +		set id "$expect_out(2,string).$expect_out(3,string)"
> +		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
> +			       + [expr $expect_out(3,string) - 1]]
> +	    } else {
> +		set id $expect_out(3,string)
> +		set index [expr $id - 1]
> +	    }
> +	    set frame $expect_out(4,string)
> +	    set pattern [lindex $frame_info $index]
> +	    gdb_assert {[regexp -- $pattern $frame]} \
> +		"$testname: thread $id matches"
> +	    incr thread_count
> +	    exp_continue
> +	}
> +	-re "^$gdb_prompt " {
> +	}
> +    }
> +    gdb_assert {$thread_count == [llength $frame_info]} \
> +	"$testname: all threads seen"
> +}
> +
> +# Run 'thread THREAD_NUM' and check that we switch thread.
> +proc switch_thread { thread_num } {
> +    gdb_test "thread ${thread_num}" \
> +	"Switching to thread (2\.)?${thread_num} .*"
> +}
> +
> +# Used during startup, continue the inferior and wait for all threads
> +# to stop at the breakpoint.
> +proc run_all_threads_to_breakpoint { } {
> +    global gdb_prompt
> +
> +    set stopped_thread_count 0
> +    gdb_test_multiple "continue" "wait for worker threads to stop" {
> +	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
> +	    incr stopped_thread_count
> +	    if {$stopped_thread_count < 3} {
> +		exp_continue
> +	    }
> +	}
> +
> +	-re "$gdb_prompt" {
> +	    exp_continue
> +	}
> +    }
> +
> +    gdb_assert {$stopped_thread_count == 3} \
> +	"all worker threads stopped"
> +}
> +
> +# Switch to thread #1, and interrupt it.
> +proc switch_to_and_stop_thread_1 {} {
> +    global gdb_prompt
> +    # There's a bit of a wart here in that after sending "interrupt"
> +    # the output seems to appear out of order this is probably a
> +    # consequence of being in non-stop mode, so this is what I'd like
> +    # to see:
> +    #
> +    #   (gdb) interrupt
> +    #   Thread 1 "...." stopped.
> +    #   (gdb)
> +    #
> +    # But what we actually see is:
> +    #
> +    #   (gdb) interrupt
> +    #   (gdb)
> +    #   Thread 1 "...." stopped.
> +    #
> +    # What happens of course is that GDB processes the interrupt,
> +    # sends a SIGSTOP to the inferior and then returns to the prompt,
> +    # at this point we process the stop event from the inferior and
> +    # print the stopped message.
> +    #
> +    # It would be nice if GDB could be smart enough to reprint the
> +    # prompt after the stop message though.
> +    #
> +    # The first 'interrupt\n' here causes the interior to stop, while
> +    # the following lone '\n' causes the prompt to be reprinted.  This
> +    # allows us to match all the output up to the final prompt,
> +    # ensuring we don't leave any stray output in expect's output
> +    # buffer.
> +    switch_thread 1
> +    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
> +	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
> +	    pass $gdb_test_name
> +	}
> +    }
> +    gdb_test_multiple "" "wait for thread 1 to stop" {
> +	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
> +	    send_gdb "\n"
> +	    gdb_test_multiple "" \
> +		"wait for prompt after thread 1 stopped" {
> +		-re ".*$gdb_prompt " {
> +		    pass $gdb_test_name
> +		}
> +	    }
> +	}
> +    }
> +}
> +
> +# Setup for this test.  Place GDB in non-stop mode, create an initial
> +# breakpoint, run all of the threads to the breakpoint, then stop
> +# thread 1 (which doesn't hit the breakpoint).
> +proc setup_for_test {} {
> +    gdb_test_no_output "set non-stop on"
> +
> +    if ![runto_main] {
> +	fail "runto main"
> +	return
> +    }
> +
> +    gdb_test_no_output "set restore-selected-frame on"
> +
> +    gdb_breakpoint "thread_level_5"
> +
> +    with_test_prefix "setup inferior 1" {
> +	# Now run the inferior, and wait for all of the expected threads
> +	# to hit the thread_level_5 breakpoint.
> +	run_all_threads_to_breakpoint
> +
> +	# The main thread will still be running at this point, waiting for
> +	# the stopped threads to finish so it can join with them.  Lets go
> +	# and interrupt it.
> +	switch_to_and_stop_thread_1
> +    }
> +}
> +
> +setup_for_test
> +
> +# We can't rely on frames being within 'pthread_join' actually being
> +# in a frame called pthread_join.  Different versions of pthreads
> +# might call the function something different.  So, just have a
> +# match all pattern.
> +set pthread_join_pattern ".*"
> +
> +set frame_info [list "$hex in ${pthread_join_pattern}" \
> +		    "thread_level_5" \
> +		    "thread_level_5" \
> +		    "thread_level_5" ]
> +
> +
> +# We now have all threads stopped in known locations.  Lets check that
> +# everyone is where we expect them to be.
> +test_info_threads "info threads #1" $frame_info
> +
> +# First, lets move thread 1.  Then check that the info threads output
> +# reflects this.
> +gdb_test "up" ".*"
> +set frame_info [lreplace $frame_info 0 0 "$hex in main"]
> +test_info_threads "info threads #2" $frame_info
> +
> +# Now lets change the other threads, one at a time, checking the
> +# output of info threads after each change.
> +foreach spec [list [list 2 5 "$hex in thread_worker"] \
> +		  [list 3 3 "$hex in thread_level_2"] \
> +		  [list 4 1 "$hex in thread_level_4"] ] {
> +    set thr [lindex $spec 0]
> +    with_test_prefix "change frame for thread $thr" {
> +	switch_thread $thr
> +	gdb_test "frame [lindex $spec 1]" ".*"
> +	set idx [expr $thr - 1]
> +	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
> +	test_info_threads "info threads #3" $frame_info
> +    }
> +}
> +
> +# Start a new inferior, and runto main.
> +gdb_test "add-inferior" "Added inferior 2 .*" \
> +    "add empty inferior 2"
> +gdb_test "inferior 2" "Switching to inferior 2 .*" \
> +    "switch to inferior 2"
> +gdb_test "file ${binfile}" ".*" "load file in inferior 2"
> +
> +with_test_prefix "start inferior 2" {
> +    # Disable deleting of breakpoints.
> +    proc delete_breakpoints {} {}
> +    runto_main
> +}
> +
> +with_test_prefix "setup inferior 2" {
> +    run_all_threads_to_breakpoint
> +    switch_to_and_stop_thread_1
> +}
> +
> +set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
> +					"thread_level_5" \
> +					"thread_level_5" \
> +					"thread_level_5" ]]
> +test_info_threads "info threads #4" $frame_info
> +
> +# Now lets change the other threads, one at a time, checking the
> +# output of info threads after each change.
> +foreach spec [list [list 2 2 "$hex in thread_level_3"] \
> +		  [list 3 2 "$hex in thread_level_3"] \
> +		  [list 4 2 "$hex in thread_level_3"] ] {
> +    set thr [lindex $spec 0]
> +    with_test_prefix "change frame for thread $thr" {
> +	switch_thread "2.$thr"
> +	gdb_test "frame [lindex $spec 1]" ".*"
> +	set idx [expr 4 + $thr - 1]
> +	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
> +	test_info_threads "info threads #5" $frame_info
> +    }
> +}
> +
> +# Now step one of the threads.  The thread that is stepped should
> +# discard its stored selected frame, but all other threads should
> +# retain their selected frame.
> +switch_thread "2.2"
> +gdb_test "step" ".*" \
> +    "step in thread 2.2"
> +set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
> +test_info_threads "info threads #6" $frame_info
> +
> +# Same again for a thread in inferior #1.
> +switch_thread "1.3"
> +gdb_test "step" ".*" \
> +    "step in thread 1.3"
> +set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
> +test_info_threads "info threads #7" $frame_info
> +
> +# Now switch to another thread that already has a frame other than its
> +# innermost selected.
> +switch_thread "1.2"
> +
> +# Now disable restoring of the selected frame.
> +gdb_test_no_output "set restore-selected-frame off"
> +
> +# And check to see which frame each thread has selected.  Our current
> +# thread shouldn't change.
> +set frame_info [list "$hex in ${pthread_join_pattern}" \
> +		    "thread_worker" \
> +		    "thread_level_5" \
> +		    "thread_level_5" \
> +		    "$hex in ${pthread_join_pattern}" \
> +		    "thread_level_5" \
> +		    "thread_level_5" \
> +		    "thread_level_5"]
> +test_info_threads "info threads #8" $frame_info
> +
> +# Now switch to some other thread, at this point GDB should forget the
> +# selected frame for thread 1.2.
> +switch_thread "1.4"
> +set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
> +test_info_threads "info threads #9" $frame_info
> +
> +# A new test that will cover 'thread apply all'.  This test ensures
> +# that any changes to the selected thread in 'thread apply all' are
> +# sticky outside of the 'thread apply all'.
> +with_test_prefix "thr apply all" {
> +    clean_restart $binfile
> +    setup_for_test
> +
> +    # Move all threads up a frame.
> +    gdb_test "thread apply all -- up" ".*" \
> +	"all threads up, first time"
> +    set frame_info [list "$hex in main" \
> +			"$hex in thread_level_4" \
> +			"$hex in thread_level_4" \
> +			"$hex in thread_level_4" ]
> +    test_info_threads "info threads #10" $frame_info
> +
> +    # Move every thread back to frame 0.
> +    gdb_test "thread apply all -- frame 0" ".*"
> +    set frame_info [list "$hex in ${pthread_join_pattern}" \
> +			"thread_level_5" \
> +			"thread_level_5" \
> +			"thread_level_5" ]
> +    test_info_threads "info threads #11" $frame_info
> +
> +    # Disable restoring the current frame.
> +    gdb_test_no_output "set restore-selected-frame off"
> +
> +    # Move all threads up a frame, no frame should change after this
> +    # though.
> +    gdb_test "thread apply all -- up" ".*" \
> +	"all threads up, second time"
> +    set frame_info [list "$hex in ${pthread_join_pattern}" \
> +			"thread_level_5" \
> +			"thread_level_5" \
> +			"thread_level_5" ]
> +    test_info_threads "info threads #12" $frame_info
> +}
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 856bdee97b3..63261247e19 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -721,7 +721,7 @@ thread_alive (thread_info *tp)
>     switched, false otherwise.  */
> 
>  static bool
> -switch_to_thread_if_alive (thread_info *thr)
> +switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
>  {
>    scoped_restore_current_thread restore_thread;
> 
> @@ -731,7 +731,7 @@ switch_to_thread_if_alive (thread_info *thr)
> 
>    if (thread_alive (thr))
>      {
> -      switch_to_thread (thr);
> +      switch_to_thread (thr, restore_previous_frame);
>        restore_thread.dont_restore ();
>        return true;
>      }
> @@ -903,7 +903,10 @@ set_executing_thread (thread_info *thr, bool executing)
>  {
>    thr->executing = executing;
>    if (executing)
> -    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
> +    {
> +      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
> +      thr->selected_frame_level = -1;
> +    }
>  }
> 
>  void
> @@ -1066,6 +1069,23 @@ thread_target_id_str (thread_info *tp)
>      return target_id;
>  }
> 
> +/* When this is true, GDB restore the thread's previously selected frame

"restore" -> "restores"

Regards
-Baris



Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Gary Kershaw
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv6 2/2] gdb: Track the current frame for each thread
  2020-12-18  8:43           ` Aktemur, Tankut Baris
@ 2021-01-04 15:07             ` Andrew Burgess
  2021-01-04 16:08               ` Eli Zaretskii
  0 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2021-01-04 15:07 UTC (permalink / raw)
  To: Aktemur, Tankut Baris; +Cc: gdb-patches

Baris, thanks for the feedback.

Revised patch is included below.  Changes are:

 - Minor English fixes through out,
 - Comments rewritten in frame.h, and
 - Additional line in frame.c:cache_selected_frame_on_thread
       tp->selected_frame_id = null_frame_id;

Re-based onto current master and re-tested.

Thanks,
Andrew

---

commit 7886677621fd820df20418d9cf40eee81d57752f
Author: Andrew Burgess <andrew.burgess@embecosm.com>
Date:   Mon Feb 24 12:50:30 2020 +0000

    gdb: Track the current frame for each thread
    
    Currently in GDB, each time a user switches between threads, the inner
    most frame of the thread being switched to is selected.  In some
    situations however, it might be helpful for a user to have GDB
    remember which frame was selected in each thread, and restore this
    frame as the user switches between threads.
    
    This commit adds the following two commands:
    
      set restore-selected-frame on|off
      show restore-selected-frame
    
    This new option is off by default, so the default behaviour of GDB is
    unchanged.
    
    However, with this option turned on GDB will remember, and restore the
    selected frame for each thread.
    
    My initial motivation for this change was to have the thread restored
    when switching threads with 'thread <num>', however, as I started to
    work on this feature I realised that there were a couple of other
    places where the sticky frame would naturally appear.  These are 'info
    threads' and 'thread apply all'.
    
    With 'info threads' the output contains a 'Frame' column.  Previously,
    this was always the innermost frame, the info threads output was
    created by switching to each thread in turn and collecting information
    about the thread, this naturally placed us at the innermost frame.
    Now, the 'Frame' column displays the _selected_ frame for each
    thread.
    
    I struggled to decide if this change was good or not.  In the end I
    felt that having 'info threads' display the selected frame would feel
    more natural, that's the frame you'll end up in if you switch to that
    thread, so it seemed to make sense.  However, it would be easy enough
    to force the old behaviour if people would prefer.  Alternatively I
    could even investigate adding a switch to 'info threads' that allows
    the user to select displaying either the selected frame, or the
    innermost frame.
    
    For 'thread apply all', again, we used to always apply to the
    innermost frame.  Now it's possible for a user to adjust which frame
    will be current when the 'thread apply all' runs - this feels like a
    useful change to me.  It's easy enough to quickly restore the
    innermost frame if required ('thread apply all -- frame 0') and having
    the flexibility to tweak the selected frame in just some threads feels
    like a nice advantage.  Again, I could potentially add a command flag
    here to force the innermost frame.
    
    gdb/ChangeLog:
    
            * NEWS: Describe new feature.
            * frame.c (cache_selected_frame_on_thread): New function.
            (select_frame): Call new function.
            * frame.h (get_current_frame): Update comment.
            (get_selected_frame): Update comment.
            * gdbthread.h (class thread_info) <selected_frame_id>: New
            member variable.
            <selected_frame_level>: Likewise.
            (switch_to_thread): Extra parameter.
            * thread.c (switch_to_thread_if_alive): Extra parameter, passed to
            switch_to_thread.
            (scoped_restore_current_thread::restore): Restore the frame either
            from the thread, or from the local object.
            (set_executing_thread): Reset the currently selected frame.
            (restore_selected_frame_per_thread): New file level static variable.
            (show_restore_selected_frame_per_thread): New function.
            (print_thread_info_1): Pass extra parameter to switch_to_thread.
            (switch_to_thread): Take extra parameter, restore the previous
            frame if appropriate.
            (thread_apply_all_command): Pass extra parameter to switch_to_thread.
            (thread_apply_command): Likewise.
            (thread_select): Pass extra parameter to switch_to_thread_if_alive.
            (_initialize_thread): Add new set/show variable.
    
    gdb/doc/ChangeLog:
    
            * gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
            the Frame column of 'info threads' more.  Describe which frame is
            selected when switching threads, and document the new option for
            restoring the previously selected frame.
    
    gdb/testsuite/ChangeLog:
    
            * gdb.threads/restore-selected-frame.c: New file.
            * gdb.threads/restore-selected-frame.exp: New file.

diff --git a/gdb/NEWS b/gdb/NEWS
index 9850c627fd6..1b7010026c5 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -56,6 +56,17 @@ show restore-selected-thread
   available, then GDB falls back to selecting the first non-exited
   thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+
+  When turned on, GDB will record the currently selected frame in each
+  thread.  When switching between threads, GDB will attempt to restore
+  the previously selected frame in the thread being switched too.
+  Executing a thread will cause GDB to discard any previously selected
+  frame (GDB will select the inner most frame the next time the thread
+  stops).  The 'info threads' command will show the selected frame in
+  its 'frame' field.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 9356202368d..5a3366c8b7f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3598,6 +3598,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3625,7 +3626,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the innermost
+frame for the thread (@pxref{set restore-selected-frame} to display the
+threads selected frame instead).
 @end enumerate
 
 @noindent
@@ -3697,6 +3700,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads, @value{GDBN} will select the innermost
+frame in the thread being switched too (@pxref{set restore-selected-frame}
+to change this behaviour).
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running, then when it stops, the previously
+selected frame is discarded, and the innermost frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index fed58717c95..b8ea8141f06 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1862,12 +1862,41 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* Only record the selected frame if the level is greater than 0.  If
+	 the user has the inner most frame selected then we should always
+	 restore the inner most frame, even if the frame-id changes.  This
+	 matches the behaviour of the global SELECTED_FRAME_ID and
+	 SELECTED_FRAME_LEVEL.  */
+      if (frame_relative_level (fi) > 0)
+	save_selected_frame (&tp->selected_frame_id,
+			     &tp->selected_frame_level);
+      else
+	{
+	  tp->selected_frame_level = -1;
+	  tp->selected_frame_id = null_frame_id;
+	}
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the selected frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
   selected_frame_level = frame_relative_level (fi);
   if (selected_frame_level == 0)
     {
diff --git a/gdb/frame.h b/gdb/frame.h
index 7028b2635ad..9f6465849e6 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -289,19 +289,29 @@ enum frame_type
   SENTINEL_FRAME
 };
 
-/* For every stopped thread, GDB tracks two frames: current and
-   selected.  Current frame is the inner most frame of the selected
-   thread.  Selected frame is the one being examined by the GDB
-   CLI (selected using `up', `down', ...).  The frames are created
-   on-demand (via get_prev_frame()) and then held in a frame cache.  */
-/* FIXME: cagney/2002-11-28: Er, there is a lie here.  If you do the
-   sequence: `thread 1; up; thread 2; thread 1' you lose thread 1's
-   selected frame.  At present GDB only tracks the selected frame of
-   the current thread.  But be warned, that might change.  */
-/* FIXME: cagney/2002-11-14: At any time, only one thread's selected
-   and current frame can be active.  Switching threads causes gdb to
-   discard all that cached frame information.  Ulgh!  Instead, current
-   and selected frame should be bound to a thread.  */
+/* When stopped GDB tracks two frames, the current frame and the selected
+   frame.  The current frame is the inner most frame of the currently
+   selected thread, and the selected frame is the one being examined by GDB
+   (selected using `up', `down', ...), again, in the currently selected
+   thread.
+
+   The frames are created on-demand (via get_prev_frame()) and then held in
+   a frame cache.
+
+   Switching threads causes GDB to discard all cached frame information.
+   Ideally the current and selected frames might be stored within the
+   thread object, this is not currently done as invalidating the frame
+   cache would require GDB to visit each thread to invalidate its cached
+   information.
+
+   Each thread does cache the frame-id of its selected frame.  When
+   switching threads GDB can (optionally, see 'set restore-selected-frame')
+   restore the previous selected frame by looking for a frame with a
+   matching frame-id.  The default behaviour is to not restore the selected
+   frame when switching threads, this means that doing 'thread 1; up;
+   thread 2; thread 1' would leave the user in thread 1, frame 0.  When
+   setting restore-selected-frame on then the same commands will leave the
+   user in thread 1, frame 1.  */
 
 /* On demand, create the inner most frame using information found in
    the inferior.  If the inner most frame can't be created, throw an
@@ -325,12 +335,10 @@ extern void reinit_frame_cache (void);
 /* Return the selected frame.  Always returns non-NULL.  If there
    isn't an inferior sufficient for creating a frame, an error is
    thrown.  When MESSAGE is non-NULL, use it for the error message,
-   otherwise use a generic error message.  */
-/* FIXME: cagney/2002-11-28: At present, when there is no selected
-   frame, this function always returns the current (inner most) frame.
-   It should instead, when a thread has previously had its frame
-   selected (but not resumed) and the frame cache invalidated, find
-   and then return that thread's previously selected frame.  */
+   otherwise use a generic error message.
+
+   If no frame has previously been selected then the inner most frame will
+   be returned.  */
 extern struct frame_info *get_selected_frame (const char *message = nullptr);
 
 /* Select a specific frame.  NULL implies re-select the inner most
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index eef37f79e6a..324866171ab 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -370,6 +370,12 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id selected_frame_id {};
+  int selected_frame_level = -1;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -575,8 +581,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 899c2116470..cf723635645 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -698,7 +698,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -708,7 +708,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -880,7 +880,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_level = -1;
+    }
 }
 
 void
@@ -1043,6 +1046,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true, GDB restores the thread's previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1155,7 +1175,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1337,7 +1358,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1347,6 +1368,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_level > -1)
+    restore_selected_frame (thr->selected_frame_id,
+			    thr->selected_frame_level);
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1372,13 +1397,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers ()
@@ -1611,7 +1639,8 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info_ref &thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr.get ()))
+	if (switch_to_thread_if_alive (thr.get (),
+				       restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr.get (), cmd, from_tty, flags);
     }
 }
@@ -1768,7 +1797,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1942,7 +1971,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2209,6 +2238,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on, GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv6 2/2] gdb: Track the current frame for each thread
  2021-01-04 15:07             ` Andrew Burgess
@ 2021-01-04 16:08               ` Eli Zaretskii
  0 siblings, 0 replies; 28+ messages in thread
From: Eli Zaretskii @ 2021-01-04 16:08 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: tankut.baris.aktemur, gdb-patches

> Date: Mon, 4 Jan 2021 15:07:55 +0000
> From: Andrew Burgess <andrew.burgess@embecosm.com>
> Cc: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
>     gdb/ChangeLog:
>     
>             * NEWS: Describe new feature.
>             * frame.c (cache_selected_frame_on_thread): New function.
>             (select_frame): Call new function.
>             * frame.h (get_current_frame): Update comment.
>             (get_selected_frame): Update comment.
>             * gdbthread.h (class thread_info) <selected_frame_id>: New
>             member variable.
>             <selected_frame_level>: Likewise.
>             (switch_to_thread): Extra parameter.
>             * thread.c (switch_to_thread_if_alive): Extra parameter, passed to
>             switch_to_thread.
>             (scoped_restore_current_thread::restore): Restore the frame either
>             from the thread, or from the local object.
>             (set_executing_thread): Reset the currently selected frame.
>             (restore_selected_frame_per_thread): New file level static variable.
>             (show_restore_selected_frame_per_thread): New function.
>             (print_thread_info_1): Pass extra parameter to switch_to_thread.
>             (switch_to_thread): Take extra parameter, restore the previous
>             frame if appropriate.
>             (thread_apply_all_command): Pass extra parameter to switch_to_thread.
>             (thread_apply_command): Likewise.
>             (thread_select): Pass extra parameter to switch_to_thread_if_alive.
>             (_initialize_thread): Add new set/show variable.
>     
>     gdb/doc/ChangeLog:
>     
>             * gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
>             the Frame column of 'info threads' more.  Describe which frame is
>             selected when switching threads, and document the new option for
>             restoring the previously selected frame.
>     
>     gdb/testsuite/ChangeLog:
>     
>             * gdb.threads/restore-selected-frame.c: New file.
>             * gdb.threads/restore-selected-frame.exp: New file.

OK for the documentation parts, thanks.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv6 0/2] Restore thread and frame patches
  2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
  2020-12-10 11:39         ` [PATCHv6 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
  2020-12-10 11:39         ` [PATCHv6 2/2] gdb: Track the current frame for each thread Andrew Burgess
@ 2021-01-07 10:25         ` Andrew Burgess
  2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
  3 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2021-01-07 10:25 UTC (permalink / raw)
  To: gdb-patches

Ping!

I'd like to ask if anyone has any objections to these patches being
merged?

My original attempt to merge at least part of this series[1] back in
March last year did get some positive feedback, and I was about to
merge until Pedro objected.

I do honestly believe that I have now addressed Pedro's objections
which I interpreted as:

 1. Changing existing default behaviour might be confusing:
    - This feature is off by default now.

 2. Restoring "some" bits of state without restoring "all" bits of
    state (examples given were fibers, coroutines, etc) would be
    confusing.
    - Again, by having this off by default a user has to opt in to
      restore those features that we support restoring.  As a result
      it should not (I think) be confusing.  That is, if a user turns
      on "restore thread" then of course only threads will be
      restored.  They might then complain that there's no "restore
      coroutine" or "restore fiber" flag, but I'd be OK with that.

  3. Pedro expressed that he finds the current behaviour more
     intuitive.
     - I respectively disagree, but again, by being off be default I
       think anyone who prefers the current behaviour will not be put
       out.

I've not heard back from Pedro since I posted the latest version of
this series in Sep last year, but I would like to hear if anyone has
either new concerns about this change, or if anyone feels that Pedro's
original concerns have not been addressed.

Thanks for your time,
Andrew

[1] https://sourceware.org/pipermail/gdb-patches/2020-March/167061.html


* Andrew Burgess <andrew.burgess@embecosm.com> [2020-12-10 11:39:03 +0000]:

> Compared to v5:
> 
>   + Minor tweak to one comment in patch #2 (added a comma).
>   + Rebased onto current master.
> 
> Still hoping for Pedro to review this series in comparison to the
> original patches he objected to.
> 
> Thanks,
> Andrew
> 
> 
> ---
> 
> Andrew Burgess (2):
>   gdb: Restore previously selected thread when switching inferior
>   gdb: Track the current frame for each thread
> 
>  gdb/ChangeLog                                 |  34 ++
>  gdb/NEWS                                      |  20 ++
>  gdb/doc/ChangeLog                             |  14 +
>  gdb/doc/gdb.texinfo                           |  42 ++-
>  gdb/frame.c                                   |  26 ++
>  gdb/gdbthread.h                               |  13 +-
>  gdb/inferior.c                                |  58 ++-
>  gdb/inferior.h                                |   9 +
>  gdb/testsuite/ChangeLog                       |  10 +
>  .../gdb.threads/restore-selected-frame.c      |  85 +++++
>  .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
>  gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
>  gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
>  gdb/thread.c                                  |  61 +++-
>  14 files changed, 1160 insertions(+), 15 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
>  create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp
> 
> -- 
> 2.25.4
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv7 0/2] Restore thread and frame patches
  2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
                           ` (2 preceding siblings ...)
  2021-01-07 10:25         ` [PATCHv6 0/2] Restore thread and frame patches Andrew Burgess
@ 2021-02-12 18:20         ` Andrew Burgess
  2021-02-12 18:20           ` [PATCHv7 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
                             ` (2 more replies)
  3 siblings, 3 replies; 28+ messages in thread
From: Andrew Burgess @ 2021-02-12 18:20 UTC (permalink / raw)
  To: gdb-patches

In comparison to v7:

  - Update to current master, and retest.

Thanks,
Andrew

---

Andrew Burgess (2):
  gdb: Restore previously selected thread when switching inferior
  gdb: Track the current frame for each thread

 gdb/ChangeLog                                 |  36 ++
 gdb/NEWS                                      |  20 ++
 gdb/doc/ChangeLog                             |  14 +
 gdb/doc/gdb.texinfo                           |  42 ++-
 gdb/frame.c                                   |  29 ++
 gdb/frame.h                                   |  46 ++-
 gdb/gdbthread.h                               |  13 +-
 gdb/inferior.c                                |  60 +++-
 gdb/inferior.h                                |   9 +
 gdb/testsuite/ChangeLog                       |  10 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.c    | 248 +++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp  | 219 ++++++++++++
 gdb/thread.c                                  |  62 +++-
 15 files changed, 1195 insertions(+), 34 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv7 1/2] gdb: Restore previously selected thread when switching inferior
  2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
@ 2021-02-12 18:20           ` Andrew Burgess
  2021-02-12 18:20           ` [PATCHv7 2/2] gdb: Track the current frame for each thread Andrew Burgess
  2021-03-23 11:13           ` [PATCHv8] gdb: Restore previously selected thread when switching inferior Andrew Burgess
  2 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2021-02-12 18:20 UTC (permalink / raw)
  To: gdb-patches

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I did consider, but eventually didn't implemented, adding a warning
when switching inferiors if the previously selected thread is no
longer available.  My reasoning here is that GDB should already have
informed the user that the thread has exited, and there is already a
message indicating which thread has been switched too, so adding an
extra warning felt like unneeded clutter.

In order to store the thread within the inferior I store a pointer to
the thread_info object of the previously selected thread.  When
fetching the thread_info it is important that we do actually have a
current thread otherwise this happens:

  $ gdb
  (gdb) add-inferior
  (gdb) inferior 2
  ./gdb/thread.c:95: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.

To avoid this I added a check that inferior_ptid is not null_ptid.
Though it is not always the case, there are plenty of places in GDB
where a call to inferior_thread () is guarded by such a check.

There's a new test for this functionality.

gdb/ChangeLog:

	* inferior.c (inferior_command): Store current thread_info before
	switching inferiors.  Reselect the previous thread_info if
	possible after switching to the new inferior.
	(initialize_inferiors): Register restore-selected-thread option.
	* inferior.h (class inferior) <previous_thread_info>: New member
	variable.
	* NEWS: Mention new feature.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
---
 gdb/ChangeLog                                |  10 +
 gdb/NEWS                                     |   9 +
 gdb/doc/ChangeLog                            |   7 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/inferior.c                               |  60 ++++-
 gdb/inferior.h                               |   9 +
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 9 files changed, 584 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 1dfbbc648eb..5e15ef1f88e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -47,6 +47,15 @@ maintenance flush register-cache
 maintenance flush dcache
   A new command to flush the dcache.
 
+set restore-selected-thread on|off
+show restore-selected-thread
+  When turned on, GDB will record the currently selected thread in
+  each inferior.  When switching between inferiors, GDB will attempt
+  to restore the previously selected thread in the inferior being
+  switched too.  If the previously selected thread is no longer
+  available, then GDB falls back to selecting the first non-exited
+  thread.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 0b1deba9667..2a302c76d55 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3179,11 +3179,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads (@pxref{Threads}),
+@value{GDBN} will select the first non-exited thread in the inferior being
+switched to, and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on, @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferiors, @value{GDBN}
+will try to restore the previously selected thread in the inferior being
+switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3565,7 +3579,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors, @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command}, for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 49f869a4c78..bdb4ab9af80 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -629,6 +629,23 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* When this is true, GDB restores the inferior's previously selected
+   thread each time the inferior is changed (where possible).  */
+
+static bool restore_selected_thread_per_inferior = false;
+
+/* Implement 'show restore-selected-thread'.  */
+
+static void
+show_restore_selected_thread_per_inferior (struct ui_file *file, int from_tty,
+					   struct cmd_list_element *c,
+					   const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected thread is currently %s.\n"),
+		    value);
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
@@ -656,11 +673,40 @@ inferior_command (const char *args, int from_tty)
       if (inf == NULL)
 	error (_("Inferior ID %d not known."), num);
 
+      /* We can only call INFERIOR_THREAD if the inferior is known to have
+	 an active thread, which it wont if the inferior is currently
+	 exited.  So, first check if we currently have a thread selected.  */
+      if (inferior_ptid != null_ptid)
+	{
+	  /* Now take a strong reference to the current thread_info and
+	     store it within the inferior, this prevents the thread_info
+	     from being deleted until the inferior has released the
+	     reference.  */
+	  thread_info *tp = inferior_thread ();
+	  tp->incref ();
+	  current_inferior ()->previous_thread_info.reset (tp);
+	}
+
       if (inf->pid != 0)
 	{
 	  if (inf != current_inferior ())
 	    {
-	      thread_info *tp = any_thread_of_inferior (inf);
+	      thread_info *tp = nullptr;
+
+	      if (restore_selected_thread_per_inferior
+		  && inf->previous_thread_info != nullptr)
+		{
+		  /* Release the reference to the previous thread.  We don't
+		     switch back to this thread if it is already exited
+		     though.  */
+		  tp = inf->previous_thread_info.release ();
+		  tp->decref ();
+		  if (tp->state == THREAD_EXITED)
+		    tp = nullptr;
+		}
+	      if (tp == nullptr)
+		tp = any_thread_of_inferior (inf);
+
 	      if (tp == NULL)
 		error (_("Inferior has no threads."));
 
@@ -1038,5 +1084,17 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-thread",
+			   no_class, &restore_selected_thread_per_inferior,
+                          _("\
+Set whether GDB restores the selected thread when switching inferiors."), _("\
+Show whether GDB restores the selected thread when switching inferiors."), _("\
+When this option is on, GDB will record the currently selected thread for\n\
+each inferior, and restore the selected thread whenever GDB switches inferiors."),
+                          nullptr,
+                          show_restore_selected_thread_per_inferior,
+                          &setlist,
+                          &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index b8d5ff94fc5..f29ca787ad2 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -544,6 +544,15 @@ class inferior : public refcounted_object
   /* Data related to displaced stepping.  */
   displaced_step_inferior_state displaced_step_state;
 
+  /* This field is updated when GDB switches away from this inferior to
+     some other inferior.  Holding a reference to the previously selected
+     thread prevents GDB from deleting the thread_info (until the inferior
+     itself is deleted).
+
+     When the user switches back to this inferior this reference is used to
+     (possibly) restore the selected thread.  */
+  thread_info_ref previous_thread_info;
+
   /* Per inferior data-pointers required by other GDB modules.  */
   REGISTRY_FIELDS;
 
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv7 2/2] gdb: Track the current frame for each thread
  2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
  2021-02-12 18:20           ` [PATCHv7 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2021-02-12 18:20           ` Andrew Burgess
  2021-03-23 11:13           ` [PATCHv8] gdb: Restore previously selected thread when switching inferior Andrew Burgess
  2 siblings, 0 replies; 28+ messages in thread
From: Andrew Burgess @ 2021-02-12 18:20 UTC (permalink / raw)
  To: gdb-patches

Currently in GDB, each time a user switches between threads, the inner
most frame of the thread being switched to is selected.  In some
situations however, it might be helpful for a user to have GDB
remember which frame was selected in each thread, and restore this
frame as the user switches between threads.

This commit adds the following two commands:

  set restore-selected-frame on|off
  show restore-selected-frame

This new option is off by default, so the default behaviour of GDB is
unchanged.

However, with this option turned on GDB will remember, and restore the
selected frame for each thread.

My initial motivation for this change was to have the thread restored
when switching threads with 'thread <num>', however, as I started to
work on this feature I realised that there were a couple of other
places where the sticky frame would naturally appear.  These are 'info
threads' and 'thread apply all'.

With 'info threads' the output contains a 'Frame' column.  Previously,
this was always the innermost frame, the info threads output was
created by switching to each thread in turn and collecting information
about the thread, this naturally placed us at the innermost frame.
Now, the 'Frame' column displays the _selected_ frame for each
thread.

I struggled to decide if this change was good or not.  In the end I
felt that having 'info threads' display the selected frame would feel
more natural, that's the frame you'll end up in if you switch to that
thread, so it seemed to make sense.  However, it would be easy enough
to force the old behaviour if people would prefer.  Alternatively I
could even investigate adding a switch to 'info threads' that allows
the user to select displaying either the selected frame, or the
innermost frame.

For 'thread apply all', again, we used to always apply to the
innermost frame.  Now it's possible for a user to adjust which frame
will be current when the 'thread apply all' runs - this feels like a
useful change to me.  It's easy enough to quickly restore the
innermost frame if required ('thread apply all -- frame 0') and having
the flexibility to tweak the selected frame in just some threads feels
like a nice advantage.  Again, I could potentially add a command flag
here to force the innermost frame.

gdb/ChangeLog:

	* NEWS: Describe new feature.
	* frame.c (cache_selected_frame_on_thread): New function.
	(select_frame): Call new function.
	* frame.h (get_current_frame): Update comment.
	(get_selected_frame): Update comment.
	* gdbthread.h (class thread_info) <selected_frame_id>: New
	member variable.
	<selected_frame_level>: Likewise.
	(switch_to_thread): Extra parameter.
	* thread.c (switch_to_thread_if_alive): Extra parameter, passed to
	switch_to_thread.
	(scoped_restore_current_thread::restore): Restore the frame either
	from the thread, or from the local object.
	(set_executing_thread): Reset the currently selected frame.
	(restore_selected_frame_per_thread): New file level static variable.
	(show_restore_selected_frame_per_thread): New function.
	(print_thread_info_1): Pass extra parameter to switch_to_thread.
	(switch_to_thread): Take extra parameter, restore the previous
	frame if appropriate.
	(thread_apply_all_command): Pass extra parameter to switch_to_thread.
	(thread_apply_command): Likewise.
	(thread_select): Pass extra parameter to switch_to_thread_if_alive.
	(_initialize_thread): Add new set/show variable.

gdb/doc/ChangeLog:

	* gdb.texinfo (Threads): Add anchor to 'info threads'.  Describe
	the Frame column of 'info threads' more.  Describe which frame is
	selected when switching threads, and document the new option for
	restoring the previously selected frame.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-selected-frame.c: New file.
	* gdb.threads/restore-selected-frame.exp: New file.
---
 gdb/ChangeLog                                 |  26 ++
 gdb/NEWS                                      |  11 +
 gdb/doc/ChangeLog                             |   7 +
 gdb/doc/gdb.texinfo                           |  23 +-
 gdb/frame.c                                   |  29 ++
 gdb/frame.h                                   |  46 ++-
 gdb/gdbthread.h                               |  13 +-
 gdb/testsuite/ChangeLog                       |   5 +
 .../gdb.threads/restore-selected-frame.c      |  85 +++++
 .../gdb.threads/restore-selected-frame.exp    | 336 ++++++++++++++++++
 gdb/thread.c                                  |  62 +++-
 11 files changed, 611 insertions(+), 32 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-selected-frame.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 5e15ef1f88e..9322b9e50c9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -56,6 +56,17 @@ show restore-selected-thread
   available, then GDB falls back to selecting the first non-exited
   thread.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+
+  When turned on, GDB will record the currently selected frame in each
+  thread.  When switching between threads, GDB will attempt to restore
+  the previously selected frame in the thread being switched too.
+  Executing a thread will cause GDB to discard any previously selected
+  frame (GDB will select the inner most frame the next time the thread
+  stops).  The 'info threads' command will show the selected frame in
+  its 'frame' field.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 2a302c76d55..717890581c8 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3530,6 +3530,7 @@
 @end smallexample
 
 @table @code
+@anchor{info threads}
 @kindex info threads
 @item info threads @r{[}@var{thread-id-list}@r{]}
 
@@ -3557,7 +3558,9 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the innermost
+frame for the thread (@pxref{set restore-selected-frame} to display the
+threads selected frame instead).
 @end enumerate
 
 @noindent
@@ -3629,6 +3632,24 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+When switching between threads, @value{GDBN} will select the innermost
+frame in the thread being switched too (@pxref{set restore-selected-frame}
+to change this behaviour).
+
+@anchor{set restore-selected-frame}
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, @value{GDBN} will restore
+the previously selected frame when switching to a different thread.
+Also the @code{info threads} command (@pxref{info threads}) will display the
+currently selected frame for each thread.
+
+If a thread has been running, then when it stops, the previously
+selected frame is discarded, and the innermost frame is again
+selected.
+
+This option is @code{off} by default.
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
diff --git a/gdb/frame.c b/gdb/frame.c
index 4578b1acab3..78e55dbc6f2 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1870,12 +1870,41 @@ deprecated_safe_get_selected_frame (void)
   return get_selected_frame (NULL);
 }
 
+/* When RESTORE_SELECTED_FRAME_PER_THREAD is true, then update in the
+   current thread the information required to identify frame FI so the
+   frame can be selected again later if we switch threads.  */
+
+static void
+cache_selected_frame_on_thread ()
+{
+  struct frame_info *fi = selected_frame;
+  struct thread_info *tp
+    = find_thread_ptid (current_inferior (), inferior_ptid);
+  if (fi != nullptr && tp != nullptr)
+    {
+      /* Only record the selected frame if the level is greater than 0.  If
+	 the user has the inner most frame selected then we should always
+	 restore the inner most frame, even if the frame-id changes.  This
+	 matches the behaviour of the global SELECTED_FRAME_ID and
+	 SELECTED_FRAME_LEVEL.  */
+      if (frame_relative_level (fi) > 0)
+	save_selected_frame (&tp->selected_frame_id,
+			     &tp->selected_frame_level);
+      else
+	{
+	  tp->selected_frame_level = -1;
+	  tp->selected_frame_id = null_frame_id;
+	}
+    }
+}
+
 /* Select frame FI (or NULL - to invalidate the selected frame).  */
 
 void
 select_frame (struct frame_info *fi)
 {
   selected_frame = fi;
+  cache_selected_frame_on_thread ();
   selected_frame_level = frame_relative_level (fi);
   if (selected_frame_level == 0)
     {
diff --git a/gdb/frame.h b/gdb/frame.h
index 597a45967ed..9de53e493f3 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -289,19 +289,29 @@ enum frame_type
   SENTINEL_FRAME
 };
 
-/* For every stopped thread, GDB tracks two frames: current and
-   selected.  Current frame is the inner most frame of the selected
-   thread.  Selected frame is the one being examined by the GDB
-   CLI (selected using `up', `down', ...).  The frames are created
-   on-demand (via get_prev_frame()) and then held in a frame cache.  */
-/* FIXME: cagney/2002-11-28: Er, there is a lie here.  If you do the
-   sequence: `thread 1; up; thread 2; thread 1' you lose thread 1's
-   selected frame.  At present GDB only tracks the selected frame of
-   the current thread.  But be warned, that might change.  */
-/* FIXME: cagney/2002-11-14: At any time, only one thread's selected
-   and current frame can be active.  Switching threads causes gdb to
-   discard all that cached frame information.  Ulgh!  Instead, current
-   and selected frame should be bound to a thread.  */
+/* When stopped GDB tracks two frames, the current frame and the selected
+   frame.  The current frame is the inner most frame of the currently
+   selected thread, and the selected frame is the one being examined by GDB
+   (selected using `up', `down', ...), again, in the currently selected
+   thread.
+
+   The frames are created on-demand (via get_prev_frame()) and then held in
+   a frame cache.
+
+   Switching threads causes GDB to discard all cached frame information.
+   Ideally the current and selected frames might be stored within the
+   thread object, this is not currently done as invalidating the frame
+   cache would require GDB to visit each thread to invalidate its cached
+   information.
+
+   Each thread does cache the frame-id of its selected frame.  When
+   switching threads GDB can (optionally, see 'set restore-selected-frame')
+   restore the previous selected frame by looking for a frame with a
+   matching frame-id.  The default behaviour is to not restore the selected
+   frame when switching threads, this means that doing 'thread 1; up;
+   thread 2; thread 1' would leave the user in thread 1, frame 0.  When
+   setting restore-selected-frame on then the same commands will leave the
+   user in thread 1, frame 1.  */
 
 /* On demand, create the inner most frame using information found in
    the inferior.  If the inner most frame can't be created, throw an
@@ -325,12 +335,10 @@ extern void reinit_frame_cache (void);
 /* Return the selected frame.  Always returns non-NULL.  If there
    isn't an inferior sufficient for creating a frame, an error is
    thrown.  When MESSAGE is non-NULL, use it for the error message,
-   otherwise use a generic error message.  */
-/* FIXME: cagney/2002-11-28: At present, when there is no selected
-   frame, this function always returns the current (inner most) frame.
-   It should instead, when a thread has previously had its frame
-   selected (but not resumed) and the frame cache invalidated, find
-   and then return that thread's previously selected frame.  */
+   otherwise use a generic error message.
+
+   If no frame has previously been selected then the inner most frame will
+   be returned.  */
 extern struct frame_info *get_selected_frame (const char *message = nullptr);
 
 /* Select a specific frame.  NULL implies re-select the inner most
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index eef37f79e6a..324866171ab 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -370,6 +370,12 @@ class thread_info : public refcounted_object
      bp_longjmp_call_dummy.  */
   struct frame_id initiating_frame = null_frame_id;
 
+  /* Information for the last frame successfully selected in this thread.
+     If the user configurable setting is on then GDB will try to reselect
+     this frame when switching threads.  */
+  struct frame_id selected_frame_id {};
+  int selected_frame_level = -1;
+
   /* Private data used by the target vector implementation.  */
   std::unique_ptr<private_thread_info> priv;
 
@@ -575,8 +581,11 @@ extern int thread_count (process_stratum_target *proc_target);
 /* Return true if we have any thread in any inferior.  */
 extern bool any_thread_p ();
 
-/* Switch context to thread THR.  Also sets the STOP_PC global.  */
-extern void switch_to_thread (struct thread_info *thr);
+/* Switch context to thread THR.  Also sets the STOP_PC global.  When
+   RESTORE_PREVIOUS_FRAME is true then, if this thread has a previously
+   selected frame cached, the previous frame is restored.  */
+extern void switch_to_thread (struct thread_info *thr,
+			      bool restore_previous_frame = false);
 
 /* Switch context to no thread selected.  */
 extern void switch_to_no_thread ();
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.c b/gdb/testsuite/gdb.threads/restore-selected-frame.c
new file mode 100644
index 00000000000..c72b0b8b54b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int loop_count = 10;
+volatile int thread_count = 3;
+
+static void
+thread_level_5 (int id, int count)
+{
+  printf ("Thread %d reached %s, #%d\n",
+	  id, __PRETTY_FUNCTION__, count);
+}
+
+static void
+thread_level_4 (int id, int count)
+{
+  thread_level_5 (id, count);
+}
+
+static void
+thread_level_3 (int id, int count)
+{
+  thread_level_4 (id, count);
+}
+
+static void
+thread_level_2 (int id, int count)
+{
+  thread_level_3 (id, count);
+}
+
+static void
+thread_level_1 (int id, int count)
+{
+  thread_level_2 (id, count);
+}
+
+static void *
+thread_worker (void *arg)
+{
+  int i, max, id;
+
+  id = *((int *) arg);
+  max = loop_count;
+  for (i = 0; i < max; ++i)
+    thread_level_1 (id, (i + 1));
+
+  return NULL;
+}
+
+struct thread_info
+{
+  pthread_t thread;
+  int id;
+};
+
+int
+main ()
+{
+  int i, max = thread_count;
+
+  struct thread_info *info = malloc (sizeof (struct thread_info) * max);
+  if (info == NULL)
+    abort ();
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      thr->id = i + 1;
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->id) != 0)
+	abort ();
+    }
+
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_info *thr = &info[i];
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+    }
+
+  free (info);
+}
diff --git a/gdb/testsuite/gdb.threads/restore-selected-frame.exp b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
new file mode 100644
index 00000000000..b40386fc58e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,336 @@
+# Copyright 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/>.
+
+# This tests GDB's tracking of the currently selected frame on a
+# per-thread basis.
+#
+# We setup a couple of inferiors, each with mutliple theads, we then
+# switch between threads and modify the current frame.  We use 'info
+# threads' to check that GDB is correctly tracking the current frame.
+#
+# Toward the end of the test we check that when a thread executes the
+# currently selected frame is reset.
+#
+# Finally we disable tracking of the currently selected frame and
+# ensure GDB no longer restores the current frame.
+
+standard_testfile
+
+set options { debug pthreads }
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 $options] == -1} {
+    return -1
+}
+
+# Run the 'info threads' command, and check that the frame part of
+# each threads output matches the corresponding pattern in FRAME_INFO,
+# with thread 1 using entry 0 from FRAME_INFO, thread 2 using entry 1,
+# and so on.
+proc test_info_threads { testname frame_info } {
+    global decimal hex gdb_prompt
+
+    set thread_count 0
+    gdb_test_multiple "info threads" ${testname} {
+	-re ".*  Id\\s+Target Id\\s+Frame\\s*\r\n" {
+	    # Discard the info threads header line as well as any
+	    # output before it in the expect buffer.
+	    exp_continue
+	}
+
+	-re "^\[* \]\\s+(($decimal)\.)?($decimal)\\s+Thread $hex \\(LWP $decimal\\) \"\[^\"\]+\"\\s+(\[^\r\n\]*)\r\n" {
+	    if {[info exists expect_out(2,string)]} {
+		set id "$expect_out(2,string).$expect_out(3,string)"
+		set index [expr [expr [expr $expect_out(2,string) - 1] * 4] \
+			       + [expr $expect_out(3,string) - 1]]
+	    } else {
+		set id $expect_out(3,string)
+		set index [expr $id - 1]
+	    }
+	    set frame $expect_out(4,string)
+	    set pattern [lindex $frame_info $index]
+	    gdb_assert {[regexp -- $pattern $frame]} \
+		"$testname: thread $id matches"
+	    incr thread_count
+	    exp_continue
+	}
+	-re "^$gdb_prompt " {
+	}
+    }
+    gdb_assert {$thread_count == [llength $frame_info]} \
+	"$testname: all threads seen"
+}
+
+# Run 'thread THREAD_NUM' and check that we switch thread.
+proc switch_thread { thread_num } {
+    gdb_test "thread ${thread_num}" \
+	"Switching to thread (2\.)?${thread_num} .*"
+}
+
+# Used during startup, continue the inferior and wait for all threads
+# to stop at the breakpoint.
+proc run_all_threads_to_breakpoint { } {
+    global gdb_prompt
+
+    set stopped_thread_count 0
+    gdb_test_multiple "continue" "wait for worker threads to stop" {
+	-re "Thread (2\.)?\[234\] \"\[^\"\]+\" hit Breakpoint" {
+	    incr stopped_thread_count
+	    if {$stopped_thread_count < 3} {
+		exp_continue
+	    }
+	}
+
+	-re "$gdb_prompt" {
+	    exp_continue
+	}
+    }
+
+    gdb_assert {$stopped_thread_count == 3} \
+	"all worker threads stopped"
+}
+
+# Switch to thread #1, and interrupt it.
+proc switch_to_and_stop_thread_1 {} {
+    global gdb_prompt
+    # There's a bit of a wart here in that after sending "interrupt"
+    # the output seems to appear out of order this is probably a
+    # consequence of being in non-stop mode, so this is what I'd like
+    # to see:
+    #
+    #   (gdb) interrupt
+    #   Thread 1 "...." stopped.
+    #   (gdb)
+    #
+    # But what we actually see is:
+    #
+    #   (gdb) interrupt
+    #   (gdb)
+    #   Thread 1 "...." stopped.
+    #
+    # What happens of course is that GDB processes the interrupt,
+    # sends a SIGSTOP to the inferior and then returns to the prompt,
+    # at this point we process the stop event from the inferior and
+    # print the stopped message.
+    #
+    # It would be nice if GDB could be smart enough to reprint the
+    # prompt after the stop message though.
+    #
+    # The first 'interrupt\n' here causes the interior to stop, while
+    # the following lone '\n' causes the prompt to be reprinted.  This
+    # allows us to match all the output up to the final prompt,
+    # ensuring we don't leave any stray output in expect's output
+    # buffer.
+    switch_thread 1
+    gdb_test_multiple "interrupt\\n" "interrupt thread 1" {
+	-re "^interrupt\\\\n\\r\\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+    gdb_test_multiple "" "wait for thread 1 to stop" {
+	-re "Thread (2\.)?1 \"\[^\"\]+\" stopped\." {
+	    send_gdb "\n"
+	    gdb_test_multiple "" \
+		"wait for prompt after thread 1 stopped" {
+		-re ".*$gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+}
+
+# Setup for this test.  Place GDB in non-stop mode, create an initial
+# breakpoint, run all of the threads to the breakpoint, then stop
+# thread 1 (which doesn't hit the breakpoint).
+proc setup_for_test {} {
+    gdb_test_no_output "set non-stop on"
+
+    if ![runto_main] {
+	fail "runto main"
+	return
+    }
+
+    gdb_test_no_output "set restore-selected-frame on"
+
+    gdb_breakpoint "thread_level_5"
+
+    with_test_prefix "setup inferior 1" {
+	# Now run the inferior, and wait for all of the expected threads
+	# to hit the thread_level_5 breakpoint.
+	run_all_threads_to_breakpoint
+
+	# The main thread will still be running at this point, waiting for
+	# the stopped threads to finish so it can join with them.  Lets go
+	# and interrupt it.
+	switch_to_and_stop_thread_1
+    }
+}
+
+setup_for_test
+
+# We can't rely on frames being within 'pthread_join' actually being
+# in a frame called pthread_join.  Different versions of pthreads
+# might call the function something different.  So, just have a
+# match all pattern.
+set pthread_join_pattern ".*"
+
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5" ]
+
+
+# We now have all threads stopped in known locations.  Lets check that
+# everyone is where we expect them to be.
+test_info_threads "info threads #1" $frame_info
+
+# First, lets move thread 1.  Then check that the info threads output
+# reflects this.
+gdb_test "up" ".*"
+set frame_info [lreplace $frame_info 0 0 "$hex in main"]
+test_info_threads "info threads #2" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 5 "$hex in thread_worker"] \
+		  [list 3 3 "$hex in thread_level_2"] \
+		  [list 4 1 "$hex in thread_level_4"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread $thr
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #3" $frame_info
+    }
+}
+
+# Start a new inferior, and runto main.
+gdb_test "add-inferior" "Added inferior 2 .*" \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    # Disable deleting of breakpoints.
+    proc delete_breakpoints {} {}
+    runto_main
+}
+
+with_test_prefix "setup inferior 2" {
+    run_all_threads_to_breakpoint
+    switch_to_and_stop_thread_1
+}
+
+set frame_info [concat $frame_info [list "$hex in ${pthread_join_pattern}" \
+					"thread_level_5" \
+					"thread_level_5" \
+					"thread_level_5" ]]
+test_info_threads "info threads #4" $frame_info
+
+# Now lets change the other threads, one at a time, checking the
+# output of info threads after each change.
+foreach spec [list [list 2 2 "$hex in thread_level_3"] \
+		  [list 3 2 "$hex in thread_level_3"] \
+		  [list 4 2 "$hex in thread_level_3"] ] {
+    set thr [lindex $spec 0]
+    with_test_prefix "change frame for thread $thr" {
+	switch_thread "2.$thr"
+	gdb_test "frame [lindex $spec 1]" ".*"
+	set idx [expr 4 + $thr - 1]
+	set frame_info [lreplace $frame_info $idx $idx [lindex $spec 2]]
+	test_info_threads "info threads #5" $frame_info
+    }
+}
+
+# Now step one of the threads.  The thread that is stepped should
+# discard its stored selected frame, but all other threads should
+# retain their selected frame.
+switch_thread "2.2"
+gdb_test "step" ".*" \
+    "step in thread 2.2"
+set frame_info [lreplace $frame_info 5 5 "thread_level_5"]
+test_info_threads "info threads #6" $frame_info
+
+# Same again for a thread in inferior #1.
+switch_thread "1.3"
+gdb_test "step" ".*" \
+    "step in thread 1.3"
+set frame_info [lreplace $frame_info 2 2 "thread_level_5"]
+test_info_threads "info threads #7" $frame_info
+
+# Now switch to another thread that already has a frame other than its
+# innermost selected.
+switch_thread "1.2"
+
+# Now disable restoring of the selected frame.
+gdb_test_no_output "set restore-selected-frame off"
+
+# And check to see which frame each thread has selected.  Our current
+# thread shouldn't change.
+set frame_info [list "$hex in ${pthread_join_pattern}" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in ${pthread_join_pattern}" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "thread_level_5"]
+test_info_threads "info threads #8" $frame_info
+
+# Now switch to some other thread, at this point GDB should forget the
+# selected frame for thread 1.2.
+switch_thread "1.4"
+set frame_info [lreplace $frame_info 1 1 "thread_level_5"]
+test_info_threads "info threads #9" $frame_info
+
+# A new test that will cover 'thread apply all'.  This test ensures
+# that any changes to the selected thread in 'thread apply all' are
+# sticky outside of the 'thread apply all'.
+with_test_prefix "thr apply all" {
+    clean_restart $binfile
+    setup_for_test
+
+    # Move all threads up a frame.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, first time"
+    set frame_info [list "$hex in main" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" \
+			"$hex in thread_level_4" ]
+    test_info_threads "info threads #10" $frame_info
+
+    # Move every thread back to frame 0.
+    gdb_test "thread apply all -- frame 0" ".*"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #11" $frame_info
+
+    # Disable restoring the current frame.
+    gdb_test_no_output "set restore-selected-frame off"
+
+    # Move all threads up a frame, no frame should change after this
+    # though.
+    gdb_test "thread apply all -- up" ".*" \
+	"all threads up, second time"
+    set frame_info [list "$hex in ${pthread_join_pattern}" \
+			"thread_level_5" \
+			"thread_level_5" \
+			"thread_level_5" ]
+    test_info_threads "info threads #12" $frame_info
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 82107067217..8615e7b4a72 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -698,7 +698,7 @@ thread_alive (thread_info *tp)
    switched, false otherwise.  */
 
 static bool
-switch_to_thread_if_alive (thread_info *thr)
+switch_to_thread_if_alive (thread_info *thr, bool restore_previous_frame)
 {
   scoped_restore_current_thread restore_thread;
 
@@ -708,7 +708,7 @@ switch_to_thread_if_alive (thread_info *thr)
 
   if (thread_alive (thr))
     {
-      switch_to_thread (thr);
+      switch_to_thread (thr, restore_previous_frame);
       restore_thread.dont_restore ();
       return true;
     }
@@ -880,7 +880,10 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    {
+      thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+      thr->selected_frame_level = -1;
+    }
 }
 
 void
@@ -1043,6 +1046,23 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* When this is true, GDB restores the thread's previously selected frame
+   each time the current thread is changed (when possible).  */
+
+static bool restore_selected_frame_per_thread = false;
+
+/* Implement 'show restore-selected-frame'.  */
+
+static void
+show_restore_selected_frame_per_thread (struct ui_file *file, int from_tty,
+					struct cmd_list_element *c,
+					const char *value)
+{
+  fprintf_filtered (file,
+		    _("Restoring the selected frame is currently %s.\n"),
+		    value);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1155,7 +1175,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
 	    uiout->field_signed ("id", tp->global_num);
 
 	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
+	  switch_to_thread (tp, (tp == current_thread
+				 || restore_selected_frame_per_thread));
 
 	  /* For the CLI, we stuff everything into the target-id field.
 	     This is a gross hack to make the output come out looking
@@ -1337,7 +1358,7 @@ switch_to_no_thread ()
 /* See gdbthread.h.  */
 
 void
-switch_to_thread (thread_info *thr)
+switch_to_thread (thread_info *thr, bool restore_previous_frame)
 {
   gdb_assert (thr != NULL);
 
@@ -1347,6 +1368,10 @@ switch_to_thread (thread_info *thr)
   switch_to_thread_no_regs (thr);
 
   reinit_frame_cache ();
+
+  if (restore_previous_frame && thr->selected_frame_level > -1)
+    restore_selected_frame (thr->selected_frame_id,
+			    thr->selected_frame_level);
 }
 
 /* See gdbsupport/common-gdbthread.h.  */
@@ -1372,13 +1397,16 @@ scoped_restore_current_thread::restore ()
 	 in the mean time exited (or killed, detached, etc.), then don't revert
 	 back to it, but instead simply drop back to no thread selected.  */
       && m_inf->pid != 0)
-    switch_to_thread (m_thread.get ());
+    switch_to_thread (m_thread.get (), restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf.get ());
 
   /* The running state of the originally selected thread may have
-     changed, so we have to recheck it here.  */
+     changed, so we have to recheck it here.  We only restore the frame
+     here if we didn't restore the threads selected frame when switching
+     thread above (see use of RESTORE_SELECTED_FRAME_PER_THREAD).  */
   if (inferior_ptid != null_ptid
+      && !restore_selected_frame_per_thread
       && m_was_stopped
       && m_thread->state == THREAD_STOPPED
       && target_has_registers ()
@@ -1612,7 +1640,8 @@ thread_apply_all_command (const char *cmd, int from_tty)
       scoped_restore_current_thread restore_thread;
 
       for (thread_info_ref &thr : thr_list_cpy)
-	if (switch_to_thread_if_alive (thr.get ()))
+	if (switch_to_thread_if_alive (thr.get (),
+				       restore_selected_frame_per_thread))
 	  thr_try_catch_cmd (thr.get (), cmd, from_tty, flags);
     }
 }
@@ -1769,7 +1798,7 @@ thread_apply_command (const char *tidlist, int from_tty)
 	  continue;
 	}
 
-      if (!switch_to_thread_if_alive (tp))
+      if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
 	{
 	  warning (_("Thread %s has terminated."), print_thread_id (tp));
 	  continue;
@@ -1943,7 +1972,7 @@ show_print_thread_events (struct ui_file *file, int from_tty,
 void
 thread_select (const char *tidstr, thread_info *tp)
 {
-  if (!switch_to_thread_if_alive (tp))
+  if (!switch_to_thread_if_alive (tp, restore_selected_frame_per_thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
   annotate_thread_changed ();
@@ -2210,6 +2239,19 @@ Show printing of thread events (such as thread start and exit)."), NULL,
 			   show_print_thread_events,
 			   &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("restore-selected-frame",
+			   class_stack, &restore_selected_frame_per_thread,
+			   _("\
+Set whether GDB restores the selected frame when switching threads."), _("\
+Show whether GDB restores the selected frame when switching threads."), _("\
+When this option is on, GDB will record the currently selected frame for\n\
+each thread, and restore the selected frame whenever GDB switches thread.\n\
+Causing a thread to execute will invalidate the selected frame."),
+			   nullptr,
+			   show_restore_selected_frame_per_thread,
+			   &setlist,
+			   &showlist);
+
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
   create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCHv8] gdb: Restore previously selected thread when switching inferior
  2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
  2021-02-12 18:20           ` [PATCHv7 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
  2021-02-12 18:20           ` [PATCHv7 2/2] gdb: Track the current frame for each thread Andrew Burgess
@ 2021-03-23 11:13           ` Andrew Burgess
  2021-03-23 11:43             ` Eli Zaretskii
  2 siblings, 1 reply; 28+ messages in thread
From: Andrew Burgess @ 2021-03-23 11:13 UTC (permalink / raw)
  To: gdb-patches

Given Pedro's reluctance to give any feedback on my previous iteration
of the patch I thought I'd have an attempt at a completely different
approach.

I have now converted the first of the changes to be a Python
extension.  This did require an additional observer / Python event,
but maybe this is considered OK?

If the feedback on this is generally positive I would probably look at
converting the second patch along similar lines.

Thanks,
Andrew

---

This commit adds a new option that allows the user to control how GDB
behaves when switching between multi-threaded inferiors.

Currently (and this remains the default after this commit) when
switching between inferiors GDB would select the first non-exited
thread from the inferior being switched to.

This commit adds the following new commands:

     set restore-selected-thread on|off
     show restore-selected-thread

This option is off by default in order to retain the existing
behaviour, but, when switched on GDB will remember which thread was
selected in each inferior.  As the user switches between inferiors GDB
will attempt to restore the previously selected thread.

If the previously selected thread is no longer available, for example,
if the thread has exited, then GDB will fall back on the old
behaviour.

I've previously attempted to upstream this functionality, these
attempts can be found:

  https://sourceware.org/pipermail/gdb-patches/2020-April/167985.html
  https://sourceware.org/pipermail/gdb-patches/2020-September/172135.html
  https://sourceware.org/pipermail/gdb-patches/2020-November/173068.html
  https://sourceware.org/pipermail/gdb-patches/2020-November/173199.html
  https://sourceware.org/pipermail/gdb-patches/2020-December/173968.html
  https://sourceware.org/pipermail/gdb-patches/2021-February/175973.html

The previous attempt has stalled as it needed to get approval from
Pedro, who unfortunately has so far been unable to provide feedback on
the later versions of the patch.

This patch is a brand new approach to solving this problem.  I have
now implemented the command as a Python extension.

We currently have the user_selected_context_changed observer that is
triggered at the right spot in GDB to allow me to implement this patch
in pure Python.

The problem with this observer is that it is used as the hook by which
GDB displays the 'Switching to inferior ....' and 'Switching to thread
...' messages.  I need to ensure that Python gets a chance to observe
the inferior change before these messages are displayed.

I see two possible solutions, one choice is to allow a basic level of
ordering when attaching to an observer.  In this way when the Python
code attaches to user_selected_context_changed it could request to be
called early on.  The hooks that print the messages can request that
they are called later.  The observer object would be in charge of
keeping the attached observers sorted.

The other, possibly simpler choice, is to just add a new observer that
is called when the user switches inferior.

I chose the second approach, though I'd be happy to investigate the
first approach if folk think it would be a better choice.

I've installed the Python code in the data-directory so that it is
auto-loaded into a users session, as the restore behaviour is off by
default this hopefully shouldn't be too invasive.

There's a new test for this functionality.

gdb/ChangeLog:

	* NEWS: Mention new feature and new Python event.
	* data-directory/Makefile.in (PYTHON_FILE_LIST): Add
	restore-thread.py.
	* inferior.c (switch_to_inferior_no_thread): Move declaration of
	some variables into more inner block.  Get the current inferior in
	the top function scope.  After switching inferior, call the new
	observable.
	* observable.c (inferior_changed): Define new observable.
	* observable.h (inferior_changed): Declare new observable.
	* python/lib/gdb/command/restore-thread.py: New file.
	* python/py-all-events.def (inferior_changed): Declare.
	* python/py-event-types.def (inferior_changed): Declare.
	* python/py-inferior.c (python_inferior_changed): New function.
	(gdbpy_initialize_inferior): Register with inferior_changed
	observer.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
	tracking within the inferior command.
	(Threads): Mention thread tracking in the general thread
	discussion.
	* python.texinfo (Events In Python): Document new
	gdb.InferiorChangedEvent.

gdb/testsuite/ChangeLog:

	* gdb.threads/restore-thread.c: New file.
	* gdb.threads/restore-thread.exp: New file.
---
 gdb/ChangeLog                                |  18 ++
 gdb/NEWS                                     |  14 ++
 gdb/data-directory/Makefile.in               |   1 +
 gdb/doc/ChangeLog                            |   9 +
 gdb/doc/gdb.texinfo                          |  19 +-
 gdb/doc/python.texi                          |  35 +++
 gdb/inferior.c                               |  18 +-
 gdb/observable.c                             |   1 +
 gdb/observable.h                             |  12 +
 gdb/python/lib/gdb/command/restore-thread.py | 107 ++++++++
 gdb/python/py-all-events.def                 |   1 +
 gdb/python/py-event-types.def                |   5 +
 gdb/python/py-inferior.c                     |  65 +++++
 gdb/testsuite/ChangeLog                      |   5 +
 gdb/testsuite/gdb.threads/restore-thread.c   | 248 +++++++++++++++++++
 gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++
 16 files changed, 771 insertions(+), 6 deletions(-)
 create mode 100644 gdb/python/lib/gdb/command/restore-thread.py
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 7f5a745d0c0..a0609aa55a6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -50,6 +50,15 @@ maintenance flush dcache
 maintenance info target-sections
   Print GDB's internal target sections table.
 
+set restore-selected-thread on|off
+show restore-selected-thread
+  When turned on, GDB will record the currently selected thread in
+  each inferior.  When switching between inferiors, GDB will attempt
+  to restore the previously selected thread in the inferior being
+  switched too.  If the previously selected thread is no longer
+  available, then GDB falls back to selecting the first non-exited
+  thread.
+
 * Changed commands
 
 break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM]
@@ -98,6 +107,11 @@ maintenance info sections
 
 ARM Symbian			arm*-*-symbianelf*
 
+* Python API
+
+  ** New gdb.InferiorChangedEvent which is emitted when the user
+     requests a change of the currently selected inferior.
+
 *** Changes in GDB 10
 
 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core"
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 8b65790cdd9..6b9d65698ed 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -82,6 +82,7 @@ PYTHON_FILE_LIST = \
 	gdb/command/frame_filters.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
+	gdb/command/restore-thread.py \
 	gdb/command/type_printers.py \
 	gdb/command/unwinders.py \
 	gdb/command/xmethods.py \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 80ccf74a049..18fe1db0e48 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3179,11 +3179,25 @@
 To switch focus between inferiors, use the @code{inferior} command:
 
 @table @code
+@anchor{inferior command}
 @kindex inferior @var{infno}
 @item inferior @var{infno}
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+When switching between inferiors with multiple threads (@pxref{Threads}),
+@value{GDBN} will select the first non-exited thread in the inferior being
+switched to, and make this the current thread.
+
+@kindex set restore-selected-thread
+@kindex show restore-selected-thread
+@item set restore-selected-thread @r{[}on|off@r{]}
+@item show restore-selected-thread
+When this option is on, @value{GDBN} will record the currently selected
+thread in each inferior.  When switching between inferiors, @value{GDBN}
+will try to restore the previously selected thread in the inferior being
+switched to.  This option is off by default.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
@@ -3565,7 +3579,10 @@
 
 If you're debugging multiple inferiors, @value{GDBN} displays thread
 IDs using the qualified @var{inferior-num}.@var{thread-num} format.
-Otherwise, only @var{thread-num} is shown.
+Otherwise, only @var{thread-num} is shown.  When switching between
+inferiors, @value{GDBN} will select a suitable thread in the inferior
+being switched to, see @ref{inferior command,,the @code{inferior}
+command}, for further details on how to control this behaviour.
 
 If you specify the @samp{-gid} option, @value{GDBN} displays a column
 indicating each thread's global thread ID:
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 9135d415dd1..b9c0028cec8 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3314,6 +3314,41 @@
 The new thread.
 @end defvar
 
+@item events.inferior_changed
+This is emitted when the user requests @value{GDBN} change the
+currently selected inferior.  The event is of type
+@code{gdb.InferiorChangedEvent}, and has the following attributes:
+
+@defvar InferiorChangedEvent.from_inferior
+The gdb.Inferior (@pxref{Inferiors In Python}) that used to be
+selected, but no longer is.
+@end defvar
+
+@defvar InferiorChangedEvent.to_inferior
+The gdb.Inferior (@pxref{Inferiors In Python}) that is now the current
+inferior.
+@end defvar
+
+@defvar InferiorChangedEvent.from_thread
+The gdb.InferiorThread (@pxref{Threads In Python}) that used to be the
+current thread.  This can be @code{None} as an inferior that is not
+yet running will have no threads, in which case, when switching from
+such an inferior @code{from_thread} will be @code{None}.
+
+If this field is not @code{None} then it will represent a thread from
+within the set @code{from_inferior.threads ()}.
+@end defvar
+
+@defvar InferiorChangedEvent.to_thread
+The gdb.InferiorThread (@pxref{Threads In Python}) that is now the
+current thread.  This can be @code{None} as an inferior that is not
+yet running will have no threads, in which case, when switching to
+such an inferior @code{to_thread} will be @code{None}.
+
+If this field is not @code{None} then it will represent a thread from
+within the set @code{to_inferior.threads ()}.
+@end defvar
+
 @end table
 
 @node Threads In Python
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 49f869a4c78..e16534c9b37 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -632,13 +632,12 @@ switch_to_inferior_no_thread (inferior *inf)
 static void
 inferior_command (const char *args, int from_tty)
 {
-  struct inferior *inf;
-  int num;
+  /* GDB always has a "current" inferior.  */
+  struct inferior *inf = current_inferior ();
+  gdb_assert (inf != nullptr);
 
   if (args == nullptr)
     {
-      inf = current_inferior ();
-      gdb_assert (inf != nullptr);
       const char *filename = inf->pspace->exec_filename.get ();
 
       if (filename == nullptr)
@@ -650,7 +649,10 @@ inferior_command (const char *args, int from_tty)
     }
   else
     {
-      num = parse_and_eval_long (args);
+      struct inferior *old_inf = inf;
+      ptid_t old_inferior_ptid = inferior_ptid;
+
+      int num = parse_and_eval_long (args);
 
       inf = find_inferior_id (num);
       if (inf == NULL)
@@ -667,6 +669,9 @@ inferior_command (const char *args, int from_tty)
 	      switch_to_thread (tp);
 	    }
 
+	  gdb::observers::inferior_changed.notify
+	    (old_inf, old_inferior_ptid, inf, inferior_thread ()->ptid);
+
 	  gdb::observers::user_selected_context_changed.notify
 	    (USER_SELECTED_INFERIOR
 	     | USER_SELECTED_THREAD
@@ -676,6 +681,9 @@ inferior_command (const char *args, int from_tty)
 	{
 	  switch_to_inferior_no_thread (inf);
 
+	  gdb::observers::inferior_changed.notify
+	    (old_inf, old_inferior_ptid, inf, null_ptid);
+
 	  gdb::observers::user_selected_context_changed.notify
 	    (USER_SELECTED_INFERIOR);
 	}
diff --git a/gdb/observable.c b/gdb/observable.c
index 10b8aad829e..c7b18e24341 100644
--- a/gdb/observable.c
+++ b/gdb/observable.c
@@ -77,6 +77,7 @@ DEFINE_OBSERVABLE (register_changed);
 DEFINE_OBSERVABLE (user_selected_context_changed);
 DEFINE_OBSERVABLE (source_styling_changed);
 DEFINE_OBSERVABLE (current_source_symtab_and_line_changed);
+DEFINE_OBSERVABLE (inferior_changed);
 
 } /* namespace observers */
 } /* namespace gdb */
diff --git a/gdb/observable.h b/gdb/observable.h
index 915770ff363..021498b6387 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -251,6 +251,18 @@ extern observable<> source_styling_changed;
 
 extern observable<> current_source_symtab_and_line_changed;
 
+/* This is notified when the user triggers a change in the current
+   inferior.  This is notified before the user_selected_context_changed is
+   notified, this allows an observer to adjust the selected thread or
+   frame.  FROM_INFERIOR and FROM_PTID identify the inferior and thread
+   being switched from, while TO_INFERIOR and TO_PTID are the inferior and
+   thread being switched too.  The *_INFERIOR objects should never be
+   nullptr, however the *_PTID value might be null_ptid.  */
+extern observable<inferior */* from_inferior */,
+		  ptid_t /* from_ptid */,
+		  inferior */* to_inferior */,
+		  ptid_t /* to_ptid */> inferior_changed;
+
 } /* namespace observers */
 
 } /* namespace gdb */
diff --git a/gdb/python/lib/gdb/command/restore-thread.py b/gdb/python/lib/gdb/command/restore-thread.py
new file mode 100644
index 00000000000..69b5556c322
--- /dev/null
+++ b/gdb/python/lib/gdb/command/restore-thread.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2021 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/>.
+
+# Implement 'set/show restore-selected-thread' option.
+#
+# When the user switches between inferior restore the previously
+# selected thread within the inferior being switched too.
+
+import gdb
+
+# A class that implements the 'set/show restore-selected-thread'
+# option.  An instance of this class can be treated as a boolean to
+# indicate if this feature is on or off.
+class restore_thread_parameter (gdb.Parameter):
+    '''When this option is on, GDB will record the currently selected thread for
+each inferior, and restore the selected thread whenever GDB switches inferiors.'''
+
+    set_doc = "Set whether GDB restores the selected thread when switching inferiors."
+    show_doc = "Show whether GDB restores the selected thread when switching inferiors."
+
+    def __init__ (self):
+        gdb.Parameter.__init__ (self, "restore-selected-thread",
+                                gdb.COMMAND_NONE,
+                                gdb.PARAM_BOOLEAN)
+        self.value = False
+
+    def get_show_string (self, value):
+        return ("Restoring previously selected thread is %s."
+                % (value))
+
+    def __nonzero__ (self):
+        if (self.value):
+            return 1
+        else:
+            return 0
+
+    def __bool__ (self):
+        return self.value
+
+# Create an instance of the above class.  This registers the option
+# with the GDB core, but we also create a global object, this will
+# allow us to check if the setting is on or off.
+restore_selected_thread_p = restore_thread_parameter ()
+
+# This map has keys that are inferior numbers as assigned by GDB, the
+# values are the gdb.InferiorThread object that we last saw as
+# selected for that inferior.
+#
+# We only update the values held in the map when we switch away from
+# an inferior, trying keep the map up to date while a particular
+# inferior is selected is not required.
+inferior_to_thread_map = {}
+
+# Function called when the user changes the currently selected
+# inferior.
+def inferior_changed (event):
+    global restore_selected_thread_p
+    global inferior_to_thread_map
+
+    # Record the previously selected thread.  The thread object might
+    # be None, e.g. if the previous inferior was not running it will
+    # have had no threads.
+    #
+    # We always update this map even when restore-selected-thread is
+    # off, this means that if the user turns this setting on then they
+    # will start seeing the restore behaviour immediately.
+    inferior_to_thread_map[event.from_inferior.num] = event.from_thread
+
+    # If restore-selected-thread is off then we're done.
+    if not restore_selected_thread_p:
+        return
+
+    # If we have restore data for the inferior we are switching too,
+    # and the restore data is a gdb.InferiorThread, and the thread is
+    # still valid, then switch to that thread now.
+    #
+    # Otherwise, delete the map entry for the inferior we are
+    # switching too, the data we hold is clearly of no value.
+    if event.to_inferior.num in inferior_to_thread_map:
+        thr = inferior_to_thread_map[event.to_inferior.num]
+        if thr and thr.is_valid ():
+            thr.switch ()
+        else:
+            del inferior_to_thread_map[event.to_inferior.num]
+
+# Function called when an inferior is deleted.  Remove any restore
+# data we are holding for that inferior.
+def inferior_deleted (event):
+    global inferior_to_thread_map
+    if event.inferior.num in inferior_to_thread_map:
+        del inferior_to_thread_map[event.inferior.num]
+
+# Register the two even listeners.
+gdb.events.inferior_changed.connect (inferior_changed)
+gdb.events.inferior_deleted.connect (inferior_deleted)
diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def
index d12a2103e80..22347d030a9 100644
--- a/gdb/python/py-all-events.def
+++ b/gdb/python/py-all-events.def
@@ -38,3 +38,4 @@ GDB_PY_DEFINE_EVENT(breakpoint_created)
 GDB_PY_DEFINE_EVENT(breakpoint_deleted)
 GDB_PY_DEFINE_EVENT(breakpoint_modified)
 GDB_PY_DEFINE_EVENT(before_prompt)
+GDB_PY_DEFINE_EVENT(inferior_changed)
diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def
index 70df4804fc4..12767e601b7 100644
--- a/gdb/python/py-event-types.def
+++ b/gdb/python/py-event-types.def
@@ -105,3 +105,8 @@ GDB_PY_DEFINE_EVENT_TYPE (thread,
 			  "ThreadEvent",
 			  "GDB thread event object",
 			  event_object_type);
+
+GDB_PY_DEFINE_EVENT_TYPE (inferior_changed,
+			  "InferiorChangedEvent",
+			  "GDB inferior changed event object",
+			  event_object_type);
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index a3d5952a10b..f3c535a3e65 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -296,6 +296,70 @@ python_inferior_deleted (struct inferior *inf)
     gdbpy_print_stack ();
 }
 
+/* Called when the user switches between inferiors.  Notify any Python
+   event listeners.  */
+static void
+python_inferior_changed (struct inferior *from_inferior,
+			 ptid_t from_ptid,
+			 struct inferior *to_inferior,
+			 ptid_t to_ptid)
+{
+  if (!gdb_python_initialized)
+    return;
+
+  gdbpy_enter enter_py (python_gdbarch, python_language);
+
+  if (evregpy_no_listeners_p (gdb_py_events.inferior_changed))
+    return;
+
+  gdbpy_ref<inferior_object> inf_from_obj
+    = inferior_to_inferior_object (from_inferior);
+  if (inf_from_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return;
+    }
+
+  gdbpy_ref<inferior_object> inf_to_obj
+    = inferior_to_inferior_object (to_inferior);
+  if (inf_to_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return;
+    }
+
+  /* Helper to lookup a thread object given a ptid_t P.  If no suitable
+     thread object is found (e.g. P is null_ptid), then a reference to None
+     is returned.  */
+  auto thread_from_ptid = [] (ptid_t p) -> gdbpy_ref<>
+    {
+      if (p == null_ptid)
+	return gdbpy_ref<>::new_reference (Py_None);
+      thread_info *thread
+	= find_thread_ptid (current_inferior ()->process_target (),
+			    p);
+      if (thread != nullptr)
+	return thread_to_thread_object (thread);
+      return gdbpy_ref<>::new_reference (Py_None);
+    };
+
+  gdbpy_ref<> from_thr_obj = thread_from_ptid (from_ptid);
+  gdbpy_ref<> to_thr_obj = thread_from_ptid (from_ptid);
+
+  gdbpy_ref<> event = create_event_object (&inferior_changed_event_object_type);
+  if (event == NULL
+      || evpy_add_attribute (event.get (), "from_inferior",
+			     (PyObject *) inf_from_obj.get ()) < 0
+      || evpy_add_attribute (event.get (), "to_inferior",
+			     (PyObject *) inf_to_obj.get ()) < 0
+      || evpy_add_attribute (event.get (), "from_thread",
+			     (PyObject *) from_thr_obj.get ()) < 0
+      || evpy_add_attribute (event.get (), "to_thread",
+			     (PyObject *) to_thr_obj.get ()) < 0
+      || evpy_emit_event (event.get (), gdb_py_events.inferior_changed) < 0)
+    gdbpy_print_stack ();
+}
+
 gdbpy_ref<>
 thread_to_thread_object (thread_info *thr)
 {
@@ -916,6 +980,7 @@ gdbpy_initialize_inferior (void)
   gdb::observers::new_objfile.attach (python_new_objfile);
   gdb::observers::inferior_added.attach (python_new_inferior);
   gdb::observers::inferior_removed.attach (python_inferior_deleted);
+  gdb::observers::inferior_changed.attach (python_inferior_changed);
 
   membuf_object_type.tp_new = PyType_GenericNew;
   if (PyType_Ready (&membuf_object_type) < 0)
diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c
new file mode 100644
index 00000000000..3eb1f722199
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.c
@@ -0,0 +1,248 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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 <pthread.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+/* The number of threads to create.  */
+volatile int thread_count = 3;
+
+/* This is initialised with our pid. GDB will read and print this value
+   from the Dejagnu test script, the test script will then use the pid to
+   send signals to this process.  */
+pid_t global_pid;
+
+/* Holds one end of two different pipes.  Things written to READ will not
+   appear on WRITE.  */
+struct pipe_fds
+{
+  int read;
+  int write;
+};
+
+/* Information passed into each thread.  */
+struct thread_info
+{
+  /* Just a numeric id for the thread.  */
+  int id;
+
+  /* File handles with which the worker thread can communicate with the
+     master thread.  */
+  struct pipe_fds fds;
+};
+
+/* The control information held by the master thread, one of these for each
+   worker thread.  */
+struct thread_ctrl
+{
+  /* The actual pthread handle, used to join the threads.  */
+  pthread_t thread;
+
+  /* File handles with which the master thread can communicate with the
+     worker threads.  */
+  struct pipe_fds fds;
+
+  /* The information that is passed into the worker thread.  */
+  struct thread_info info;
+};
+
+/* Wait for a single byte of the read file handle in FDS.  */
+static void
+wait_on_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c;
+
+  while ((rtn = read (fds->read, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Send a single byte to the write file handle in FDS.  */
+static void
+send_byte (struct pipe_fds *fds)
+{
+  ssize_t rtn;
+  char c = 'x';
+  while ((rtn = write (fds->write, &c, 1)) != 1)
+    {
+      if (rtn != -1 || errno != EINTR)
+	abort ();
+    }
+}
+
+/* Create a function used to mark a breakpoint location.  */
+#define BREAKPOINT_FUNC(N)				\
+  static void						\
+  breakpt_ ## N ()					\
+  {							\
+    printf ("Hit breakpt_" #N "\n");			\
+  }
+
+BREAKPOINT_FUNC (0)	/* breakpt_0 */
+BREAKPOINT_FUNC (1)	/* breakpt_1 */
+BREAKPOINT_FUNC (2)	/* breakpt_2 */
+
+/* The worker thread entry point.  */
+static void *
+thread_worker (void *arg)
+{
+  struct thread_info *info = (struct thread_info *) arg;
+  int id = info->id;
+
+  printf ("Thread %d created.\n", id);
+  breakpt_0 ();
+
+  /* Let the main thread know that this thread is now running.  */
+  send_byte (&info->fds);
+
+  /* The thread with id #2 is special, it waits here for a nudge from the
+     main thread.  */
+  if (id == 2)
+    {
+      wait_on_byte (&info->fds);
+      breakpt_2 ();
+      send_byte (&info->fds);
+    }
+
+  /* Now wait for an incoming message indicating that the thread should
+     exit.  */
+  wait_on_byte (&info->fds);
+  printf ("In thread %d, exiting...\n", id);
+  return NULL;
+}
+
+/* Initialise CTRL for thread ID, this includes setting up all of the pipe
+   file handles.  */
+static void
+thread_ctrl_init (struct thread_ctrl *ctrl, int id)
+{
+  int fds[2];
+
+  ctrl->info.id = id;
+  if (pipe (fds))
+    abort ();
+  ctrl->info.fds.read = fds[0];
+  ctrl->fds.write = fds[1];
+
+  if (pipe (fds))
+    abort ();
+  ctrl->fds.read = fds[0];
+  ctrl->info.fds.write = fds[1];
+}
+
+/* Wait for a SIGUSR1 to arrive.  Assumes that SIGUSR1 is blocked on entry
+   to this function.  */
+static void
+wait_for_sigusr1 (void)
+{
+  int signo;
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+
+  /* Wait for a SIGUSR1.  */
+  if (sigwait (&set, &signo) != 0)
+    abort ();
+  if (signo != SIGUSR1)
+    abort ();
+}
+
+/* Main program.  */
+int
+main ()
+{
+  sigset_t set;
+  int i, max = thread_count;
+
+  /* Set an alarm in case the testsuite crashes, don't leave the test
+     running forever.  */
+  alarm (300);
+
+  struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max);
+  if (info == NULL)
+    abort ();
+
+  /* Put the pid somewhere easy for GDB to read, also print it.  */
+  global_pid = getpid ();
+  printf ("pid = %lld\n", ((long long) global_pid));
+
+  /* Block SIGUSR1, all threads will inherit this sigmask. */
+  sigemptyset (&set);
+  sigaddset (&set, SIGUSR1);
+  if (pthread_sigmask (SIG_BLOCK, &set, NULL))
+    abort ();
+
+  /* Create each thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+      thread_ctrl_init (thr, i + 1);
+
+      if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0)
+	abort ();
+
+      /* Wait for an indication that the thread has started, and is ready
+	 for action.  */
+      wait_on_byte (&thr->fds);
+    }
+
+  printf ("All threads created.\n");
+
+  /* Give thread thread #1 a little nudge.  */
+  if (max >= 2)
+    {
+      send_byte (&info[1].fds);
+      wait_on_byte (&info[1].fds);
+    }
+
+  breakpt_1 ();
+
+  /* For each thread in turn wait for a SIGUSR1 to arrive, signal the
+     thread so that it will exit (by sending it a byte down its pipe), then
+     join the newly exited thread.  */
+  for (i = 0; i < max; ++i)
+    {
+      struct thread_ctrl *thr = &info[i];
+
+      wait_for_sigusr1 ();
+
+      printf ("Telling thread %d to exit\n", thr->info.id);
+      send_byte (&thr->fds);
+
+      if (pthread_join (thr->thread, NULL) != 0)
+	abort ();
+
+      printf ("Thread %d exited\n", thr->info.id);
+    }
+
+  free (info);
+
+  /* Final wait before exiting.  */
+  wait_for_sigusr1 ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp
new file mode 100644
index 00000000000..f768b123c74
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-thread.exp
@@ -0,0 +1,219 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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/>.
+
+# Test GDB's ability to restore the selected thread when switching
+# between inferiors, and check what happens when the selected thread
+# of one inferior exits while we have a different inferior selected.
+
+standard_testfile
+
+if [prepare_for_testing "failed to prepare" $binfile $srcfile \
+	{debug pthreads}] {
+    return -1
+}
+
+# Check that the current thread is THR in inferior INF.
+proc check_current_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "check_current_thread ${inf} ${thr}"
+    }
+
+    # As a final check, lets check the output for the 'thread'
+    # command.
+    gdb_test "thread" "Current thread is ${inf}.${thr} .*" \
+	"current thread is ${inf}.${thr}: $testname"
+}
+
+# Switch to inferior number INF, we expect that thread number THR
+# within the inferior will be selected.
+proc switch_to_inferior { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_inferior $inf $thr"
+    }
+
+    gdb_test "inferior $inf" \
+	"Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \
+	"$testname: select inferior ${inf}"
+
+    check_current_thread $inf $thr "$testname: check current thread"
+}
+
+# Switch to thread number THR.  INF should be the number of the
+# currently selected inferior and is used when checking the currently
+# selected thread.
+proc switch_to_thread { inf thr {testname ""} } {
+    if {${testname} == ""} {
+	set testname "switch_to_thread $inf $thr"
+    }
+
+    gdb_test "thread ${thr}" \
+	"Switching to thread ${inf}.${thr} .*" \
+	"${testname}: select thread ${thr}"
+    check_current_thread $inf $thr \
+	"${testname}: check current thread"
+}
+
+# Continue the program in the background.
+proc continue_in_bg { testname } {
+    global gdb_prompt
+
+    gdb_test_multiple "continue&" $testname {
+	-re "Continuing\\.\r\n$gdb_prompt " {
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Send SIGUSR1 to PID, this will cause one of that processes threads
+# to exit (assuming the process is currently running).
+proc send_thread_exit_signal { pid } {
+    global decimal
+
+    remote_exec target "kill -USR1 ${pid}"
+    gdb_test_multiple "" "wait for thread to exit" {
+	-re "Thread $decimal exited.*exited\\\].*" {
+	}
+    }
+}
+
+# Start of test script.
+
+set pid_1 0
+set pid_2 0
+
+if ![runto_main] {
+    return -1
+}
+
+# Restoring the selected thread is off by default.  Switch it on now.
+gdb_test_no_output "set restore-selected-thread on"
+
+gdb_breakpoint "breakpt_0"
+gdb_breakpoint "breakpt_1"
+
+with_test_prefix "start inferior 1" {
+    gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 1 threads created" \
+	".* breakpt_1 .*"
+    gdb_test "info threads" ".*"
+    set pid_1 [get_valueof "/d" "global_pid" 0]
+}
+
+# Start another inferior.
+gdb_test "add-inferior" [multi_line \
+			     "\\\[New inferior 2\\\]" \
+			     "Added inferior 2 .*" ] \
+    "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" \
+    "switch to inferior 2"
+gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+with_test_prefix "start inferior 2" {
+    gdb_breakpoint "breakpt_2"
+    gdb_run_cmd
+    gdb_test "" "hit Breakpoint .*" \
+	"runto breakpoint in main"
+    gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*"
+    gdb_continue_to_breakpoint "all inferior 2 threads created" \
+	".* breakpt_2 .*"
+    gdb_test "info threads" ".*"
+    set pid_2 [get_valueof "/d" "global_pid" 0]
+}
+
+gdb_assert {${pid_1} != 0} "read the pid for inferior 1"
+gdb_assert {${pid_2} != 0} "read the pid for inferior 2"
+
+check_current_thread 2 3 "check initial thread is 2.3"
+switch_to_inferior 1 1 "first switch to thread 1.1"
+switch_to_inferior 2 3
+switch_to_thread 2 2
+
+switch_to_inferior 1 1 "second switch to thread 1.1"
+switch_to_thread 1 3
+switch_to_inferior 2 2
+
+# Inferior 2 is special; it will have stopped at breakpt_2, in thread
+# 2.3.  To set this inferior up so that threads can exit we need to
+# continue to breakpt_1.
+gdb_continue_to_breakpoint "all inferior 2 threads created" \
+    ".* breakpt_1 .*"
+
+with_test_prefix "inferior 2 ready" {
+    check_current_thread 2 1
+
+    switch_to_inferior 1 3
+    switch_to_thread 1 2
+
+    continue_in_bg "continue inferior 1"
+    switch_to_inferior 2 1
+    switch_to_thread 2 2
+    continue_in_bg "continue inferior 2"
+}
+
+# Cause thread 1.2 to exit.
+send_thread_exit_signal ${pid_1}
+
+with_test_prefix "after 1.2 exited" {
+    # We should go back to 1.1 now as 1.2 has exited.
+    switch_to_inferior 1 1
+    switch_to_thread 1 4
+
+    # Cause thread 2.2 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.2 exited" {
+    # We should go back to 2.1 now as 2.2 has exited.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.3 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.3 exited" {
+    # We should still switch back to 1.4 as only 1.3 exited.
+    switch_to_inferior 1 4
+
+    # Cause thread 2.3 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.3 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+
+    # Cause thread 1.4 to exit.
+    send_thread_exit_signal ${pid_1}
+}
+
+with_test_prefix "after 1.4 exited" {
+    # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the
+    # only thread left now.
+    switch_to_inferior 1 1
+
+    # Cause thread 2.4 to exit.
+    send_thread_exit_signal ${pid_2}
+}
+
+with_test_prefix "after 2.4 exited" {
+    # Switch back to 2.1, which should still be selected.
+    switch_to_inferior 2 1
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCHv8] gdb: Restore previously selected thread when switching inferior
  2021-03-23 11:13           ` [PATCHv8] gdb: Restore previously selected thread when switching inferior Andrew Burgess
@ 2021-03-23 11:43             ` Eli Zaretskii
  0 siblings, 0 replies; 28+ messages in thread
From: Eli Zaretskii @ 2021-03-23 11:43 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <andrew.burgess@embecosm.com>
> Date: Tue, 23 Mar 2021 11:13:36 +0000
> 
> gdb/ChangeLog:
> 
> 	* NEWS: Mention new feature and new Python event.
> 	* data-directory/Makefile.in (PYTHON_FILE_LIST): Add
> 	restore-thread.py.
> 	* inferior.c (switch_to_inferior_no_thread): Move declaration of
> 	some variables into more inner block.  Get the current inferior in
> 	the top function scope.  After switching inferior, call the new
> 	observable.
> 	* observable.c (inferior_changed): Define new observable.
> 	* observable.h (inferior_changed): Declare new observable.
> 	* python/lib/gdb/command/restore-thread.py: New file.
> 	* python/py-all-events.def (inferior_changed): Declare.
> 	* python/py-event-types.def (inferior_changed): Declare.
> 	* python/py-inferior.c (python_inferior_changed): New function.
> 	(gdbpy_initialize_inferior): Register with inferior_changed
> 	observer.
> 
> gdb/doc/ChangeLog:
> 
> 	* gdb.texinfo (Inferiors Connections and Programs): Mention thread
> 	tracking within the inferior command.
> 	(Threads): Mention thread tracking in the general thread
> 	discussion.
> 	* python.texinfo (Events In Python): Document new
> 	gdb.InferiorChangedEvent.
> 
> gdb/testsuite/ChangeLog:
> 
> 	* gdb.threads/restore-thread.c: New file.
> 	* gdb.threads/restore-thread.exp: New file.

The documentation parts are okay, assuming that the following not is
taken care of:

> +@defvar InferiorChangedEvent.from_inferior
> +The gdb.Inferior (@pxref{Inferiors In Python}) that used to be
> +selected, but no longer is.
> +@end defvar
> +
> +@defvar InferiorChangedEvent.to_inferior
> +The gdb.Inferior (@pxref{Inferiors In Python}) that is now the current
> +inferior.
> +@end defvar

There's no need to have more than one copy of the same @pxref so close
to one another; it's enough to have only the first of these.

Thanks.

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2021-03-23 11:42 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-09-25 22:24 [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
2020-09-25 22:24 ` [PATCHv3 1/3] gdb: unify two copies of restore_selected_frame Andrew Burgess
2020-09-25 22:24 ` [PATCHv3 2/3] gdb: Restore previously selected thread when switching inferior Andrew Burgess
2020-09-26  6:09   ` Eli Zaretskii
2020-09-25 22:24 ` [PATCHv3 3/3] gdb: Track the current frame for each thread Andrew Burgess
2020-09-26  6:16   ` Eli Zaretskii
2020-09-26  8:31     ` Andreas Schwab
2020-09-26  8:54       ` Eli Zaretskii
2020-10-08  9:59 ` [PATCHv3 0/3] Restore thread and frame patches Andrew Burgess
2020-11-06 23:02   ` [PATCHv4 0/2] " Andrew Burgess
2020-11-06 23:02     ` [PATCHv4 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
2020-11-09 15:02       ` Aktemur, Tankut Baris
2020-11-06 23:02     ` [PATCHv4 2/2] gdb: Track the current frame for each thread Andrew Burgess
2020-11-12 11:59     ` [PATCHv5 0/2] Restore thread and frame patches Andrew Burgess
2020-12-10 11:39       ` [PATCHv6 " Andrew Burgess
2020-12-10 11:39         ` [PATCHv6 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
2020-12-10 11:39         ` [PATCHv6 2/2] gdb: Track the current frame for each thread Andrew Burgess
2020-12-18  8:43           ` Aktemur, Tankut Baris
2021-01-04 15:07             ` Andrew Burgess
2021-01-04 16:08               ` Eli Zaretskii
2021-01-07 10:25         ` [PATCHv6 0/2] Restore thread and frame patches Andrew Burgess
2021-02-12 18:20         ` [PATCHv7 " Andrew Burgess
2021-02-12 18:20           ` [PATCHv7 1/2] gdb: Restore previously selected thread when switching inferior Andrew Burgess
2021-02-12 18:20           ` [PATCHv7 2/2] gdb: Track the current frame for each thread Andrew Burgess
2021-03-23 11:13           ` [PATCHv8] gdb: Restore previously selected thread when switching inferior Andrew Burgess
2021-03-23 11:43             ` Eli Zaretskii
2020-11-12 11:59     ` [PATCHv5 1/2] " Andrew Burgess
2020-11-12 11:59     ` [PATCHv5 2/2] gdb: Track the current frame for each thread Andrew Burgess

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).