[v2,1/1] gdb: remember previously selected thread per inferior

Message ID 20260325122321.315910-2-klaus.gerlicher@intel.com
State New
Headers
Series [v2,1/1] gdb: remember previously selected thread per inferior |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Klaus Gerlicher March 25, 2026, 12:23 p.m. UTC
  Add support for remembering which thread was selected when switching
between inferiors.  This feature can be controlled with a new setting
'set remember-threads-per-inferior' which is enabled by default.

Before this patch, GDB selected any non-exited thread when switching to
another inferior.  Note in the below example that the last inferior
command ends up in thread 2.1 even though we were previously in
thread 2.2:

  (gdb) info threads
    Id   Target Id
    1.1  Thread 0x7ffff7d7b740 (LWP 572047) "inferior-switch"
    1.2  Thread 0x7ffff7d7a640 (LWP 572050) "inferior-switch"
  * 2.1  Thread 0x7ffff7d7b740 (LWP 572618) "inferior-switch"
    2.2  Thread 0x7ffff7d7a640 (LWP 572619) "inferior-switch"

  (gdb) thread 2.2
  [Switching to thread 2.2 (Thread 0x7ffff7d7a640 (LWP 572619))]

  (gdb) inferior 1
  [Switching to inferior 1 [process 572047] (/bin/inferior-switch)]
  [Switching to thread 1.1 (Thread 0x7ffff7d7b740 (LWP 572047))]

  (gdb) thread 1.2
  [Switching to thread 1.2 (Thread 0x7ffff7d7a640 (LWP 572050))]

  (gdb) inferior 2
  [Switching to inferior 2 [process 572618] (/bin/inferior-switch)]
  [Switching to thread 2.1 (Thread 0x7ffff7d7b740 (LWP 572618))]
  ^^^ Expected 2.2, got 2.1 (context lost)

With this patch, GDB now records which thread was current when switching
away from an inferior, and automatically restores that thread when
switching back (if it still exists):

  (gdb) info threads
    Id   Target Id
    1.1  Thread 0x7ffff7d7b740 (LWP 559826) "inferior-switch"
    1.2  Thread 0x7ffff7d7a640 (LWP 559829) "inferior-switch"
  * 2.1  Thread 0x7ffff7d7b740 (LWP 560308) "inferior-switch"
    2.2  Thread 0x7ffff7d7a640 (LWP 560497) "inferior-switch"

  (gdb) thread 2.2
  [Switching to thread 2.2 (Thread 0x7ffff7d7a640 (LWP 560497))]

  (gdb) inferior 1
  [Switching to inferior 1 [process 559826] (/bin/inferior-switch)]
  [Switching to thread 1.1 (Thread 0x7ffff7d7b740 (LWP 559826))]

  (gdb) thread 1.2
  [Switching to thread 1.2 (Thread 0x7ffff7d7a640 (LWP 559829))]

  (gdb) inferior 2
  [Switching to inferior 2 [process 560308] (/bin/inferior-switch)]
  [Switching to thread 2.2 (Thread 0x7ffff7d7a640 (LWP 560497))]
  ^^^ Now correctly restores thread 2.2

  (gdb) inferior 1
  [Switching to inferior 1 [process 559826] (/bin/inferior-switch)]
  [Switching to thread 1.2 (Thread 0x7ffff7d7a640 (LWP 559829))]
  ^^^ And thread 1.2 is also remembered

The feature improves the debugging experience when working with multiple
inferiors by maintaining context.  Users who prefer the original behavior
can disable it with 'set remember-threads-per-inferior off'.

Implementation Details:

The implementation stores a thread reference in each inferior structure
and uses two mechanisms to keep it updated:

1. The user_selected_context_changed observer handles explicit user
   commands (thread, inferior) that switch the current thread.

2. A direct call in fetch_inferior_event handles event-driven thread
   switches that occur during execution (e.g., breakpoints, signals,
   schedule-multiple mode).

The dual mechanism is necessary because the user_selected_context_changed
observer only fires for explicit user commands, not for automatic
switches triggered by events.  Without the fetch_inferior_event save
point, automatic switches would not be recorded and the feature would
break in async scenarios.

When switching to an inferior, the stored thread is restored if it still
exists; otherwise, any available thread is selected as before.  The
feature respects the setting at both save and restore points to ensure
correct behavior when toggling the feature on/off.
---
 gdb/NEWS                                      |   7 +
 gdb/doc/gdb.texinfo                           |  19 ++
 gdb/gdbthread.h                               |   4 +
 gdb/inferior.c                                |  58 +++++-
 gdb/inferior.h                                |  14 ++
 gdb/infrun.c                                  |   6 +-
 gdb/testsuite/gdb.base/inferior-switch.c      |  42 +++++
 gdb/testsuite/gdb.base/inferior-switch.exp    | 172 ++++++++++++++++++
 .../gdb.mi/user-selected-context-sync.exp     |  14 ++
 gdb/thread.c                                  |   8 +
 10 files changed, 341 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/inferior-switch.c
 create mode 100644 gdb/testsuite/gdb.base/inferior-switch.exp
  

Comments

Eli Zaretskii March 25, 2026, 2:34 p.m. UTC | #1
> From: Klaus Gerlicher <klaus.gerlicher@intel.com>
> Date: Wed, 25 Mar 2026 12:23:21 +0000
> 
>  gdb/NEWS                                      |   7 +
>  gdb/doc/gdb.texinfo                           |  19 ++
>  gdb/gdbthread.h                               |   4 +
>  gdb/inferior.c                                |  58 +++++-
>  gdb/inferior.h                                |  14 ++
>  gdb/infrun.c                                  |   6 +-
>  gdb/testsuite/gdb.base/inferior-switch.c      |  42 +++++
>  gdb/testsuite/gdb.base/inferior-switch.exp    | 172 ++++++++++++++++++
>  .../gdb.mi/user-selected-context-sync.exp     |  14 ++
>  gdb/thread.c                                  |   8 +
>  10 files changed, 341 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/inferior-switch.c
>  create mode 100644 gdb/testsuite/gdb.base/inferior-switch.exp

Thanks.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,13 @@
>  
>  *** Changes since GDB 17
>  
> +*   By default, GDB now remembers which thread was selected when you were
> +  last working in each inferior.  When you switch back to that inferior,
> +  GDB automatically restores the previous thread selection (if the thread
> +  still exists).  You can disable this behavior with
> +  'set remember-threads-per-inferior off' to use the original behavior
> +  of always selecting the first available thread.
> +
>  * Support for .gdb_index sections with version less than 7 has been
>    removed.

This part is okay.

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index ceb69669ea6..dcbbcb273c1 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo

This part is also okay, with one minor comment:

> +When enabled (the default), switching to a previously used inferior will
> +restore the thread that was active when you switched away.  When disabled,
                               ^^^^^^
I think we should say "selected" there, not "active".  The latter is
ambiguous in this context.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 03f46df5400..44e6ed799a4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,13 @@ 
 
 *** Changes since GDB 17
 
+*   By default, GDB now remembers which thread was selected when you were
+  last working in each inferior.  When you switch back to that inferior,
+  GDB automatically restores the previous thread selection (if the thread
+  still exists).  You can disable this behavior with
+  'set remember-threads-per-inferior off' to use the original behavior
+  of always selecting the first available thread.
+
 * Support for .gdb_index sections with version less than 7 has been
   removed.
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ceb69669ea6..dcbbcb273c1 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3516,6 +3516,25 @@  To switch focus between inferiors, use the @code{inferior} command:
 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.
+
+By default, when switching to an inferior, @value{GDBN} will restore the
+thread that was previously selected in that inferior (if it still exists).
+This behavior can be controlled with the @code{set remember-threads-per-inferior}
+command, described below.
+@end table
+
+@cindex remember threads per inferior
+@kindex set remember-threads-per-inferior
+@kindex show remember-threads-per-inferior
+@table @code
+@item set remember-threads-per-inferior on|off
+Control whether @value{GDBN} remembers the selected thread for each inferior.
+When enabled (the default), switching to a previously used inferior will
+restore the thread that was active when you switched away.  When disabled,
+@value{GDBN} always selects the first available thread.
+
+@item show remember-threads-per-inferior
+Show whether @value{GDBN} remembers the selected thread for each inferior.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 68d7aebbbf7..cecbef8be20 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -906,6 +906,10 @@  class scoped_restore_current_thread
   scoped_restore_current_language m_lang;
 };
 
+/* Return true if there is a current thread.  */
+
+bool has_current_thread ();
+
 /* Returns a pointer into the thread_info corresponding to
    INFERIOR_PTID.  INFERIOR_PTID *must* be in the thread list.  */
 extern struct thread_info* inferior_thread (void);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index e050dec402e..5ea00839692 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -46,6 +46,9 @@  static int highest_inferior_num;
 /* See inferior.h.  */
 bool print_inferior_events = true;
 
+/* Control whether to remember selected threads per inferior.  */
+bool remember_inferior_threads = true;
+
 /* The Current Inferior.  This is a strong reference.  I.e., whenever
    an inferior is the current inferior, its refcount is
    incremented.  */
@@ -508,6 +511,25 @@  inferior_pid_to_str (int pid)
 
 /* See inferior.h.  */
 
+void
+save_inferior_last_thread ()
+{
+  if (has_current_thread ())
+    current_inferior ()->last_user_thread
+      = thread_info_ref::new_reference (inferior_thread ());
+  else
+    current_inferior ()->last_user_thread = nullptr;
+}
+
+static void
+inferiors_on_user_selected_context_changed (user_selected_what selection)
+{
+  if (remember_inferior_threads && (selection & USER_SELECTED_THREAD))
+    save_inferior_last_thread ();
+}
+
+/* See inferior.h.  */
+
 void
 print_selected_inferior (struct ui_out *uiout)
 {
@@ -771,8 +793,23 @@  inferior_command (const char *args, int from_tty)
 	{
 	  if (inf != current_inferior ())
 	    {
-	      thread_info *tp = any_thread_of_inferior (inf);
-	      if (tp == NULL)
+	      thread_info *tp = nullptr;
+
+	      if (remember_inferior_threads)
+		{
+		  thread_info *stored_tp = inf->last_user_thread.get ();
+
+		  /* Fallback to selecting any non-exited thread of
+		     inferior.  */
+		  tp = (stored_tp == nullptr
+			|| stored_tp->state == THREAD_EXITED)
+		    ? any_thread_of_inferior (inf)
+		    : stored_tp;
+		}
+	      else
+		tp = any_thread_of_inferior (inf);
+
+	      if (tp == nullptr)
 		error (_("Inferior has no threads."));
 
 	      switch_to_thread (tp);
@@ -1255,5 +1292,22 @@  Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("remember-threads-per-inferior", no_class,
+	 &remember_inferior_threads, _("\
+Set whether GDB will cache the current thread in each inferior."), _("\
+Show whether GDB will cache the current thread in each inferior."), _("\
+When on, GDB records which thread was current when switching away from\n\
+an inferior, and automatically restores that thread when switching back\n\
+to that inferior (if the thread still exists).  When off, GDB selects\n\
+the first available thread."),
+	 NULL,
+	 NULL,
+	 &setlist, &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
+
+  /* Observe user_selected_context_changed to store the current user
+     thread.  */
+  gdb::observers::user_selected_context_changed.attach (
+      inferiors_on_user_selected_context_changed, "inferiors");
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index fbf9765fb0e..49f0406710c 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -265,6 +265,10 @@  extern int stopped_by_random_signal;
    `set print inferior-events'.  */
 extern bool print_inferior_events;
 
+/* Control whether GDB remembers the selected thread per inferior, set with
+   `set remember-threads-per-inferior'.  */
+extern bool remember_inferior_threads;
+
 /* Anything but NO_STOP_QUIETLY means we expect a trap and the caller
    will handle it themselves.  STOP_QUIETLY is used when running in
    the shell before the child program has been exec'd and when running
@@ -685,6 +689,10 @@  class inferior : public refcounted_object,
   /* Per inferior data-pointers required by other GDB modules.  */
   registry<inferior> registry_fields;
 
+  /* The last thread that was current when inferior was switched away
+     from.  */
+  thread_info_ref last_user_thread;
+
 private:
 
   /* Unpush TARGET and assert that it worked.  */
@@ -891,6 +899,12 @@  extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Record the selected thread of the current inferior when the user
+   switches a context.  GDB will select the recorded thread, if alive,
+   instead of any thread from the current inferior.  */
+
+extern void save_inferior_last_thread ();
+
 /* Return true if ID is a valid global inferior number.  */
 
 inline bool
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9864b5bbdec..35292e13d95 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4774,7 +4774,11 @@  fetch_inferior_event ()
 	    if (!non_stop
 		&& cmd_done
 		&& ecs.ws.kind () != TARGET_WAITKIND_NO_RESUMED)
-	      restore_thread.dont_restore ();
+	      {
+		restore_thread.dont_restore ();
+		if (remember_inferior_threads)
+		  save_inferior_last_thread ();
+	      }
 	  }
       }
 
diff --git a/gdb/testsuite/gdb.base/inferior-switch.c b/gdb/testsuite/gdb.base/inferior-switch.c
new file mode 100644
index 00000000000..107090d003c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/inferior-switch.c
@@ -0,0 +1,42 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+   Copyright 2024 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 <unistd.h>
+#include <pthread.h>
+
+static void *
+task (void *arg)
+{
+  volatile unsigned int duration = 0;
+
+  int a = 1; /* worker thread break 1.  */
+
+  sleep (duration);
+  int b = 2; /* worker thread break 2.  */
+
+  sleep (duration);
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t th;
+
+  alarm (30);
+
+  pthread_create (&th, NULL, task, NULL);
+  pthread_join (th, NULL);
+
+  return 0; /* main thread break.  */
+}
diff --git a/gdb/testsuite/gdb.base/inferior-switch.exp b/gdb/testsuite/gdb.base/inferior-switch.exp
new file mode 100644
index 00000000000..4ac1aa707c2
--- /dev/null
+++ b/gdb/testsuite/gdb.base/inferior-switch.exp
@@ -0,0 +1,172 @@ 
+# Copyright 2024 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 test covers retaining of current thread when switching between
+# inferiors.
+#
+# It starts two inferiors that have 2 threads each, a main thread and a
+# thread that will have strategically placed sleeps to control execution
+# behaviour.
+#
+# test_explicit: Switch threads and switch between inferiors during stops.
+# Check that GDB remembers the previously selected thread of an inferior when
+# we switch to that inferior.
+#
+# test_implicit: Check that GDB remembers the previously selected thread of
+# an inferior when we are implicitly switched to another thread due to an
+# event.
+
+standard_testfile
+
+# This testcase explicitly creates another inferior.
+if {[use_gdb_stub]} {
+    return
+}
+
+if {[build_executable "failed to prepare" $testfile $srcfile \
+    {debug pthreads}] == -1} {
+    return -1
+}
+
+proc switch_inferior { num msg } {
+    gdb_test "inferior ${num}" "\\\[Switching to inferior ${num}.*" "${msg}"
+}
+
+proc switch_inferior_expect_thread { inf tid msg } {
+    gdb_test "inferior ${inf}" "\\\[Switching to thread $tid.*" "${msg}"
+}
+
+proc switch_thread { tid msg } {
+    gdb_test "thread $tid" "\\\[Switching to thread $tid.*" "${msg}"
+}
+
+# Common test preparation: start two inferiors and let them run to BP
+# at "worker thread break 1" location.
+
+proc_with_prefix prepare_test { } {
+    global binfile testfile
+
+    clean_restart $testfile
+
+    gdb_breakpoint [gdb_get_line_number "worker thread break 1"]
+
+    # Add another inferior, switch to it, load the binary and run it to after
+    # additional threads have started.
+    gdb_test "add-inferior" "Added inferior 2.*" \
+	"add new inferior"
+    switch_inferior 2 "switch to the new inferior 2"
+
+    gdb_test "file $binfile" ".*" "load binary for inferior 2"
+    gdb_test_no_output "set remote exec-file $binfile" \
+	"set remote exec-file"
+
+
+    gdb_test "run" \
+	"Thread 2.2.*hit Breakpoint 1.2.*" \
+	"inferior 2 run to breakpoint break"
+
+    switch_inferior 1 "switch to inferior 1"
+
+    gdb_test "run" \
+	"Thread 1.2.*hit Breakpoint 1.1.*" \
+	"inferior 1 run to breakpoint break"
+}
+
+# Test scenario for verifying that during stops, explicit inferior and thread
+# switches store the correct last user thread.
+
+proc_with_prefix test_explicit {} {
+    prepare_test
+
+    switch_inferior_expect_thread 2 2.2 "switch to inferior 2 thread 2.2"
+    switch_inferior_expect_thread 1 1.2 "back to inferior 1 thread 1.2"
+
+    gdb_continue_to_end "inferior 1" continue 1
+
+    switch_inferior_expect_thread 2 2.2 "again back to inferior 2 thread 2.2"
+
+    gdb_continue_to_end "inferior 2" continue 1
+}
+
+# Test scenario for verifying that during stops, implicit inferior and thread
+# switches store the correct last user thread.
+
+proc_with_prefix test_implicit {} {
+    prepare_test
+
+    gdb_breakpoint [gdb_get_line_number "worker thread break 2"]
+
+    # Enable sleeps in inferior 1 worker thread to slow it down.  Consequently
+    # breakpoint in inferior 2 will be hit first.
+    gdb_test "print duration = 2" " = 2"
+
+    switch_thread 2.1 "switch inferior 2 to main thread"
+    switch_thread 1.1 "switch inferior 1 to main thread"
+
+    gdb_test_no_output "set schedule-multiple on"
+
+    gdb_test "continue" \
+	".*Thread 2.2.*Breakpoint 2.2.*" \
+	"continue next breakpoint expect T2.2"
+
+    # Enable sleeps in inferior 2 worker thread to slow it down.  Consequently
+    # next breakpoint in inferior 1 will be hit first.
+    gdb_test "print duration = 3" " = 3"
+
+    gdb_test "continue" \
+	".*Thread 1.2.*Breakpoint 2.1.*" \
+	"continue next breakpoint expect T1.2"
+
+    switch_inferior_expect_thread 2 2.2 "switch inferior 2 stopped at 2nd BP"
+    switch_inferior_expect_thread 1 1.2 "switch inferior 1 stopped at 2nd BP"
+}
+
+# Test scenario for verifying that the feature can be disabled.
+
+proc_with_prefix test_disabled_behavior {} {
+    prepare_test
+
+    # Disable the feature
+    gdb_test_no_output "set remember-threads-per-inferior off" \
+	"disable remember-threads-per-inferior feature"
+
+    # Switch to thread 2.2
+    switch_thread 2.2 "switch to thread 2.2"
+
+    # Switch to inferior 1 - with feature disabled, should NOT remember thread 1.2,
+    # should pick first available (1.1)
+    gdb_test "inferior 1" "\\\[Switching to thread 1.1.*" \
+	"with feature off, switches to first thread"
+
+    # Switch to thread 1.2
+    switch_thread 1.2 "switch to thread 1.2"
+
+    # Switch to inferior 2 - with feature disabled, should NOT remember thread 2.2,
+    # should pick first available (2.1)
+    gdb_test "inferior 2" "\\\[Switching to thread 2.1.*" \
+	"with feature off, selects first thread in inferior"
+
+    # Re-enable the feature and verify it works again
+    gdb_test_no_output "set remember-threads-per-inferior on" \
+	"re-enable remember-threads-per-inferior feature"
+
+    # Now switching back to inferior 1 should remember 1.2
+    switch_inferior_expect_thread 1 1.2 \
+	"feature on again, restores previously selected thread 1.2"
+}
+
+test_explicit
+test_implicit
+test_disabled_behavior
diff --git a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 844206f7511..3b6f174a1a1 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -52,6 +52,14 @@  set main_break_line [gdb_get_line_number "main break line"]
 set thread_loop_line [gdb_get_line_number "thread loop line"]
 set thread_caller_line [gdb_get_line_number "thread caller line"]
 
+# Reset to thread 2.1.
+
+proc reset_to_known_thread { pfx } {
+    with_test_prefix "$pfx" {
+	reset_selection "2.1"
+    }
+}
+
 # Return whether we expect thread THREAD to be running in mode MODE.
 #
 # MODE can be either "all-stop" or "non-stop".
@@ -1398,6 +1406,9 @@  proc do_test { mode } {
     }
     pass $test
 
+    # Reset to known state.
+    reset_to_known_thread "before testing CLI"
+
     # Test selecting inferior, thread and frame from CLI
 
     test_cli_inferior $mode
@@ -1411,6 +1422,9 @@  proc do_test { mode } {
     test_mi_thread_select $mode
     test_mi_stack_select_frame $mode
 
+    # Reset to known state.
+    reset_to_known_thread "before testing MI"
+
     # Test some CLI commands sent through MI, both with a "direct" command,
     # such as "thread 1", and with -interpreter-exec, such as
     # '-interpreter-exec console "thread 1"'.
diff --git a/gdb/thread.c b/gdb/thread.c
index 96e3bb7b50f..72b44a95122 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -85,6 +85,14 @@  is_current_thread (const thread_info *thr)
   return thr == current_thread_;
 }
 
+/* See gdbthread.h.  */
+
+bool
+has_current_thread ()
+{
+  return current_thread_ != nullptr;
+}
+
 struct thread_info*
 inferior_thread (void)
 {