]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Decouple user selection from internal selection users/simark/user-selection-rfc
authorSimon Marchi <simon.marchi@ericsson.com>
Thu, 8 Dec 2016 18:06:14 +0000 (13:06 -0500)
committerSimon Marchi <simon.marchi@ericsson.com>
Thu, 23 Feb 2017 22:25:30 +0000 (17:25 -0500)
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.

Comments are welcome!

Thanks,

Simon

[1] https://sourceware.org/ml/gdb-patches/2016-08/msg00031.html

30 files changed:
gdb/Makefile.in
gdb/cli-out.h
gdb/cli/cli-interp.c
gdb/doc/observer.texi
gdb/event-top.c
gdb/frame.c
gdb/frame.h
gdb/gdb.h
gdb/gdbthread.h
gdb/infcall.c
gdb/inferior.c
gdb/inferior.h
gdb/infrun.c
gdb/jit.c
gdb/mi/mi-cmd-stack.c
gdb/mi/mi-interp.c
gdb/mi/mi-main.c
gdb/observer.sh
gdb/stack.c
gdb/stack.h
gdb/testsuite/gdb.mi/mi-pthreads.exp
gdb/testsuite/gdb.mi/mi-return.exp
gdb/testsuite/gdb.mi/user-selected-context-sync.exp
gdb/thread.c
gdb/top.c
gdb/tracefile-tfile.c
gdb/tui/tui-interp.c
gdb/ui-out.h
gdb/user-selection.c [new file with mode: 0644]
gdb/user-selection.h [new file with mode: 0644]

index 268c2c6ead30d77ec3bf073d478a3a650522a206..85706a590941ec505c309acb7d8000b90fea4a3f 100644 (file)
@@ -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 \
index 1b6a1ade81211e054131194acdfebdc9865a19e4..5a775e9881f1f9cb3d30828d70fb229c58f04aeb 100644 (file)
@@ -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 ();
index 8712c75f3962b3d480e1230c841b353a4c56c11d..d460a68771bb9a548ae89b512ab0617f4936fd46 100644 (file)
@@ -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);
 }
index 606ddfe53687f82d7d8d7a4df8ac116f228c51ab..f786b1e2b7d30a172526325a4fa5f22a07041154 100644 (file)
@@ -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
index 5d8d077a9a44a1209d37ed26092d8f3617857241..9056b46c60ad69a6220be9698a4787e6c9b622e7 100644 (file)
@@ -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++)
     ;
index d98003dee7c34a63bd25356e6674721664a4b2f3..93f658534d1b3f465e348e52fc6c2452148e2d4f 100644 (file)
@@ -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");
index 1d0644f12de59b86130882db5ae02a01d18c103f..5c68febde67f81d3f6bb0e2b4e173d00170919d8 100644 (file)
@@ -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
index ac1e683314d0aae1fe39d2c0faacaea3a9253a6a..c224d419be6e9ec7010ff62bc89d0b979d784cee 100644 (file)
--- 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);
index 06ed78f56819be733eaf6bf8a9b929903574ab27..47cf1258a1282bcdd17b94c3e2776e53b6368e89 100644 (file)
@@ -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;
 
index f55acb5c27e27a1a625c1114ee86b499a34de0b9..fc3e4e7e75f3b43482f28aa116ad73a8bedb1c88 100644 (file)
@@ -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);
index c4ab6d7d7f6130d4a9b8c39fc8d5be337aac941c..d9d026a936c221a3b93f2a52bf621f945c9b7f90 100644 (file)
@@ -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;
index 7c0ddf37f1e0bdea33833916fd2718ce3eb3076b..b06866f49006e5f2b2129da82710665d10c96699 100644 (file)
@@ -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) */
index 1e5e9f14a64593a4bef574cbf79621ff599c83f4..a55acdf6dd40d7ac1b36a952b960ad8123f559d0 100644 (file)
@@ -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);
index 158d6d821545f19fbad05b9e6838980a6231dbb5..d58457da020c5e859ac3b23c96c5ca26f3147c2a 100644 (file)
--- 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);
 
index acb44a28ee51cd0ec79835f7a96989c0fcd75cd9..fadfdaa4352978e4ca173fe9fd9cd553eed8bba9 100644 (file)
@@ -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
index 86340e4a0815df8c2692e7e1e664feae42cfd88d..958f154130c2c74bd5a260161bf064eaa910d7da 100644 (file)
@@ -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);
 }
index cf4e45ad4137e91abcd6141bfb0d82a4ab17b7c3..63458d9113953085e5e63ad3f5c2b7b8e6ef9fd8 100644 (file)
@@ -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"));
 
index 49db6b8cef400c146a92019dd2c050eeb26eccea..02a3e7be22144ab0c902b684947ccf9e6b63e122 100755 (executable)
@@ -65,6 +65,7 @@ struct objfile;
 struct thread_info;
 struct inferior;
 struct trace_state_variable;
+class user_selection;
 EOF
         ;;
 esac
index aa3a80e9a82605cca205e2e44730589c41e22824..26fb40dbec7468e2ab69df5e9e0f8ec56322076e 100644 (file)
@@ -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
index 1583200cb2930339c9f1b65e3b7554cf74059161..75fa4d6692000ad5815746474958938e1194e1a2 100644 (file)
 #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);
index 1b569d7e64efdb34b1d087091d5cff52ab23232a..07e02ab446b149844ec083c419aa6787a7ff14d8 100644 (file)
@@ -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 {
index 91f414610747921b05e72946e5b5c667c0f44f6c..6b099bf001fd9b7f5047c95df5f2e73c00013a8f 100644 (file)
@@ -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
index 77734aec05c3641d48803d8cd7f1a7eea3af1fe7..f7697f5b126428194a7c9f6b85eb9fde64be44c8 100644 (file)
@@ -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 ""
 
index 99fe42471780c0b74224e19c82eb37f68a6f642a..b40d8cd36ae4c30337c6efcbad740991e9fb8cf6 100644 (file)
@@ -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.  */
 
index 6bf9d8c02165ee70a002cef37f8c61f4769b2107..3a8b9616dffa471b8b094e04871dc24c37929492 100644 (file)
--- 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.  */
index dbcd65d5dfac71424dfc690caf22d05e0eaf1522..91f97cfa9fd3f532322efdeee5f7659df8a4f934 100644 (file)
@@ -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);
index 702c34269c0c0f84664260d38015609be47f35ae..66423efee4e0af600c6da71c7dde4920bd8e7dbc 100644 (file)
@@ -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);
 }
index 9278cabdaab41e3f6a7d16132158569bddbf4ebe..17933d12f3c726ef34b0654e02f0b2b8191b88da 100644 (file)
@@ -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 (file)
index 0000000..098f915
--- /dev/null
@@ -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 (file)
index 0000000..a6f9af3
--- /dev/null
@@ -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 */