diff --git a/gdb/NEWS b/gdb/NEWS
index e33d838dd18..88a936d8d5d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -19,6 +19,13 @@
 
 * TUI windows can now be arranged horizontally.
 
+* GDB now tracks the currently selected frame for each thread.  When
+  switching threads the last frame selected in that thread will be
+  restored.  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' commands now shows
+  the selected frame in its 'frame' field.
+
 * New commands
 
 set exec-file-mismatch -- Set exec-file-mismatch handling (ask|warn|off).
@@ -36,6 +43,11 @@ tui new-layout NAME WINDOW WEIGHT [WINDOW WEIGHT]...
   Define a new TUI layout, specifying its name and the windows that
   will be displayed.
 
+set restore-selected-frame [on|off]
+show restore-selected-frame
+  When switching threads, restore the previously selected frame in the
+  thread being switched too.  This is on by default.
+
 * New targets
 
 GNU/Linux/RISC-V (gdbserver)	riscv*-*-linux*
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f1798e35b5f..123f3f80268 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3459,7 +3459,12 @@
 program itself.
 
 @item
-the current stack frame summary for that thread
+the current stack frame summary for that thread, this is the currently
+selected stack frame (@pxref{Frames, ,Frames}, for a description of
+stack frames) in this thread.  When @code{restore-selected-frame} is
+@code{off} (@pxref{set restore-selected-frame}) then all threads
+except the currently selected thread will display their innermost
+frame.
 @end enumerate
 
 @noindent
@@ -3528,6 +3533,14 @@
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
+@value{GDBN} will track the last frame selected (@pxref{Frames,
+,Frames}, for a description of stack frames) in each thread and will
+restore the frame when switching back to a thread.  Any time that a
+thread executes then the currently selected frame will be reset to the
+inner most frame (@pxref{set restore-selected-frame,,set
+restore-selected-frame} for how to disable restoring the last selected
+frame).
+
 @anchor{thread apply all}
 @kindex thread apply
 @cindex apply command to several threads
@@ -8229,6 +8242,24 @@
 distracting.
 @end table
 
+@anchor{set restore-selected-frame}
+@value{GDBN} remembers the currently selected frame for each thread
+(@pxref{Threads, ,Threads}, for a description of threads) in the
+inferior.  When switching thread the previously selected frame in each
+thread is restored.
+
+@table @code
+@item set restore-selected-frame @r{[}on|off@r{]}
+@itemx show restore-selected-frame
+When @code{restore-selected-frame} is on, which it is by default,
+@value{GDBN} will restore the previously selected frame when switching
+to a different 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.
+@end table
+
 @node Frame Info
 @section Information About a Frame
 
diff --git a/gdb/frame.c b/gdb/frame.c
index 863df107009..f9fde2397ad 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1704,12 +1704,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 26e572d1e98..0f046b09978 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -387,6 +387,11 @@ public:
      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;
 
@@ -581,8 +586,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.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 390df005542..9deac664f98 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -888,8 +888,8 @@ proc_with_prefix test_mi_thread_select { mode } {
 	# running on non-stop.
 
 	if { $mode == "all-stop" } {
-	    set mi_re [make_mi_re $mode 3 0 response]
-	    set cli_re [make_cli_re $mode -1 1.3 0]
+	    set mi_re [make_mi_re $mode 3 1 response]
+	    set cli_re [make_cli_re $mode -1 1.3 1]
 	} else {
 	    set mi_re [make_mi_re $mode 3 -1 response]
 	    set cli_re [make_cli_re $mode -1 1.3 -1]
@@ -1056,8 +1056,8 @@ proc_with_prefix test_cli_in_mi_thread { mode cli_in_mi_mode } {
 	# Do the 'thread' command to select a stopped thread.
 
 	set command [make_cli_in_mi_command $cli_in_mi_mode "thread 1.2"]
-	set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 1 -1 1.2 2 0]
-	set cli_re [make_cli_re $mode -1 1.2 0]
+	set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 1 -1 1.2 2 1]
+	set cli_re [make_cli_re $mode -1 1.2 1]
 
 	with_spawn_id $mi_spawn_id {
 	    mi_gdb_test $command $mi_re "select thread"
@@ -1070,7 +1070,7 @@ proc_with_prefix test_cli_in_mi_thread { mode cli_in_mi_mode } {
 	# Do the 'thread' command to select the same thread.  We shouldn't
 	# receive an event on CLI, since we won't actually switch thread.
 
-	set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 1.2 2 0]
+	set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 1.2 2 1]
 	set cli_re ""
 
 	with_spawn_id $mi_spawn_id {
@@ -1103,8 +1103,8 @@ proc_with_prefix test_cli_in_mi_thread { mode cli_in_mi_mode } {
 
 	set command [make_cli_in_mi_command $cli_in_mi_mode "thread 1.3"]
 	if { $mode == "all-stop" } {
-	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 1 -1 1.3 3 0]
-	    set cli_re [make_cli_re $mode -1 "1.3" 0]
+	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 1 -1 1.3 3 1]
+	    set cli_re [make_cli_re $mode -1 "1.3" 1]
 	} else {
 	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 1 -1 1.3 3 -1]
 	    set cli_re [make_cli_re $mode -1 "1.3" -1]
@@ -1122,7 +1122,7 @@ proc_with_prefix test_cli_in_mi_thread { mode cli_in_mi_mode } {
 	# shouldn't receive an event on MI.
 
 	if { $mode == "all-stop" } {
-	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 1.3 3 0]
+	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 1.3 3 1]
 	} else {
 	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 1.3 3 -1]
 	}
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..00e81bf449b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/restore-selected-frame.exp
@@ -0,0 +1,322 @@
+# 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
+    send_gdb "interrupt\n"
+    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_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
+
+set frame_info [list "$hex in pthread_join" \
+		    "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" \
+					"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" \
+		    "thread_worker" \
+		    "thread_level_5" \
+		    "thread_level_5" \
+		    "$hex in pthread_join" \
+		    "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" ".*"
+    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" \
+			"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" ".*"
+    set frame_info [list "$hex in pthread_join" \
+			"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 d0c795c5864..684ad6f93b0 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_info.reset ();
+    }
 }
 
 void
@@ -1043,6 +1046,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 = true;
+
+/* 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
@@ -1335,7 +1356,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);
 
@@ -1345,6 +1366,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.  */
@@ -1368,13 +1393,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);
+    switch_to_thread (m_thread, restore_selected_frame_per_thread);
   else
     switch_to_inferior_no_thread (m_inf);
 
   /* 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
@@ -1639,7 +1667,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);
     }
 }
@@ -1796,7 +1824,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;
@@ -1964,7 +1992,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 ();
@@ -2231,6 +2259,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);
 }
