This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH RFC] Decouple user selection from internal selection


I am sending this as an RFC because it's far from complete and
definitive, but I'd like to gather some comments and opinions before
going further in this direction.

The goal of this patch is to decouple the notion of the user-selected
inferior/thread/frame from GDB's internally selected
inferior/thread/frame.

Currently, for example, the inferior_ptid variable has two jobs:

 - it's the user-selected thread: it's changed by the "thread" command.
   Other commands (continue, backtrace, etc) apply to this thread.
 - it's the internally-selected thread: it defines the thread GDB is
   currently "working" on.  For example, implementations of
   to_xfer_partial will refer to it to know from which thread to
   read/write memory.

Because of this dual usage, if we want to do some operations on a thread
other than the currently selected one, we have to save the current
inferior/thread/frame and restore them when we're done.  Failing to do
so would result in an unexpected selection switch for the user.

To improve this, Pedro suggested in [1] to decouple the two concepts.  This
is essentially what this patch is trying to do.

A new "user_selection" object is introduced, which contains the selected
inferior/thread/frame from the point of view of the user.  Before every
command, we "apply" this selection to the core of GDB to make sure the
internal selection matches the user selection.

There is a single user selection for the whole GDB (named "global
user-selection"), but as was mentioned in the linked thread, it opens
the door to having different selections for different UIs.  This means
that each UI would have its own user-selection object, which would be
applied to the core prior to executing commands from this UI.

The global user-selection object only gets modified when we really
intend to change it.  It can be because of the thread / -thread-select /
up / down / frame / inferior commands, a breakpoint hit in all-stop, an
inferior exit, etc.

The problem that initially prompted this effort is that the "--thread"
flag of MI commands changes the user-selected thread under the user's
feet.  My initial attempt to fix it was to restore the selection after
the MI command execution.  However, some cases are hard to get right.
For example:

  (thread 1 is currently selected)
  -interpreter-exec --thread 2 console "thread 3"

Restoring the selected thread to thread 1 after the MI command execution
wrongfully cancels the switch to thread 3.  So it's hard to determine
when we should or shouldn't restore.   With the current patch, it works
naturally: the --thread flag doesn't touch the user-selected thread,
only the internal one.  The "thread 3" command updates the user
selection.

Another difficulty is to send the right notifications to MI when the
user selection changes.  That means to not miss any, but not send too
many either.  Getting it somewhat right lead to ugly hacks (see the
command_notifies_uscc_observer function) and even then it's not perfect
(see the kfails in user-selected-context-sync.exp test).  With the
proposed method, it's easy to know when the user-selection changes and
send notifications.

With this patch, there are probably a few usage of
make_cleanup_restore_current_thread that are not needed anymore, if they
are only used to restore the user selection.  I kept removing them for a
later time though.

In the current state, there are a few minor regressions in the testsuite
(especially some follow-fork stuff I'm not sure how to handle), but the
vast majority of the previously passing tests still pass.

I've pushed the patch to users/simark/user-selection-rfc on sourceware.
Comments are welcome!

Thanks,

Simon

[1] https://sourceware.org/ml/gdb-patches/2016-08/msg00031.html
---
 gdb/Makefile.in                                    |   2 +
 gdb/cli-out.h                                      |   9 +-
 gdb/cli/cli-interp.c                               |  17 +-
 gdb/doc/observer.texi                              |   7 +-
 gdb/event-top.c                                    |   4 +
 gdb/frame.c                                        |   2 +
 gdb/frame.h                                        |   2 +
 gdb/gdb.h                                          |   4 -
 gdb/gdbthread.h                                    |  26 +-
 gdb/infcall.c                                      |   2 +-
 gdb/inferior.c                                     |  44 +--
 gdb/inferior.h                                     |   5 +-
 gdb/infrun.c                                       |  49 +--
 gdb/jit.c                                          |   3 +
 gdb/mi/mi-cmd-stack.c                              |   6 +-
 gdb/mi/mi-interp.c                                 |  30 +-
 gdb/mi/mi-main.c                                   |  89 +----
 gdb/observer.sh                                    |   1 +
 gdb/stack.c                                        |  85 +++--
 gdb/stack.h                                        |   5 +-
 gdb/testsuite/gdb.mi/mi-pthreads.exp               |   2 +-
 gdb/testsuite/gdb.mi/mi-return.exp                 |   2 +-
 .../gdb.mi/user-selected-context-sync.exp          |  20 +-
 gdb/thread.c                                       | 167 +++++-----
 gdb/top.c                                          |   2 +
 gdb/tracefile-tfile.c                              |   2 +
 gdb/tui/tui-interp.c                               |  20 +-
 gdb/ui-out.h                                       |  20 ++
 gdb/user-selection.c                               | 357 +++++++++++++++++++++
 gdb/user-selection.h                               | 100 ++++++
 30 files changed, 766 insertions(+), 318 deletions(-)
 create mode 100644 gdb/user-selection.c
 create mode 100644 gdb/user-selection.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 268c2c6ead..85706a5909 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1189,6 +1189,7 @@ SFILES = \
 	ui-file.h \
 	ui-out.c \
 	user-regs.c \
+	user-selection.c \
 	utils.c \
 	valarith.c \
 	valops.c \
@@ -1791,6 +1792,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
 	ui-file.o \
 	ui-out.o \
 	user-regs.o \
+	user-selection.o \
 	utils.o \
 	utils-selftests.o \
 	valarith.o \
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index 1b6a1ade81..5a775e9881 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -32,6 +32,12 @@ public:
 
   ui_file *set_stream (ui_file *stream);
 
+  bool suppress_output ()
+  { return m_suppress_output; }
+
+  void suppress_output (bool val)
+  { m_suppress_output = val; }
+
 protected:
 
   virtual void do_table_begin (int nbrofcols, int nr_rows,
@@ -62,9 +68,6 @@ protected:
   virtual void do_flush () override;
   virtual void do_redirect (struct ui_file *outstream) override;
 
-  bool suppress_output ()
-  { return m_suppress_output; }
-
 private:
 
   void field_separator ();
diff --git a/gdb/cli/cli-interp.c b/gdb/cli/cli-interp.c
index 8712c75f39..d460a68771 100644
--- a/gdb/cli/cli-interp.c
+++ b/gdb/cli/cli-interp.c
@@ -29,6 +29,7 @@
 #include "observer.h"
 #include "gdbthread.h"
 #include "thread-fsm.h"
+#include "user-selection.h"
 
 cli_interp_base::cli_interp_base (const char *name)
   : interp (name)
@@ -250,15 +251,15 @@ cli_on_command_error (void)
 /* Observer for the user_selected_context_changed notification.  */
 
 static void
-cli_on_user_selected_context_changed (user_selected_what selection)
+cli_on_global_user_selection_changed (user_selection *us,
+				      user_selected_what selection)
 {
-  struct thread_info *tp;
-
   /* This event is suppressed.  */
   if (cli_suppress_notification.user_selected_context)
     return;
 
-  tp = find_thread_ptid (inferior_ptid);
+  struct thread_info *tp = us->thread ();
+  struct inferior *inf = us->inferior ();
 
   SWITCH_THRU_ALL_UIS ()
     {
@@ -268,11 +269,11 @@ cli_on_user_selected_context_changed (user_selected_what selection)
 	continue;
 
       if (selection & USER_SELECTED_INFERIOR)
-	print_selected_inferior (cli->cli_uiout);
+	print_selected_inferior (cli->cli_uiout, inf);
 
       if (tp != NULL
 	  && ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME))))
-	print_selected_thread_frame (cli->cli_uiout, selection);
+	print_selected_thread_frame (cli->cli_uiout, us, selection);
     }
 }
 
@@ -474,6 +475,6 @@ _initialize_cli_interp (void)
   observer_attach_no_history (cli_on_no_history);
   observer_attach_sync_execution_done (cli_on_sync_execution_done);
   observer_attach_command_error (cli_on_command_error);
-  observer_attach_user_selected_context_changed
-    (cli_on_user_selected_context_changed);
+  observer_attach_global_user_selection_changed
+    (cli_on_global_user_selection_changed);
 }
diff --git a/gdb/doc/observer.texi b/gdb/doc/observer.texi
index 606ddfe536..f786b1e2b7 100644
--- a/gdb/doc/observer.texi
+++ b/gdb/doc/observer.texi
@@ -307,7 +307,8 @@ This observer is used for internal testing.  Do not use.
 See testsuite/gdb.gdb/observer.exp.
 @end deftypefun
 
-@deftypefun void user_selected_context_changed (user_selected_what @var{selection})
-The user-selected inferior, thread and/or frame has changed.  The user_select_what
-flag specifies if the inferior, thread and/or frame has changed.
+@deftypefun void global_user_selection_changed (user_selection *@var{us}, user_selected_what @var{selection})
+The user-selected inferior, thread and/or frame in US has changed.  The
+user_select_what flag specifies if the inferior, thread and/or frame has
+changed.
 @end deftypefun
diff --git a/gdb/event-top.c b/gdb/event-top.c
index 5d8d077a9a..9056b46c60 100644
--- a/gdb/event-top.c
+++ b/gdb/event-top.c
@@ -40,6 +40,7 @@
 #include "buffer.h"
 #include "ser-event.h"
 #include "gdb_select.h"
+#include "user-selection.h"
 
 /* readline include files.  */
 #include "readline/readline.h"
@@ -582,6 +583,9 @@ command_handler (char *command)
 
   scoped_command_stats stat_reporter (true);
 
+  /* Before executing the command, apply the user selection to the gdb core.  */
+  apply_global_user_selection ();
+
   /* Do not execute commented lines.  */
   for (c = command; *c == ' ' || *c == '\t'; c++)
     ;
diff --git a/gdb/frame.c b/gdb/frame.c
index d98003dee7..93f658534d 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -42,6 +42,7 @@
 #include "tracepoint.h"
 #include "hashtab.h"
 #include "valprint.h"
+#include "user-selection.h"
 
 /* The sentinel frame terminates the innermost end of the frame chain.
    If unwound, it returns the information needed to construct an
@@ -1791,6 +1792,7 @@ reinit_frame_cache (void)
 
   sentinel_frame = NULL;		/* Invalidate cache */
   select_frame (NULL);
+  global_user_selection ()->select_frame (NULL, false);
   frame_stash_invalidate ();
   if (frame_debug)
     fprintf_unfiltered (gdb_stdlog, "{ reinit_frame_cache () }\n");
diff --git a/gdb/frame.h b/gdb/frame.h
index 1d0644f12d..5c68febde6 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -79,6 +79,8 @@ struct gdbarch;
 struct ui_file;
 struct ui_out;
 
+#define INVALID_FRAME_LEVEL -1
+
 /* Status of a given frame's stack.  */
 
 enum frame_id_stack_status
diff --git a/gdb/gdb.h b/gdb/gdb.h
index ac1e683314..c224d419be 100644
--- a/gdb/gdb.h
+++ b/gdb/gdb.h
@@ -47,10 +47,6 @@ enum gdb_rc {
 enum gdb_rc gdb_breakpoint_query (struct ui_out *uiout, int bnum,
 				  char **error_message);
 
-/* Switch thread and print notification.  */
-enum gdb_rc gdb_thread_select (struct ui_out *uiout, char *tidstr,
-			       char **error_message);
-
 /* Print a list of known thread ids.  */
 enum gdb_rc gdb_list_thread_ids (struct ui_out *uiout,
 				 char **error_message);
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 06ed78f568..47cf1258a1 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -22,6 +22,7 @@
 #define GDBTHREAD_H
 
 struct symtab;
+class user_selection;
 
 #include "breakpoint.h"
 #include "frame.h"
@@ -253,7 +254,13 @@ struct thread_info
   /* If this is > 0, then it means there's code out there that relies
      on this thread being listed.  Don't delete it from the lists even
      if we detect it exiting.  */
-  int refcount;
+  int refcount_;
+
+  void get ()
+  { refcount_++; }
+
+  void put ()
+  { refcount_--; }
 
   /* State of GDB control of inferior thread execution.
      See `struct thread_control_state'.  */
@@ -440,8 +447,9 @@ void thread_change_ptid (ptid_t old_ptid, ptid_t new_ptid);
 
 /* Iterator function to call a user-provided callback function
    once for each known thread.  */
-typedef int (*thread_callback_func) (struct thread_info *, void *);
-extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
+typedef std::function<int(struct thread_info *, void*)> thread_callback_func;
+extern struct thread_info *iterate_over_threads (thread_callback_func callback,
+						 void *data = nullptr);
 
 /* Traverse all threads.  */
 #define ALL_THREADS(T)				\
@@ -469,6 +477,9 @@ extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
 
 extern int thread_count (void);
 
+/* Change the user-selected thread.  */
+extern bool thread_select (const char *tidstr, bool tid_is_qualified);
+
 /* Switch from one thread to another.  Also sets the STOP_PC
    global.  */
 extern void switch_to_thread (ptid_t ptid);
@@ -508,15 +519,17 @@ extern void set_stop_requested (ptid_t ptid, int stop);
    The latter also returns true on exited threads, most likelly not
    what you want.  */
 
-/* Reports if in the frontend's perpective, thread PTID is running.  */
+/* Reports if in the frontend's perspective, thread PTID is running.  */
 extern int is_running (ptid_t ptid);
 
 /* Is this thread listed, but known to have exited?  We keep it listed
    (but not visible) until it's safe to delete.  */
 extern int is_exited (ptid_t ptid);
+extern bool is_exited (struct thread_info *thread);
 
-/* In the frontend's perpective, is this thread stopped?  */
+/* In the frontend's perspective, is this thread stopped?  */
 extern int is_stopped (ptid_t ptid);
+extern bool is_stopped (struct thread_info *thread);
 
 /* Marks thread PTID as executing, or not.  If PTID is minus_one_ptid,
    marks all threads.
@@ -636,7 +649,8 @@ extern int show_thread_that_caused_stop (void);
 
 /* Print the message for a thread or/and frame selected.  */
 extern void print_selected_thread_frame (struct ui_out *uiout,
-					 user_selected_what selection);
+					 user_selection *us,
+				         user_selected_what selection);
 
 extern struct thread_info *thread_list;
 
diff --git a/gdb/infcall.c b/gdb/infcall.c
index f55acb5c27..fc3e4e7e75 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -1147,7 +1147,7 @@ call_function_by_hand_dummy (struct value *function,
     observer_notify_inferior_call_post (call_thread_ptid, funaddr);
 
     tp = find_thread_ptid (call_thread_ptid);
-    if (tp != NULL)
+    if (tp != NULL && !is_exited (tp))
       {
 	/* The FSM should still be the same.  */
 	gdb_assert (tp->thread_fsm == &sm->thread_fsm);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index c4ab6d7d7f..d9d026a936 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -35,6 +35,7 @@
 #include "arch-utils.h"
 #include "target-descriptions.h"
 #include "readline/tilde.h"
+#include "user-selection.h"
 
 void _initialize_inferiors (void);
 
@@ -554,9 +555,8 @@ inferior_pid_to_str (int pid)
 /* See inferior.h.  */
 
 void
-print_selected_inferior (struct ui_out *uiout)
+print_selected_inferior (struct ui_out *uiout, struct inferior *inf)
 {
-  struct inferior *inf = current_inferior ();
   const char *filename = inf->pspace->pspace_exec_filename;
 
   if (filename == NULL)
@@ -566,8 +566,7 @@ print_selected_inferior (struct ui_out *uiout)
 		  inf->num, inferior_pid_to_str (inf->pid), filename);
 }
 
-/* Prints the list of inferiors and their details on UIOUT.  This is a
-   version of 'info_inferior_command' suitable for use from MI.
+/* Prints the list of inferiors and their details on UIOUT.
 
    If REQUESTED_INFERIORS is not NULL, it's a list of GDB ids of the
    inferiors that should be printed.  Otherwise, all inferiors are
@@ -579,6 +578,7 @@ print_inferior (struct ui_out *uiout, char *requested_inferiors)
   struct inferior *inf;
   struct cleanup *old_chain;
   int inf_count = 0;
+  struct inferior *selected_inferior = global_user_selection ()->inferior ();
 
   /* Compute number of inferiors we will print.  */
   for (inf = inferior_list; inf; inf = inf->next)
@@ -612,7 +612,7 @@ print_inferior (struct ui_out *uiout, char *requested_inferiors)
 
       chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
 
-      if (inf == current_inferior ())
+      if (inf == selected_inferior)
 	uiout->field_string ("current", "*");
       else
 	uiout->field_skip ("current");
@@ -732,6 +732,7 @@ inferior_command (char *args, int from_tty)
 {
   struct inferior *inf;
   int num;
+  user_selection *us = global_user_selection ();
 
   num = parse_and_eval_long (args);
 
@@ -739,31 +740,12 @@ inferior_command (char *args, int from_tty)
   if (inf == NULL)
     error (_("Inferior ID %d not known."), num);
 
-  if (inf->pid != 0)
+  /* Keep the old behavior of printing "Switching to inferior X" even if it was
+     already the selected inferior.  */
+  if (!us->select_inferior (inf, true))
     {
-      if (inf->pid != ptid_get_pid (inferior_ptid))
-	{
-	  struct thread_info *tp;
-
-	  tp = any_thread_of_process (inf->pid);
-	  if (!tp)
-	    error (_("Inferior has no threads."));
-
-	  switch_to_thread (tp->ptid);
-	}
-
-      observer_notify_user_selected_context_changed
-	(USER_SELECTED_INFERIOR
-	 | USER_SELECTED_THREAD
-	 | USER_SELECTED_FRAME);
-    }
-  else
-    {
-      set_current_inferior (inf);
-      switch_to_thread (null_ptid);
-      set_current_program_space (inf->pspace);
-
-      observer_notify_user_selected_context_changed (USER_SELECTED_INFERIOR);
+      print_selected_inferior (current_uiout, us->inferior ());
+      print_selected_thread_frame (current_uiout, us, USER_SELECTED_THREAD | USER_SELECTED_FRAME);
     }
 }
 
@@ -783,6 +765,8 @@ remove_inferior_command (char *args, int from_tty)
   if (args == NULL || *args == '\0')
     error (_("Requires an argument (inferior id(s) to remove)"));
 
+  struct inferior *selected_inferior = global_user_selection ()->inferior ();
+
   number_or_range_parser parser (args);
   while (!parser.finished ())
     {
@@ -795,7 +779,7 @@ remove_inferior_command (char *args, int from_tty)
 	  continue;
 	}
 
-      if (inf == current_inferior ())
+      if (inf == selected_inferior)
 	{
 	  warning (_("Can not remove current inferior %d."), num);
 	  continue;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 7c0ddf37f1..b06866f490 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -544,7 +544,8 @@ extern int number_of_inferiors (void);
 
 extern struct inferior *add_inferior_with_spaces (void);
 
-/* Print the current selected inferior.  */
-extern void print_selected_inferior (struct ui_out *uiout);
+/* Print the "Switching to inferior X" message using INF.  */
+extern void print_selected_inferior (struct ui_out *uiout,
+				     struct inferior *inf);
 
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 1e5e9f14a6..a55acdf6dd 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -64,6 +64,7 @@
 #include "event-loop.h"
 #include "thread-fsm.h"
 #include "common/enum-flags.h"
+#include "user-selection.h"
 
 /* Prototypes for local functions */
 
@@ -152,12 +153,6 @@ show_step_stop_if_no_debug (struct ui_file *file, int from_tty,
   fprintf_filtered (file, _("Mode of the step operation is %s.\n"), value);
 }
 
-/* proceed and normal_stop use this to notify the user when the
-   inferior stopped in a different thread than it had been running
-   in.  */
-
-static ptid_t previous_inferior_ptid;
-
 /* If set (default for legacy reasons), when following a fork, GDB
    will detach from one of the fork branches, child or parent.
    Exactly which branch is detached depends on 'set follow-fork-mode'
@@ -779,6 +774,11 @@ follow_fork (void)
 	      {
 		switch_to_thread (child);
 
+		/* Switch the user-selected thread as well.  */
+		struct thread_info *child_tp = find_thread_ptid (child);
+		gdb_assert (child_tp != nullptr);
+		global_user_selection ()->select_thread (child_tp, false);
+
 		/* ... and preserve the stepping state, in case the
 		   user was stepping over the fork call.  */
 		if (should_resume)
@@ -809,7 +809,14 @@ follow_fork (void)
 		follow_inferior_reset_breakpoints ();
 	      }
 	    else
-	      switch_to_thread (parent);
+	      {
+		switch_to_thread (parent);
+
+		/* Switch the user-selected thread as well.  */
+		struct thread_info *parent_tp = find_thread_ptid (parent);
+		gdb_assert (parent_tp != nullptr);
+		global_user_selection ()->select_thread (parent_tp, false);
+	      }
 	  }
       }
       break;
@@ -2999,9 +3006,6 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
       return;
     }
 
-  /* We'll update this if & when we switch to a new thread.  */
-  previous_inferior_ptid = inferior_ptid;
-
   regcache = get_current_regcache ();
   gdbarch = get_regcache_arch (regcache);
   aspace = get_regcache_aspace (regcache);
@@ -3252,8 +3256,6 @@ init_wait_for_inferior (void)
 
   target_last_wait_ptid = minus_one_ptid;
 
-  previous_inferior_ptid = inferior_ptid;
-
   /* Discard any skipped inlined frames.  */
   clear_inline_frame_state (minus_one_ptid);
 }
@@ -8157,7 +8159,7 @@ save_stop_context (void)
       /* Take a strong reference so that the thread can't be deleted
 	 yet.  */
       sc->thread = inferior_thread ();
-      sc->thread->refcount++;
+      sc->thread->get ();
     }
   else
     sc->thread = NULL;
@@ -8174,7 +8176,8 @@ release_stop_context_cleanup (void *arg)
   struct stop_context *sc = (struct stop_context *) arg;
 
   if (sc->thread != NULL)
-    sc->thread->refcount--;
+    sc->thread->put ();
+
   xfree (sc);
 }
 
@@ -8262,20 +8265,16 @@ normal_stop (void)
      after this event is handled, so we're not really switching, only
      informing of a stop.  */
   if (!non_stop
-      && !ptid_equal (previous_inferior_ptid, inferior_ptid)
       && target_has_execution
       && last.kind != TARGET_WAITKIND_SIGNALLED
       && last.kind != TARGET_WAITKIND_EXITED
       && last.kind != TARGET_WAITKIND_NO_RESUMED)
     {
-      SWITCH_THRU_ALL_UIS ()
-	{
-	  target_terminal_ours_for_output ();
-	  printf_filtered (_("[Switching to %s]\n"),
-			   target_pid_to_str (inferior_ptid));
-	  annotate_thread_changed ();
-	}
-      previous_inferior_ptid = inferior_ptid;
+      struct thread_info *thread = find_thread_ptid (inferior_ptid);
+
+      gdb_assert (thread != nullptr);
+
+      global_user_selection ()->select_thread (thread, false);
     }
 
   if (last.kind == TARGET_WAITKIND_NO_RESUMED)
@@ -8986,7 +8985,7 @@ struct infcall_control_state
   enum stop_stack_kind stop_stack_dummy;
   int stopped_by_random_signal;
 
-  /* ID if the selected frame when the inferior function call was made.  */
+  /* ID of the selected frame when the inferior function call was made.  */
   struct frame_id selected_frame_id;
 };
 
@@ -9080,6 +9079,8 @@ restore_infcall_control_state (struct infcall_control_state *inf_status)
 	/* Error in restoring the selected frame.  Select the innermost
 	   frame.  */
 	select_frame (get_current_frame ());
+
+      global_user_selection ()->select_frame (get_selected_frame (NULL), false);
     }
 
   xfree (inf_status);
diff --git a/gdb/jit.c b/gdb/jit.c
index 158d6d8215..d58457da02 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -41,6 +41,7 @@
 #include "gdb_bfd.h"
 #include "readline/tilde.h"
 #include "completer.h"
+#include "user-selection.h"
 
 static const char *jit_reader_dir = NULL;
 
@@ -226,6 +227,7 @@ jit_reader_load_command (char *args, int from_tty)
 
   loaded_jit_reader = jit_reader_load (so_name);
   reinit_frame_cache ();
+  global_user_selection ()->select_frame (NULL, false);
   jit_inferior_created_hook ();
   do_cleanups (prev_cleanup);
 }
@@ -239,6 +241,7 @@ jit_reader_unload_command (char *args, int from_tty)
     error (_("No JIT reader loaded."));
 
   reinit_frame_cache ();
+  global_user_selection ()->select_frame (NULL, false);
   jit_inferior_exit_hook (current_inferior ());
   loaded_jit_reader->functions->destroy (loaded_jit_reader->functions);
 
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index acb44a28ee..fadfdaa435 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -34,6 +34,7 @@
 #include "extension.h"
 #include <ctype.h>
 #include "mi-parse.h"
+#include "user-selection.h"
 
 enum what_to_list { locals, arguments, all };
 
@@ -696,7 +697,10 @@ mi_cmd_stack_select_frame (char *command, char **argv, int argc)
   if (argc == 0 || argc > 1)
     error (_("-stack-select-frame: Usage: FRAME_SPEC"));
 
-  select_frame_command (argv[0], 1 /* not used */ );
+  struct frame_info *frame = parse_frame_specification (argv[0], NULL);
+  user_selection *us = global_user_selection ();
+
+  us->select_frame (frame, true);
 }
 
 void
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index 86340e4a08..958f154130 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -39,6 +39,7 @@
 #include "cli-out.h"
 #include "thread-fsm.h"
 #include "cli/cli-interp.h"
+#include "user-selection.h"
 
 /* These are the interpreter setup, etc. functions for the MI
    interpreter.  */
@@ -1282,16 +1283,17 @@ mi_memory_changed (struct inferior *inferior, CORE_ADDR memaddr,
    changed.  */
 
 static void
-mi_user_selected_context_changed (user_selected_what selection)
+mi_on_global_user_selection_changed (user_selection *us,
+				  user_selected_what selection)
 {
-  struct thread_info *tp;
+  struct inferior *inf = us->inferior ();
+  struct thread_info *thread = us->thread ();
+  struct frame_info *frame = us->frame ();
 
   /* Don't send an event if we're responding to an MI command.  */
   if (mi_suppress_notification.user_selected_context)
     return;
 
-  tp = find_thread_ptid (inferior_ptid);
-
   SWITCH_THRU_ALL_UIS ()
     {
       struct mi_interp *mi = as_mi_interp (top_level_interpreter ());
@@ -1311,23 +1313,19 @@ mi_user_selected_context_changed (user_selected_what selection)
       target_terminal_ours_for_output ();
 
       if (selection & USER_SELECTED_INFERIOR)
-	print_selected_inferior (mi->cli_uiout);
+	print_selected_inferior (mi->cli_uiout, inf);
 
-      if (tp != NULL
+      if (thread != NULL
 	  && (selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME)))
 	{
-	  print_selected_thread_frame (mi->cli_uiout, selection);
+	  print_selected_thread_frame (mi->cli_uiout, us, selection);
 
 	  fprintf_unfiltered (mi->event_channel,
 			      "thread-selected,id=\"%d\"",
-			      tp->global_num);
+			      thread->global_num);
 
-	  if (tp->state != THREAD_RUNNING)
-	    {
-	      if (has_stack_frames ())
-		print_stack_frame_to_uiout (mi_uiout, get_selected_frame (NULL),
-					    1, SRC_AND_LOC, 1);
-	    }
+	  if (thread->state != THREAD_RUNNING && frame != nullptr)
+	    print_stack_frame_to_uiout (mi_uiout, frame, 1, SRC_AND_LOC, 1);
 	}
 
       gdb_flush (mi->event_channel);
@@ -1439,6 +1437,6 @@ _initialize_mi_interp (void)
   observer_attach_command_param_changed (mi_command_param_changed);
   observer_attach_memory_changed (mi_memory_changed);
   observer_attach_sync_execution_done (mi_on_sync_execution_done);
-  observer_attach_user_selected_context_changed
-    (mi_user_selected_context_changed);
+  observer_attach_global_user_selection_changed
+    (mi_on_global_user_selection_changed);
 }
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index cf4e45ad41..63458d9113 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -54,6 +54,7 @@
 #include "extension.h"
 #include "gdbcmd.h"
 #include "observer.h"
+#include "user-selection.h"
 
 #include <ctype.h>
 #include "run-time-clock.h"
@@ -560,31 +561,15 @@ mi_cmd_target_flash_erase (char *command, char **argv, int argc)
 void
 mi_cmd_thread_select (char *command, char **argv, int argc)
 {
-  enum gdb_rc rc;
-  char *mi_error_message;
-  ptid_t previous_ptid = inferior_ptid;
-
   if (argc != 1)
     error (_("-thread-select: USAGE: threadnum."));
 
-  rc = gdb_thread_select (current_uiout, argv[0], &mi_error_message);
+  thread_select (argv[0], false);
 
-  /* If thread switch did not succeed don't notify or print.  */
-  if (rc == GDB_RC_FAIL)
-    {
-      make_cleanup (xfree, mi_error_message);
-      error ("%s", mi_error_message);
-    }
+  user_selection *us = global_user_selection ();
 
-  print_selected_thread_frame (current_uiout,
+  print_selected_thread_frame (current_uiout, us,
 			       USER_SELECTED_THREAD | USER_SELECTED_FRAME);
-
-  /* Notify if the thread has effectively changed.  */
-  if (!ptid_equal (inferior_ptid, previous_ptid))
-    {
-      observer_notify_user_selected_context_changed (USER_SELECTED_THREAD
-						     | USER_SELECTED_FRAME);
-    }
 }
 
 void
@@ -2025,6 +2010,8 @@ captured_mi_execute_command (struct ui_out *uiout, struct mi_parse *context)
       {
 	char *argv[2];
 
+	apply_global_user_selection ();
+
 	/* A CLI command was read from the input stream.  */
 	/* This "feature" will be removed as soon as we have a
 	   complete set of mi commands.  */
@@ -2085,34 +2072,6 @@ mi_print_exception (const char *token, struct gdb_exception exception)
   fputs_unfiltered ("\n", mi->raw_stdout);
 }
 
-/* Determine whether the parsed command already notifies the
-   user_selected_context_changed observer.  */
-
-static int
-command_notifies_uscc_observer (struct mi_parse *command)
-{
-  if (command->op == CLI_COMMAND)
-    {
-      /* CLI commands "thread" and "inferior" already send it.  */
-      return (strncmp (command->command, "thread ", 7) == 0
-	      || strncmp (command->command, "inferior ", 9) == 0);
-    }
-  else /* MI_COMMAND */
-    {
-      if (strcmp (command->command, "interpreter-exec") == 0
-	  && command->argc > 1)
-	{
-	  /* "thread" and "inferior" again, but through -interpreter-exec.  */
-	  return (strncmp (command->argv[1], "thread ", 7) == 0
-		  || strncmp (command->argv[1], "inferior ", 9) == 0);
-	}
-
-      else
-	/* -thread-select already sends it.  */
-	return strcmp (command->command, "thread-select") == 0;
-    }
-}
-
 void
 mi_execute_command (const char *cmd, int from_tty)
 {
@@ -2139,7 +2098,6 @@ mi_execute_command (const char *cmd, int from_tty)
 
   if (command != NULL)
     {
-      ptid_t previous_ptid = inferior_ptid;
       struct cleanup *cleanup = make_cleanup (null_cleanup, NULL);
 
       command->token = token;
@@ -2178,39 +2136,6 @@ mi_execute_command (const char *cmd, int from_tty)
 
       bpstat_do_actions ();
 
-      if (/* The notifications are only output when the top-level
-	     interpreter (specified on the command line) is MI.  */
-	  interp_ui_out (top_level_interpreter ())->is_mi_like_p ()
-	  /* Don't try report anything if there are no threads --
-	     the program is dead.  */
-	  && thread_count () != 0
-	  /* If the command already reports the thread change, no need to do it
-	     again.  */
-	  && !command_notifies_uscc_observer (command))
-	{
-	  struct mi_interp *mi = (struct mi_interp *) top_level_interpreter ();
-	  int report_change = 0;
-
-	  if (command->thread == -1)
-	    {
-	      report_change = (!ptid_equal (previous_ptid, null_ptid)
-			       && !ptid_equal (inferior_ptid, previous_ptid)
-			       && !ptid_equal (inferior_ptid, null_ptid));
-	    }
-	  else if (!ptid_equal (inferior_ptid, null_ptid))
-	    {
-	      struct thread_info *ti = inferior_thread ();
-
-	      report_change = (ti->global_num != command->thread);
-	    }
-
-	  if (report_change)
-	    {
-		observer_notify_user_selected_context_changed
-		  (USER_SELECTED_THREAD | USER_SELECTED_FRAME);
-	    }
-	}
-
       mi_parse_free (command);
 
       do_cleanups (cleanup);
@@ -2224,6 +2149,8 @@ mi_cmd_execute (struct mi_parse *parse)
 
   cleanup = prepare_execute_command ();
 
+  apply_global_user_selection ();
+
   if (parse->all && parse->thread_group != -1)
     error (_("Cannot specify --thread-group together with --all"));
 
diff --git a/gdb/observer.sh b/gdb/observer.sh
index 49db6b8cef..02a3e7be22 100755
--- a/gdb/observer.sh
+++ b/gdb/observer.sh
@@ -65,6 +65,7 @@ struct objfile;
 struct thread_info;
 struct inferior;
 struct trace_state_variable;
+class user_selection;
 EOF
         ;;
 esac
diff --git a/gdb/stack.c b/gdb/stack.c
index aa3a80e9a8..26fb40dbec 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -52,6 +52,9 @@
 #include "symfile.h"
 #include "extension.h"
 #include "observer.h"
+#include "user-selection.h"
+#include "common/scoped_restore.h"
+#include "cli-out.h"
 
 /* The possible choices of "set print frame-arguments", and the value
    of this setting.  */
@@ -1281,7 +1284,7 @@ print_frame (struct frame_info *frame, int print_level,
    this function never returns NULL).  When SELECTED_FRAME_P is non-NULL
    set its target to indicate that the default selected frame was used.  */
 
-static struct frame_info *
+struct frame_info *
 parse_frame_specification (const char *frame_exp, int *selected_frame_p)
 {
   int numargs;
@@ -2300,14 +2303,21 @@ find_relative_frame (struct frame_info *frame, int *level_offset_ptr)
    See parse_frame_specification for more info on proper frame
    expressions.  */
 
-void
+static void
 select_frame_command (char *level_exp, int from_tty)
 {
-  struct frame_info *prev_frame = get_selected_frame_if_set ();
+  cli_ui_out *uiout = dynamic_cast<cli_ui_out *> (current_uiout);
+  scoped_restore_suppress_output<cli_ui_out> restore (uiout);
+  user_selection *us = global_user_selection ();
+
+  uiout->suppress_output (true);
 
-  select_frame (parse_frame_specification (level_exp, NULL));
-  if (get_selected_frame_if_set () != prev_frame)
-    observer_notify_user_selected_context_changed (USER_SELECTED_FRAME);
+  if (level_exp != nullptr)
+    {
+      struct frame_info *frame = parse_frame_specification (level_exp, NULL);
+
+      us->select_frame (frame, true);
+    }
 }
 
 /* The "frame" command.  With no argument, print the selected frame
@@ -2317,20 +2327,30 @@ select_frame_command (char *level_exp, int from_tty)
 static void
 frame_command (char *level_exp, int from_tty)
 {
-  struct frame_info *prev_frame = get_selected_frame_if_set ();
+  user_selection *us = global_user_selection ();
 
-  select_frame (parse_frame_specification (level_exp, NULL));
-  if (get_selected_frame_if_set () != prev_frame)
-    observer_notify_user_selected_context_changed (USER_SELECTED_FRAME);
+  if (level_exp == nullptr)
+    {
+      if (us->thread () != nullptr
+	  && is_stopped (us->thread ()))
+	print_selected_thread_frame (current_uiout, us, USER_SELECTED_FRAME);
+      else
+	current_uiout->message (_("No stack.\n"));
+    }
   else
-    print_selected_thread_frame (current_uiout, USER_SELECTED_FRAME);
+    {
+      struct frame_info *frame = parse_frame_specification (level_exp, NULL);
+
+      if (!us->select_frame (frame, true))
+	print_selected_thread_frame (current_uiout, us, USER_SELECTED_FRAME);
+    }
 }
 
 /* Select the frame up one or COUNT_EXP stack levels from the
    previously selected frame, and print it briefly.  */
 
 static void
-up_silently_base (const char *count_exp)
+up_command (char *count_exp, int from_tty)
 {
   struct frame_info *frame;
   int count = 1;
@@ -2341,27 +2361,29 @@ up_silently_base (const char *count_exp)
   frame = find_relative_frame (get_selected_frame ("No stack."), &count);
   if (count != 0 && count_exp == NULL)
     error (_("Initial frame selected; you cannot go up."));
-  select_frame (frame);
+
+  user_selection *us = global_user_selection ();
+  us->select_frame (frame, true);
 }
 
 static void
 up_silently_command (char *count_exp, int from_tty)
 {
-  up_silently_base (count_exp);
-}
+  cli_ui_out *uiout = dynamic_cast<cli_ui_out *> (current_uiout);
 
-static void
-up_command (char *count_exp, int from_tty)
-{
-  up_silently_base (count_exp);
-  observer_notify_user_selected_context_changed (USER_SELECTED_FRAME);
+  gdb_assert (uiout != nullptr);
+
+  scoped_restore_suppress_output<cli_ui_out> restore (uiout);
+  uiout->suppress_output (true);
+
+  up_command (count_exp, from_tty);
 }
 
 /* Select the frame down one or COUNT_EXP stack levels from the previously
    selected frame, and print it briefly.  */
 
 static void
-down_silently_base (const char *count_exp)
+down_command (char *count_exp, int from_tty)
 {
   struct frame_info *frame;
   int count = -1;
@@ -2380,20 +2402,21 @@ down_silently_base (const char *count_exp)
       error (_("Bottom (innermost) frame selected; you cannot go down."));
     }
 
-  select_frame (frame);
+  user_selection *us = global_user_selection ();
+  us->select_frame (frame, true);
 }
 
 static void
 down_silently_command (char *count_exp, int from_tty)
 {
-  down_silently_base (count_exp);
-}
+  cli_ui_out *uiout = dynamic_cast<cli_ui_out *> (current_uiout);
 
-static void
-down_command (char *count_exp, int from_tty)
-{
-  down_silently_base (count_exp);
-  observer_notify_user_selected_context_changed (USER_SELECTED_FRAME);
+  gdb_assert (uiout != nullptr);
+
+  scoped_restore_suppress_output<cli_ui_out> restore (uiout);
+  uiout->suppress_output (true);
+
+  down_command (count_exp, from_tty);
 }
 
 void
@@ -2518,9 +2541,7 @@ return_command (char *retval_exp, int from_tty)
     frame_pop (get_current_frame ());
 
   select_frame (get_current_frame ());
-  /* If interactive, print the frame that is now current.  */
-  if (from_tty)
-    print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
+  global_user_selection ()->select_frame (get_selected_frame (NULL), true);
 }
 
 /* Sets the scope to input function name, provided that the function
diff --git a/gdb/stack.h b/gdb/stack.h
index 1583200cb2..75fa4d6692 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -20,11 +20,12 @@
 #ifndef STACK_H
 #define STACK_H
 
-void select_frame_command (char *level_exp, int from_tty);
-
 void find_frame_funname (struct frame_info *frame, char **funname,
 			 enum language *funlang, struct symbol **funcp);
 
+struct frame_info *parse_frame_specification (const char *frame_exp,
+					      int *selected_frame_p);
+
 typedef void (*iterate_over_block_arg_local_vars_cb) (const char *print_name,
 						      struct symbol *sym,
 						      void *cb_data);
diff --git a/gdb/testsuite/gdb.mi/mi-pthreads.exp b/gdb/testsuite/gdb.mi/mi-pthreads.exp
index 1b569d7e64..07e02ab446 100644
--- a/gdb/testsuite/gdb.mi/mi-pthreads.exp
+++ b/gdb/testsuite/gdb.mi/mi-pthreads.exp
@@ -39,7 +39,7 @@ proc check_mi_thread_command_set {} {
     "check_mi_thread_command_set: -thread-select"
 
   mi_gdb_test "-thread-select 123456789" \
-    {&.*\^error,msg="Thread ID 123456789 not known\."} \
+    {\^error,msg="Thread ID 123456789 not known\."} \
     "check_mi_thread_command_set: -thread-select 123456789"
 
   foreach thread $thread_list {
diff --git a/gdb/testsuite/gdb.mi/mi-return.exp b/gdb/testsuite/gdb.mi/mi-return.exp
index 91f4146107..6b099bf001 100644
--- a/gdb/testsuite/gdb.mi/mi-return.exp
+++ b/gdb/testsuite/gdb.mi/mi-return.exp
@@ -50,7 +50,7 @@ proc test_return_simple {} {
     set line_callee3_call         [expr $line_callee3_head + 2]
     set line_callee3_close_brace  [expr $line_callee3_head + 3]
 
-    mi_gdb_test "111-exec-return" "111\\^done,frame=\{level=\"0\",addr=\"$hex\",func=\"callee3\",args=\\\[.*\\\],file=\".*basics.c\",fullname=\"${fullname_syntax}${srcfile}\",line=\"($line_callee3_call|$line_callee3_close_brace)\"\}" "return from callee4 now"
+    mi_gdb_test "111-exec-return" ".*=thread-selected,.*111\\^done,frame=\{level=\"0\",addr=\"$hex\",func=\"callee3\",args=\\\[.*\\\],file=\".*basics.c\",fullname=\"${fullname_syntax}${srcfile}\",line=\"($line_callee3_call|$line_callee3_close_brace)\"\}" "return from callee4 now"
 }
 
 mi_runto callee4
diff --git a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 77734aec05..f7697f5b12 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -112,7 +112,7 @@ proc make_cli_re { mode inf thread frame } {
 	set thread_re $all_stop_thread_re
 
 	if [thread_is_running $mode $thread] {
-	    set thread_re "$thread_re\\\(running\\\)"
+	    set thread_re "$thread_re \\\(running\\\)"
 	}
 
 	append cli_re $thread_re
@@ -543,8 +543,10 @@ proc_with_prefix test_cli_inferior { mode } {
 	match_re_or_ensure_not_output $mi_re "event on MI"
     }
 
-    # Do the 'inferior' command on the currently selected inferior.  For now,
-    # GDB naively re-outputs everything.
+    # Do the 'inferior' command on the currently selected inferior.
+
+    set mi_re ""
+    
     with_spawn_id $gdb_main_spawn_id {
 	gdb_test "inferior 2" $cli_re "CLI select inferior again"
     }
@@ -927,8 +929,7 @@ proc_with_prefix test_mi_thread_select { mode } {
 	with_spawn_id $gdb_main_spawn_id {
 	    # This doesn't work as of now, no event is sent on CLI.  It is
 	    # commented out so we don't have to wait for the timeout every time.
-	    # match_re_or_ensure_not_output "$cli_re\r\n" "-thread-select, event on cli"
-	    kfail "gdb/20631" "thread-select, event on cli"
+	    match_re_or_ensure_not_output "$cli_re\r\n" "-thread-select, event on cli"
 	}
     }
 
@@ -1027,8 +1028,11 @@ proc_with_prefix test_cli_in_mi_inferior { mode cli_in_mi_mode } {
 	match_re_or_ensure_not_output "$cli_re\r\n" "select inferior, event on CLI"
     }
 
-    # Do the 'inferior' command on the currently selected inferior.  For now,
-    # GDB naively re-outputs everything.
+    # Do the 'inferior' command on the currently selected inferior.
+
+    set mi_re ""
+    set cli_re ""
+
     with_spawn_id $mi_spawn_id {
 	mi_gdb_test $command $mi_re "select inferior again"
     }
@@ -1232,7 +1236,7 @@ proc_with_prefix test_cli_in_mi_frame { mode cli_in_mi_mode } {
 	if { $mode == "all-stop" } {
 	    set mi_re [make_cli_in_mi_re $command $cli_in_mi_mode $mode 0 -1 -1 -1 1]
 	} else {
-	    set mi_re "\\^error,msg=\"No stack\\.\""
+	    set mi_re "\\^done"
 	}
 	set cli_re ""
 
diff --git a/gdb/thread.c b/gdb/thread.c
index 99fe424717..b40d8cd36a 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -44,6 +44,7 @@
 #include "cli/cli-utils.h"
 #include "thread-fsm.h"
 #include "tid-parse.h"
+#include "user-selection.h"
 
 /* Definition of struct thread_info exported to gdbthread.h.  */
 
@@ -283,6 +284,8 @@ add_thread_silent (ptid_t ptid)
 	 new template thread in the list with an invalid ptid, switch
 	 to it, delete the original thread, reset the new thread's
 	 ptid, and switch to it.  */
+      if (tp == global_user_selection ()->thread ())
+	global_user_selection ()->select_thread (NULL, false);
 
       if (ptid_equal (inferior_ptid, ptid))
 	{
@@ -439,7 +442,7 @@ delete_thread_1 (ptid_t ptid, int silent)
   /* If this is the current thread, or there's code out there that
      relies on it existing (refcount > 0) we can't delete yet.  Mark
      it as exited, and notify it.  */
-  if (tp->refcount > 0
+  if (tp->refcount_ > 0
       || ptid_equal (tp->ptid, inferior_ptid))
     {
       if (tp->state != THREAD_EXITED)
@@ -541,7 +544,7 @@ find_thread_ptid (ptid_t ptid)
  */
 
 struct thread_info *
-iterate_over_threads (int (*callback) (struct thread_info *, void *),
+iterate_over_threads (thread_callback_func callback,
 		      void *data)
 {
   struct thread_info *tp, *next;
@@ -549,7 +552,7 @@ iterate_over_threads (int (*callback) (struct thread_info *, void *),
   for (tp = thread_list; tp; tp = next)
     {
       next = tp->next;
-      if ((*callback) (tp, data))
+      if (callback (tp, data))
 	return tp;
     }
 
@@ -989,12 +992,24 @@ is_stopped (ptid_t ptid)
   return is_thread_state (ptid, THREAD_STOPPED);
 }
 
+bool
+is_stopped (struct thread_info *thread)
+{
+  return thread->state == THREAD_STOPPED;
+}
+
 int
 is_exited (ptid_t ptid)
 {
   return is_thread_state (ptid, THREAD_EXITED);
 }
 
+bool
+is_exited (struct thread_info *thread)
+{
+  return thread->state == THREAD_EXITED;
+}
+
 int
 is_running (ptid_t ptid)
 {
@@ -1218,14 +1233,14 @@ print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
 		     int show_global_ids)
 {
   struct thread_info *tp;
-  ptid_t current_ptid;
+  const struct thread_info *current_thread
+    = global_user_selection ()->thread ();
   struct cleanup *old_chain;
   const char *extra_info, *name, *target_id;
   struct inferior *inf;
   int default_inf_num = current_inferior ()->num;
 
   update_thread_list ();
-  current_ptid = inferior_ptid;
 
   /* We'll be switching threads temporarily.  */
   old_chain = make_cleanup_restore_current_thread ();
@@ -1289,14 +1304,14 @@ print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
       if (uiout->is_mi_like_p ())
 	{
 	  /* Compatibility.  */
-	  if (ptid_equal (tp->ptid, current_ptid))
+	  if (tp == current_thread)
 	    uiout->text ("* ");
 	  else
 	    uiout->text ("  ");
 	}
       else
 	{
-	  if (ptid_equal (tp->ptid, current_ptid))
+	  if (tp == current_thread)
 	    uiout->field_string ("current", "*");
 	  else
 	    uiout->field_skip ("current");
@@ -1632,7 +1647,8 @@ restore_current_thread_cleanup_dtor (void *arg)
 
   tp = find_thread_ptid (old->inferior_ptid);
   if (tp)
-    tp->refcount--;
+    tp->put ();
+
   inf = find_inferior_id (old->inf_id);
   if (inf != NULL)
     inf->removable = old->was_removable;
@@ -1649,7 +1665,7 @@ set_thread_refcount (void *data)
     = (struct thread_array_cleanup *) data;
 
   for (k = 0; k != ta_cleanup->count; k++)
-    ta_cleanup->tp_array[k]->refcount--;
+    ta_cleanup->tp_array[k]->put ();
 }
 
 struct cleanup *
@@ -1689,7 +1705,7 @@ make_cleanup_restore_current_thread (void)
 
       tp = find_thread_ptid (inferior_ptid);
       if (tp)
-	tp->refcount++;
+	tp->get ();
     }
 
   current_inferior ()->removable = 0;
@@ -1806,7 +1822,7 @@ thread_apply_all_command (char *cmd, int from_tty)
       ALL_NON_EXITED_THREADS (tp)
         {
           tp_array[i] = tp;
-          tp->refcount++;
+          tp->get ();
           i++;
         }
       /* Because we skipped exited threads, we may end up with fewer
@@ -1940,50 +1956,34 @@ thread_apply_command (char *tidlist, int from_tty)
 void
 thread_command (char *tidstr, int from_tty)
 {
+  user_selection *us = global_user_selection ();
+
   if (tidstr == NULL)
     {
-      if (ptid_equal (inferior_ptid, null_ptid))
+      struct thread_info *thread = us->thread ();
+
+      if (thread == nullptr)
 	error (_("No thread selected"));
 
       if (target_has_stack)
 	{
-	  struct thread_info *tp = inferior_thread ();
-
-	  if (is_exited (inferior_ptid))
+	  if (is_exited (thread))
 	    printf_filtered (_("[Current thread is %s (%s) (exited)]\n"),
-			     print_thread_id (tp),
-			     target_pid_to_str (inferior_ptid));
+			     print_thread_id (thread),
+			     target_pid_to_str (thread->ptid));
 	  else
 	    printf_filtered (_("[Current thread is %s (%s)]\n"),
-			     print_thread_id (tp),
-			     target_pid_to_str (inferior_ptid));
+			     print_thread_id (thread),
+			     target_pid_to_str (thread->ptid));
 	}
       else
 	error (_("No stack."));
     }
   else
     {
-      ptid_t previous_ptid = inferior_ptid;
-      enum gdb_rc result;
-
-      result = gdb_thread_select (current_uiout, tidstr, NULL);
-
-      /* If thread switch did not succeed don't notify or print.  */
-      if (result == GDB_RC_FAIL)
-	return;
-
-      /* Print if the thread has not changed, otherwise an event will be sent.  */
-      if (ptid_equal (inferior_ptid, previous_ptid))
-	{
-	  print_selected_thread_frame (current_uiout,
-				       USER_SELECTED_THREAD
-				       | USER_SELECTED_FRAME);
-	}
-      else
-	{
-	  observer_notify_user_selected_context_changed (USER_SELECTED_THREAD
-							 | USER_SELECTED_FRAME);
-	}
+      if (!thread_select (tidstr, true))
+	print_selected_thread_frame (current_uiout, us,
+				     USER_SELECTED_THREAD | USER_SELECTED_FRAME);
     }
 }
 
@@ -2069,48 +2069,56 @@ show_print_thread_events (struct ui_file *file, int from_tty,
                     value);
 }
 
-static int
-do_captured_thread_select (struct ui_out *uiout, void *tidstr_v)
+bool
+thread_select (const char *tidstr, bool tid_is_qualified)
 {
-  const char *tidstr = (const char *) tidstr_v;
-  struct thread_info *tp;
+  struct thread_info *thread;
 
-  if (uiout->is_mi_like_p ())
+  if (tid_is_qualified)
     {
-      int num = value_as_long (parse_and_eval (tidstr));
+      thread = parse_thread_id (tidstr, NULL);
 
-      tp = find_thread_global_id (num);
-      if (tp == NULL)
-	error (_("Thread ID %d not known."), num);
+      /* parse_thread_id is not supposed to return NULL.  */
+      gdb_assert (thread != NULL);
     }
   else
     {
-      tp = parse_thread_id (tidstr, NULL);
-      gdb_assert (tp != NULL);
+      int num = value_as_long (parse_and_eval (tidstr));
+
+      thread = find_thread_global_id (num);
+      if (thread == NULL)
+	error (_("Thread ID %d not known."), num);
     }
 
-  if (!thread_alive (tp))
+  if (!thread_alive (thread))
     error (_("Thread ID %s has terminated."), tidstr);
 
-  switch_to_thread (tp->ptid);
-
   annotate_thread_changed ();
 
-  /* Since the current thread may have changed, see if there is any
-     exited thread we can now delete.  */
-  prune_threads ();
+  if (global_user_selection ()->select_thread (thread, true))
+    {
+      /* Since the current thread may have changed, see if there is any
+	 exited thread we can now delete.  */
+      prune_threads ();
 
-  return GDB_RC_OK;
+      return true;
+    }
+  else
+    return false;
 }
 
 /* Print thread and frame switch command response.  */
 
 void
 print_selected_thread_frame (struct ui_out *uiout,
+			     user_selection *us,
 			     user_selected_what selection)
 {
-  struct thread_info *tp = inferior_thread ();
-  struct inferior *inf = current_inferior ();
+  struct inferior *inf = us->inferior ();
+  struct thread_info *thread = us->thread ();
+
+  if (thread == nullptr)
+    return;
 
   if (selection & USER_SELECTED_THREAD)
     {
@@ -2121,39 +2129,28 @@ print_selected_thread_frame (struct ui_out *uiout,
 	}
       else
 	{
-	  uiout->text ("[Switching to thread ");
-	  uiout->field_string ("new-thread-id", print_thread_id (tp));
-	  uiout->text (" (");
-	  uiout->text (target_pid_to_str (inferior_ptid));
-	  uiout->text (")]");
+	  const char *thread_id = print_thread_id (thread);
+	  const char *pid_str = target_pid_to_str (thread->ptid);
+	  const char *running_str
+	    = thread->state == THREAD_RUNNING ? " (running)" : "";
+
+	  uiout->field_fmt (NULL, "[Switching to thread %s (%s)]%s\n",
+			    thread_id, pid_str, running_str);
 	}
     }
 
-  if (tp->state == THREAD_RUNNING)
+  if ((selection & USER_SELECTED_FRAME)
+      && thread->state == THREAD_STOPPED)
     {
-      if (selection & USER_SELECTED_THREAD)
-	uiout->text ("(running)\n");
-    }
-  else if (selection & USER_SELECTED_FRAME)
-    {
-      if (selection & USER_SELECTED_THREAD)
-	uiout->text ("\n");
+      struct frame_info *frame = us->frame ();
 
-      if (has_stack_frames ())
-	print_stack_frame_to_uiout (uiout, get_selected_frame (NULL),
-				    1, SRC_AND_LOC, 1);
+      if (frame != nullptr)
+	print_stack_frame_to_uiout (uiout, frame, 1, SRC_AND_LOC, 1);
+      else
+	uiout->message (_("No selected frame.\n"));
     }
 }
 
-enum gdb_rc
-gdb_thread_select (struct ui_out *uiout, char *tidstr, char **error_message)
-{
-  if (catch_exceptions_with_msg (uiout, do_captured_thread_select, tidstr,
-				 error_message, RETURN_MASK_ALL) < 0)
-    return GDB_RC_FAIL;
-  return GDB_RC_OK;
-}
-
 /* Update the 'threads_executing' global based on the threads we know
    about right now.  */
 
diff --git a/gdb/top.c b/gdb/top.c
index 6bf9d8c021..3a8b9616df 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -69,6 +69,7 @@
 #include "cli-out.h"
 #include "tracepoint.h"
 #include "inf-loop.h"
+#include "user-selection.h"
 
 #if defined(TUI)
 # include "tui/tui.h"
@@ -2179,6 +2180,7 @@ gdb_init (char *argv0)
      exec_bfd of the current program space.  */
   initialize_progspace ();
   initialize_inferiors ();
+  init_global_user_selection ();
   initialize_current_architecture ();
   init_cli_cmds();
   init_main ();			/* But that omits this file!  Do it now.  */
diff --git a/gdb/tracefile-tfile.c b/gdb/tracefile-tfile.c
index dbcd65d5df..91f97cfa9f 100644
--- a/gdb/tracefile-tfile.c
+++ b/gdb/tracefile-tfile.c
@@ -33,6 +33,7 @@
 #include "target-descriptions.h"
 #include "buffer.h"
 #include <algorithm>
+#include "user-selection.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -601,6 +602,7 @@ tfile_close (struct target_ops *self)
 
   pid = ptid_get_pid (inferior_ptid);
   inferior_ptid = null_ptid;	/* Avoid confusion from thread stuff.  */
+  global_user_selection ()->select_thread (NULL, false);
   exit_inferior_silent (pid);
 
   close (trace_fd);
diff --git a/gdb/tui/tui-interp.c b/gdb/tui/tui-interp.c
index 702c34269c..66423efee4 100644
--- a/gdb/tui/tui-interp.c
+++ b/gdb/tui/tui-interp.c
@@ -33,6 +33,7 @@
 #include "infrun.h"
 #include "observer.h"
 #include "gdbthread.h"
+#include "user-selection.h"
 
 /* Set to 1 when the TUI mode must be activated when we first start
    gdb.  */
@@ -209,15 +210,15 @@ tui_on_command_error (void)
 /* Observer for the user_selected_context_changed notification.  */
 
 static void
-tui_on_user_selected_context_changed (user_selected_what selection)
+tui_on_global_user_selection_changed (user_selection *us,
+				      user_selected_what selection)
 {
-  struct thread_info *tp;
-
   /* This event is suppressed.  */
   if (cli_suppress_notification.user_selected_context)
     return;
 
-  tp = find_thread_ptid (inferior_ptid);
+  struct inferior *inf = us->inferior ();
+  struct thread_info *thread = us->thread ();
 
   SWITCH_THRU_ALL_UIS ()
     {
@@ -227,12 +228,11 @@ tui_on_user_selected_context_changed (user_selected_what selection)
 	continue;
 
       if (selection & USER_SELECTED_INFERIOR)
-	print_selected_inferior (tui->interp_ui_out ());
+	print_selected_inferior (tui->interp_ui_out (), inf);
 
-      if (tp != NULL
+      if (thread != NULL
 	  && ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME))))
-	print_selected_thread_frame (tui->interp_ui_out (), selection);
-
+	print_selected_thread_frame (tui->interp_ui_out (), us, selection);
     }
 }
 
@@ -337,6 +337,6 @@ _initialize_tui_interp (void)
   observer_attach_no_history (tui_on_no_history);
   observer_attach_sync_execution_done (tui_on_sync_execution_done);
   observer_attach_command_error (tui_on_command_error);
-  observer_attach_user_selected_context_changed
-    (tui_on_user_selected_context_changed);
+  observer_attach_global_user_selection_changed
+    (tui_on_global_user_selection_changed);
 }
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 9278cabdaa..17933d12f3 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -220,4 +220,24 @@ private:
 typedef ui_out_emit_type<ui_out_type_tuple> ui_out_emit_tuple;
 typedef ui_out_emit_type<ui_out_type_list> ui_out_emit_list;
 
+template <class T>
+class scoped_restore_suppress_output
+{
+public:
+  scoped_restore_suppress_output (T* obj)
+  : m_obj (obj),
+    m_val (m_obj->suppress_output ())
+  {}
+
+  ~scoped_restore_suppress_output ()
+  {
+    m_obj->suppress_output (m_val);
+  }
+
+private:
+
+  T* m_obj;
+  bool m_val;
+};
+
 #endif /* UI_OUT_H */
diff --git a/gdb/user-selection.c b/gdb/user-selection.c
new file mode 100644
index 0000000000..098f9154a8
--- /dev/null
+++ b/gdb/user-selection.c
@@ -0,0 +1,357 @@
+#include "defs.h"
+#include "user-selection.h"
+#include "inferior.h"
+#include "gdbthread.h"
+#include "observer.h"
+#include "gdbcmd.h"
+
+/* The user-visible selection.  */
+static user_selection main_user_selection;
+
+/* Knob for user-selection related debug traces.  */
+static int debug_user_selection = 0;
+
+/* See user-selection.h.  */
+
+user_selection *
+global_user_selection ()
+{
+  return &main_user_selection;
+}
+
+/* See user-selection.h.  */
+
+void
+init_global_user_selection ()
+{
+  /* Fetch the initial inferior, which should have been added by now.  The
+     initial inferior is selected on startup.  */
+  struct inferior *inf = find_inferior_id (1);
+
+  gdb_assert (inf != nullptr);
+
+  global_user_selection ()->select_inferior (inf, false);
+}
+
+/* See user-selection.h.  */
+
+bool
+user_selection::select_inferior (struct inferior *inf, bool notify)
+{
+  const char *debug_prefix = "user_selection::select_thread";
+
+  /* There is always a selected inferior.  */
+  gdb_assert (inf != nullptr);
+
+  if (debug_user_selection)
+    printf_unfiltered ("%s: num=%d\n", debug_prefix, inf->num);
+
+  /* No-op if this is already the currently selected inferior.  */
+  if (inf == m_inferior)
+    {
+      if (debug_user_selection)
+	printf_unfiltered ("%s: already selected inferior", debug_prefix);
+
+      return false;
+    }
+
+  /* When we change inferior, thread and frame will change as well.  */
+  user_selected_what what = USER_SELECTED_INFERIOR | USER_SELECTED_THREAD | USER_SELECTED_FRAME;
+
+  /* INF becomes selected.  */
+  m_inferior = inf;
+
+  /* Clear the thread and frame fields.  */
+  if (m_thread != nullptr)
+    {
+      m_thread->put ();
+      m_thread = nullptr;
+    }
+
+  m_frame_id = null_frame_id;
+  m_frame_level = INVALID_FRAME_LEVEL;
+
+  if (m_inferior->pid != 0)
+    {
+      /* This inferior is executing, so it should have threads.  Select the first
+         one.  */
+      m_thread = iterate_over_threads (
+	[inf] (struct thread_info *thread, void *) -> int
+	  {
+	    return inf->pid == ptid_get_pid (thread->ptid);
+	  }
+      );
+
+      /* We expect this inferior to have at least one thread.  If we didn't
+         find it, we have a problem.  */
+      gdb_assert (m_thread != nullptr);
+
+      /* Acquire a strong reference, so the thread_info object doesn't get freed
+         while it's selected.  */
+      m_thread->get ();
+    }
+
+  sanity_check ();
+
+  if (notify)
+    observer_notify_global_user_selection_changed (this, what);
+
+  return true;
+}
+
+/* See user-selection.h.  */
+
+bool
+user_selection::select_thread (struct thread_info *thread, bool notify)
+{
+  const char *debug_prefix = "user_selection::select_thread";
+
+  /* When changing thread, the frame will necessarily change as well.  */
+  user_selected_what what = USER_SELECTED_THREAD | USER_SELECTED_FRAME;
+
+  if (debug_user_selection)
+    printf_unfiltered ("%s: num=%d, ptid=%s",
+		       debug_prefix, thread->global_num,
+		       target_pid_to_str (thread->ptid));
+
+  /* No-op if this is already the currently selected thread.  */
+  if (thread == m_thread)
+    {
+      if (debug_user_selection)
+	printf_unfiltered ("%s: already selected thread", debug_prefix);
+
+      return false;
+    }
+
+  /* Clear the frame fields.  */
+  m_frame_id = null_frame_id;
+  m_frame_level = INVALID_FRAME_LEVEL;
+
+  /* Release the reference.  */
+  if (m_thread != nullptr)
+    m_thread->put ();
+
+  m_thread = thread;
+
+  if (m_thread != nullptr)
+    {
+      /* Acquire a strong reference, so the thread_info object doesn't get freed
+         while it's selected.  */
+      m_thread->get ();
+
+      /* The inferior of the thread becomes the newly selected inferior, if it's
+         not already.  */
+      if (m_inferior != thread->inf)
+	{
+	  m_inferior = thread->inf;
+
+	  what |= USER_SELECTED_INFERIOR;
+	}
+    }
+
+  sanity_check ();
+
+  if (notify)
+    observer_notify_global_user_selection_changed (this, what);
+
+  return true;
+}
+
+bool
+user_selection::select_frame (struct frame_info *frame, bool notify)
+{
+  const char *debug_prefix = "user_selection::select_frame";
+
+  /* No-op if this is already the selected frame.  */
+  if (frame_id_eq (m_frame_id, get_frame_id (frame))
+      && m_frame_level == frame_relative_level (frame))
+    return false;
+
+  m_frame_id = get_frame_id (frame);
+  m_frame_level = frame_relative_level (frame);
+
+  if (debug_user_selection)
+    {
+      string_file buf;
+
+      fprint_frame_id (&buf, m_frame_id);
+      printf_unfiltered ("%s: Selected frame #%d %s\n", debug_prefix,
+			 m_frame_level, buf.c_str ());
+    }
+
+  sanity_check ();
+
+  if (notify)
+    observer_notify_global_user_selection_changed (this, USER_SELECTED_FRAME);
+
+  return true;
+}
+
+/* Do some basic checks to verify that the selection is coherent.  */
+
+void
+user_selection::sanity_check () const
+{
+  /* We always have a current inferior.  */
+  gdb_assert (m_inferior != nullptr);
+
+  /* The selected thread must match the selected inferior.  */
+  if (m_thread != nullptr)
+    gdb_assert (m_thread->inf == m_inferior);
+
+  /* Can't have a current frame without a current thread.  */
+  if (m_frame_level != INVALID_FRAME_LEVEL)
+   gdb_assert (m_thread != nullptr);
+}
+
+/* Apply US to the core of gdb.  This makes the internally selected inferior,
+   thread and frame reflect the selection in US.  */
+
+static void
+apply_user_selection (user_selection *us)
+{
+  /* Select inferior.  */
+  set_current_inferior (us->inferior ());
+  set_current_program_space (us->inferior ()->pspace);
+
+  /* Select thread.  */
+  if (us->thread () != nullptr)
+    switch_to_thread (us->thread ()->ptid);
+  else
+    switch_to_thread (null_ptid);
+
+  /* Select frame.  */
+  if (us->has_valid_frame ())
+    {
+      struct frame_info *fi = us->frame ();
+
+      select_frame (fi);
+    }
+}
+
+/* Try to make the current (as in: where the program is currently stopped) frame
+   the selected one.  */
+
+void
+user_selection::try_select_current_frame ()
+{
+  /* This function should only be called when we don't have a selected frame
+     yet.  */
+  gdb_assert (!has_valid_frame ());
+
+  /* We need to select the relevant inferior/thread internally in order for
+     get_current_frame to work.  */
+  apply_user_selection (this);
+
+  TRY
+    {
+      struct frame_info *fi = get_current_frame ();
+
+      m_frame_id = get_frame_id (fi);
+      m_frame_level = frame_relative_level (fi);
+    }
+  CATCH (exception, RETURN_MASK_ALL)
+    {
+      /* We can't determine the current frame, too bad.  */
+    }
+  END_CATCH
+}
+
+/* See user-selection.h.  */
+
+void
+apply_global_user_selection ()
+{
+  apply_user_selection (global_user_selection ());
+}
+
+/* Callback for the new_thread observer.  */
+
+static void
+global_user_selection_on_new_thread (struct thread_info *tp)
+{
+  user_selection *us = global_user_selection ();
+
+  /* If a new thread is created while:
+
+       1. We don't have a currently selected thread,
+       2. The inferior of the new thread is the currently selected inferior,
+
+     then we silently make that new thread the selected one.  It covers the case
+     of automatically selecting the initial thread when starting an
+     inferior.  */
+
+  if (us->thread () == nullptr && tp->inf == us->inferior ())
+    us->select_thread (tp, false);
+}
+
+/* Callback for the on_exited observer.  */
+
+static void
+global_user_selection_on_exited (struct inferior *inferior)
+{
+  user_selection *us = global_user_selection ();
+
+  /* When an inferior exits and it's the current inferior, it means we have one
+     of its thread currently selected.  De-select it.  */
+
+  if (inferior == us->inferior ())
+    us->select_thread (NULL, false);
+}
+
+/* Callback for the on_target_resumed observer.  */
+
+static void
+global_user_selection_on_target_resumed (ptid_t ptid)
+{
+  user_selection *us = global_user_selection ();
+
+  /* If the selected thread has been resumed, our frame isn't valid anymore.  */
+  if (us->thread () != nullptr && ptid_match (us->thread ()->ptid, ptid))
+    us->select_frame (NULL, false);
+}
+
+/* Implementation of the "maintenance print user-selection" command.  */
+
+static void
+maint_print_user_selection (char *cmd, int from_tty)
+{
+  user_selection *us = global_user_selection ();
+
+  struct inferior *inf = us->inferior ();
+  struct thread_info *tp = us->thread ();
+  struct frame_id frame_id = us->raw_frame_id ();
+  int frame_level = us->raw_frame_level ();
+
+  /* Print inferior.  */
+  fprintf_filtered(gdb_stdout, "inferior %p (num=%d)\n", inf, inf->num);
+
+  /* Print thread.  */
+  if (tp != nullptr)
+    fprintf_filtered (gdb_stdout,
+		      "thread %p (gnum=%d, per-inf-num=%d, inf=%p)\n", tp,
+		      tp->global_num, tp->per_inf_num, tp->inf);
+  else
+    fprintf_filtered(gdb_stdout, "thread null\n");
+
+  /* Print frame.  */
+  fprint_frame_id (gdb_stdout, frame_id);
+  fprintf_filtered (gdb_stdout, ", level=%d\n", frame_level);
+}
+
+/* Initialize observer callbacks and commands.  */
+
+void
+_initialize_user_selection ()
+{
+  observer_attach_new_thread (global_user_selection_on_new_thread);
+  observer_attach_inferior_exit (global_user_selection_on_exited);
+  observer_attach_target_resumed (global_user_selection_on_target_resumed);
+
+  add_setshow_boolean_cmd ("user-selection", class_maintenance,
+			   &debug_user_selection, "blah", "blah", "blah", NULL,
+			   NULL, &setdebuglist, &showdebuglist);
+
+  add_cmd ("user-selection", class_maintenance, maint_print_user_selection,
+	   "foo", &maintenanceprintlist);
+}
diff --git a/gdb/user-selection.h b/gdb/user-selection.h
new file mode 100644
index 0000000000..a6f9af30c9
--- /dev/null
+++ b/gdb/user-selection.h
@@ -0,0 +1,100 @@
+#ifndef USER_SELECTION_H
+#define USER_SELECTION_H
+
+class user_selection {
+public:
+
+  /* Default constructor, nothing is selected.  */
+
+  user_selection ()
+  : m_inferior (nullptr),
+    m_thread (nullptr),
+    m_frame_id (null_frame_id),
+    m_frame_level (INVALID_FRAME_LEVEL)
+  {}
+
+  /* Make INF the selected inferior.  If NOTIFY is true, call the observer
+     indicating a selection change.
+
+     Return true if the newly selected inferior is different than the previously
+     selected inferior.  */
+
+  bool select_inferior (struct inferior *inf, bool notify);
+
+  /* Make THREAD the selected thread.  If NOTIFY is true, call the observer
+     indicating a selection change.
+
+     Return true if the newly selected thread is different than the previously
+     selected thread.  */
+
+  bool select_thread (struct thread_info *thread, bool notify);
+
+  /* Make FRAME the selected frame.  If NOTIFY is true, call the observer
+     indicating a selection change.
+
+     Return true if the newly selected frame is different than the previously
+     selected frame.  */
+
+  bool select_frame (struct frame_info *frame, bool notify);
+
+  /* Get the selected inferior.  */
+
+  struct inferior *inferior () const
+  { return m_inferior; }
+
+  /* Get the selected thread.  */
+
+  struct thread_info *thread () const
+  { return m_thread; }
+
+  /* Get the selected frame.  */
+
+  struct frame_info *
+  frame ()
+  {
+    if (!has_valid_frame ())
+      try_select_current_frame ();
+
+    if (!has_valid_frame ())
+      return NULL;
+
+    return frame_find_by_id (m_frame_id);
+  }
+
+  frame_id
+  raw_frame_id () const
+  { return m_frame_id; }
+
+  int
+  raw_frame_level () const
+  { return m_frame_level; }
+
+  bool has_valid_frame () const
+  { return m_frame_level != INVALID_FRAME_LEVEL; }
+
+private:
+
+  struct inferior *m_inferior;
+  struct thread_info *m_thread;
+
+  struct frame_id m_frame_id;
+  int m_frame_level;
+
+  void sanity_check () const;
+  void try_select_current_frame ();
+};
+
+/* Get the global user selection.  */
+
+user_selection *global_user_selection ();
+
+/* Initialize the global user selection.  This must be called after the initial
+   inferior has been created.  */
+
+void init_global_user_selection ();
+
+/* Apply the global user selection to core of gdb.  */
+
+void apply_global_user_selection ();
+
+#endif /* USER_SELECTION_H */
-- 
2.11.0


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]