From patchwork Wed Aug 31 13:51:48 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antoine Tremblay X-Patchwork-Id: 15118 Received: (qmail 75046 invoked by alias); 31 Aug 2016 13:52:09 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 75031 invoked by uid 89); 31 Aug 2016 13:52:08 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.9 required=5.0 tests=BAYES_00, SPF_PASS autolearn=ham version=3.3.2 spammy= X-HELO: usplmg20.ericsson.net Received: from usplmg20.ericsson.net (HELO usplmg20.ericsson.net) (198.24.6.45) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Wed, 31 Aug 2016 13:51:57 +0000 Received: from EUSAAHC003.ericsson.se (Unknown_Domain [147.117.188.81]) by (Symantec Mail Security) with SMTP id 54.5C.02488.962E6C75; Wed, 31 Aug 2016 15:58:01 +0200 (CEST) Received: from elxa4wqvvz1.dyn.mo.ca.am.ericsson.se (147.117.188.8) by smtps-am.internal.ericsson.com (147.117.188.81) with Microsoft SMTP Server (TLS) id 14.3.301.0; Wed, 31 Aug 2016 09:51:53 -0400 From: Antoine Tremblay To: CC: Antoine Tremblay Subject: [PATCH master+7.12] Send thread, frame and inferior selection events to all uis Date: Wed, 31 Aug 2016 09:51:48 -0400 Message-ID: <20160831135148.14381-1-antoine.tremblay@ericsson.com> MIME-Version: 1.0 X-IsSubscribed: yes With this patch when a thread, frame or inferior is explicitly selected by the user in the console or mi all uis will be notified. This patch fixes PR gdb/20487. Also, this patch adds a new field to the =thread-selected event for the frame, since inferior/thread/frame are most often a composition, thread and frame are sent in the same event. Front-ends need to handle this new field to properly sync the frame. Here's a detailed example for each command: - thread From console: thread 1.3 Will generate in the mi channel: =thread-selected,id="3",frame={...} From mi: -thread-select 3 Will generate in the console: [Switching to thread 1.3 ... From mi: thread 1.3 Will generate the following mi: &"thread 1.3\n" ~"#0 child_sub_function () ... =thread-selected,id="3",frame={level="0",...} ^done - frame From console: frame 1 Will generate in the mi channel: =thread-selected,id="3",frame={level="1",...} From mi: -stack-select-frame 1 Will generate in the console: #1 0x00000000004007f0 in child_function... From mi: frame 1 Will generate the following mi: &"frame 1\n" ~"#1 0x00000000004007f9 in ..." =thread-selected,id="3",frame={level="1"...} ^done - inferior For inferior selection however it only goes from the console to mi as there's no way to select the inferior in mi except with a cli command. - inferior From mi: inferior 2 Will generate the following in mi: &"inferior 2\n" ~"[Switching to inferior 2 ..." =thread-selected,id="4",frame={level="0"...} ^done From console: inferior 2 Will generate in the mi channel: =thread-selected,id="3" The test thread-selected-sync.exp also tests the select-frame cli command and corner cases of all selection commands. Note also that this patch makes it possible to suppress notifications caused by a cli command, like was done in mi-interp previously. This means that it's now possible to use the add_com_suppress_notification function to register a command with some event suppressed like is done with the select-frame command in this patch. No regressions, tested on ubuntu 14.04 x86. With gdb and native-extented-gdbserver gdb/ChangeLog: PR gdb/20487 * NEWS: Mention new frame field of =thread-selected event. * cli/cli-decode.c (add_cmd): Initialize supress_notification. (add_com_suppress_notification): New function. (cmd_func): Set the suppress_notification flag and reset it after cmd->func call. * cli/cli-decode.h (struct cmd_list_element): New field. * cli/cli-interp.c (cli_suppress_notification): Initialize cli_suppress_notification. (cli_on_user_selected_inf_thread_frame): New function. (_initialize_cli_interp): Add user_selected_inf_thread_frame observer. * command.h (struct cli_suppress_notification): New struct. (cli_suppress_notification): Struct declaration. (add_com_suppress_notification): New function. * defs.h (enum user_selected_what_flag): New enum. (user_selected_what): New enum flag type. * frame.h (print_stack_frame_to_uiout): New function. * gdbthread.h (print_selected_thread_frame): New function declaration. * inferior.c (print_selected_inferior): New function. (inferior_command): Remove printing the inferior this is now done by an event. (inferior_command): Notify user_selected_inf_thread_frame if the inferior changed. Let the event print to cli/mi. * inferior.h (print_selected_inferior): New function declaration. * mi/mi-cmds.c (struct mi_cmd): Add user_selected_thread_frame suppression for stack-select-frame, thread-select command. * mi/mi-interp.c (struct mi_suppress_notification): Init. (mi_memory_changed): mi_user_selected_inf_thread_frame): New * function. (_initialize_mi_interp): Add user_selected_inf_thread_frame observer. * mi/mi-main.c (mi_cmd_thread_select): Print thread selection response. (mi_execute_command): Don't report thread change on thread or inferior cli commands, notify if the thread changes. * mi/mi-main.h (struct mi_suppress_notification): New field. * stack.c: include "observer.h". (print_stack_frame_to_uiout): New function. (select_frame_command): Notify user_selected_inf_thread_frame. (frame_command): Use print_selected_thread_frame if there's no change or notify if there is one. (up_command): Notify user_selected_inf_thread_frame. (down_command): Likewise. (_initialize_stack): Suppress event in cli for command select-frame. * thread.c (thread_command): Print if thread has not changed. (do_captured_thread_select): Let mi_cmd_thread_select print. (print_selected_thread_frame): New function. * tui/tui-interp.c (tui_on_user_selected_inf_thread_frame): New function. (_initialize_tui_interp): Add user_selected_inf_thread_frame observer. gdb/doc/ChangeLog: PR gdb/20487 * observer.texi (user_selected_inf_thread_frame): New function. gdb/testsuite/ChangeLog: PR gdb/20487 * gdb.mi/thread-selected-sync.c: New file. * gdb.mi/thread-selected-sync.exp: Likewise. * gdb.mi/mi-pthreads.exp(check_mi_thread_command_set): Adapt =thread-select-event check. --- gdb/NEWS | 4 + gdb/cli/cli-decode.c | 31 +- gdb/cli/cli-decode.h | 6 + gdb/cli/cli-interp.c | 36 + gdb/command.h | 16 + gdb/defs.h | 15 + gdb/doc/observer.texi | 4 + gdb/frame.h | 8 + gdb/gdbthread.h | 4 + gdb/inferior.c | 40 +- gdb/inferior.h | 3 + gdb/mi/mi-cmds.c | 6 +- gdb/mi/mi-interp.c | 57 ++ gdb/mi/mi-main.c | 45 +- gdb/mi/mi-main.h | 2 + gdb/stack.c | 39 +- gdb/testsuite/gdb.mi/mi-pthreads.exp | 2 +- gdb/testsuite/gdb.mi/thread-selected-sync.c | 64 ++ gdb/testsuite/gdb.mi/thread-selected-sync.exp | 1021 +++++++++++++++++++++++++ gdb/thread.c | 80 +- gdb/tui/tui-interp.c | 31 + 21 files changed, 1451 insertions(+), 63 deletions(-) create mode 100644 gdb/testsuite/gdb.mi/thread-selected-sync.c create mode 100644 gdb/testsuite/gdb.mi/thread-selected-sync.exp diff --git a/gdb/NEWS b/gdb/NEWS index 6f5feb1..b427a7e 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -11,6 +11,10 @@ *** Changes in GDB 7.12 +* MI =thread-selected event now includes the frame field. For example: + + =thread-selected,id="3",frame={level="0",addr="0x00000000004007c0"} + * GDB and GDBserver now build with a C++ compiler by default. The --enable-build-with-cxx configure option is now enabled by diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index 0d2b137..bd52dd2 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -253,6 +253,7 @@ add_cmd (const char *name, enum command_class theclass, cmd_cfunc_ftype *fun, c->user_commands = NULL; c->cmd_pointer = NULL; c->alias_chain = NULL; + c->suppress_notification = NULL; return c; } @@ -883,6 +884,21 @@ add_com_alias (const char *name, const char *oldname, enum command_class theclas { return add_alias_cmd (name, oldname, theclass, abbrev_flag, &cmdlist); } + +/* Add an element with a suppress notification to the list of commands. */ + +struct cmd_list_element * +add_com_suppress_notification (const char *name, enum command_class theclass, + cmd_cfunc_ftype *fun, const char *doc, + int *suppress_notification) +{ + struct cmd_list_element *element; + + element = add_cmd (name, theclass, fun, doc, &cmdlist); + element->suppress_notification = suppress_notification; + + return element; +} /* Recursively walk the commandlist structures, and print out the documentation of commands that match our regex in either their @@ -1884,8 +1900,21 @@ cmd_func_p (struct cmd_list_element *cmd) void cmd_func (struct cmd_list_element *cmd, char *args, int from_tty) { + struct cleanup *cleanup = NULL; + if (cmd_func_p (cmd)) - (*cmd->func) (cmd, args, from_tty); + { + if (cmd->suppress_notification != NULL) + { + cleanup = make_cleanup_restore_integer (cmd->suppress_notification); + *cmd->suppress_notification = 1; + } + + (*cmd->func) (cmd, args, from_tty); + + if (cleanup != NULL) + do_cleanups(cleanup); + } else error (_("Invalid command")); } diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h index 4ea8063..4ef2e1b 100644 --- a/gdb/cli/cli-decode.h +++ b/gdb/cli/cli-decode.h @@ -218,6 +218,12 @@ struct cmd_list_element /* Link pointer for aliases on an alias list. */ struct cmd_list_element *alias_chain; + + /* If non-null, the pointer to a field in 'struct + cli_suppress_notification', which will be set to true in cmd_func + when this command is being executed. It will be set back to false + when the command has been executed. */ + int *suppress_notification; }; extern void help_cmd_list (struct cmd_list_element *, enum command_class, diff --git a/gdb/cli/cli-interp.c b/gdb/cli/cli-interp.c index 5d67ba4..8f1a034 100644 --- a/gdb/cli/cli-interp.c +++ b/gdb/cli/cli-interp.c @@ -37,6 +37,12 @@ struct cli_interp struct ui_out *cli_uiout; }; +/* Suppress notification struct. */ +struct cli_suppress_notification cli_suppress_notification = + { + 0 /* user_selected_inf_thread_frame */ + }; + /* Returns the INTERP's data cast as cli_interp if INTERP is a CLI, and returns NULL otherwise. */ @@ -229,6 +235,34 @@ cli_on_command_error (void) display_gdb_prompt (NULL); } +/* Observer for the user_selected_inf_thread_frame notification. */ + +static void +cli_on_user_selected_inf_thread_frame (user_selected_what selection) +{ + struct switch_thru_all_uis state; + struct thread_info *tp = find_thread_ptid (inferior_ptid); + + /* This event is suppressed. */ + if (cli_suppress_notification.user_selected_inf_thread_frame) + return; + + SWITCH_THRU_ALL_UIS (state) + { + struct cli_interp *cli = as_cli_interp (top_level_interpreter ()); + + if (cli == NULL) + continue; + + if (selection & USER_SELECTED_INFERIOR) + print_selected_inferior (cli->cli_uiout); + + if (tp != NULL + && ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME)))) + print_selected_thread_frame (cli->cli_uiout, selection); + } +} + /* pre_command_loop implementation. */ void @@ -393,4 +427,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_inf_thread_frame + (cli_on_user_selected_inf_thread_frame); } diff --git a/gdb/command.h b/gdb/command.h index ab62601..a36f05c 100644 --- a/gdb/command.h +++ b/gdb/command.h @@ -115,6 +115,17 @@ struct cmd_list_element; typedef void cmd_cfunc_ftype (char *args, int from_tty); +/* This structure specifies notifications to be suppressed by a cli + command interpreter. */ + +struct cli_suppress_notification +{ + /* Inferior, thread, frame selected notification suppressed? */ + int user_selected_inf_thread_frame; +}; + +extern struct cli_suppress_notification cli_suppress_notification; + /* Forward-declarations of the entry-points of cli/cli-decode.c. */ /* API to the manipulation of command lists. */ @@ -218,6 +229,11 @@ extern struct cmd_list_element *add_com (const char *, enum command_class, extern struct cmd_list_element *add_com_alias (const char *, const char *, enum command_class, int); +extern struct cmd_list_element *add_com_suppress_notification + (const char *name, enum command_class theclass, + cmd_cfunc_ftype *fun, const char *doc, + int *supress_notification); + extern struct cmd_list_element *add_info (const char *, cmd_cfunc_ftype *fun, const char *); diff --git a/gdb/defs.h b/gdb/defs.h index fee5f41..cb180a9 100644 --- a/gdb/defs.h +++ b/gdb/defs.h @@ -750,6 +750,21 @@ enum block_enum FIRST_LOCAL_BLOCK = 2 }; +/* User selection used in observer.h and multiple print functions. */ + +enum user_selected_what_flag + { + /* Inferior selected. */ + USER_SELECTED_INFERIOR = 1 << 1, + + /* Thread selected. */ + USER_SELECTED_THREAD = 1 << 2, + + /* Frame selected. */ + USER_SELECTED_FRAME = 1 << 3 + }; +DEF_ENUM_FLAGS_TYPE (enum user_selected_what_flag, user_selected_what); + #include "utils.h" #endif /* #ifndef DEFS_H */ diff --git a/gdb/doc/observer.texi b/gdb/doc/observer.texi index fc7aac4..4f8f0bb 100644 --- a/gdb/doc/observer.texi +++ b/gdb/doc/observer.texi @@ -307,3 +307,7 @@ This observer is used for internal testing. Do not use. See testsuite/gdb.gdb/observer.exp. @end deftypefun +@deftypefun void user_selected_inf_thread_frame (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 or frame has changed. +@end deftypefun diff --git a/gdb/frame.h b/gdb/frame.h index 5f21bb8..de13e7d 100644 --- a/gdb/frame.h +++ b/gdb/frame.h @@ -704,6 +704,14 @@ extern CORE_ADDR get_pc_function_start (CORE_ADDR); extern struct frame_info *find_relative_frame (struct frame_info *, int *); +/* Wrapper over print_stack_frame modifying current_uiout with UIOUT for + the function call. */ + +extern void print_stack_frame_to_uiout (struct ui_out *uiout, + struct frame_info *, int print_level, + enum print_what print_what, + int set_current_sal); + extern void print_stack_frame (struct frame_info *, int print_level, enum print_what print_what, int set_current_sal); diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index af2dc86..8f37fbb 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -630,6 +630,10 @@ extern void validate_registers_access (void); true iff we ever detected multiple threads. */ 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); + extern struct thread_info *thread_list; #endif /* GDBTHREAD_H */ diff --git a/gdb/inferior.c b/gdb/inferior.c index 47d91c7..60b3109 100644 --- a/gdb/inferior.c +++ b/gdb/inferior.c @@ -548,6 +548,24 @@ inferior_pid_to_str (int pid) return _(""); } +/* See inferior.h. */ + +void +print_selected_inferior (struct ui_out *uiout) +{ + char buf[PATH_MAX+256]; + struct inferior *inf = current_inferior (); + + xsnprintf (buf, sizeof (buf), + _("[Switching to inferior %d [%s] (%s)]\n"), + inf->num, + inferior_pid_to_str (inf->pid), + (inf->pspace->pspace_exec_filename != NULL + ? inf->pspace->pspace_exec_filename + : _(""))); + ui_out_text (uiout, buf); +} + /* Prints the list of inferiors and their details on UIOUT. This is a version of 'info_inferior_command' suitable for use from MI. @@ -726,13 +744,6 @@ inferior_command (char *args, int from_tty) if (inf == NULL) error (_("Inferior ID %d not known."), num); - printf_filtered (_("[Switching to inferior %d [%s] (%s)]\n"), - inf->num, - inferior_pid_to_str (inf->pid), - (inf->pspace->pspace_exec_filename != NULL - ? inf->pspace->pspace_exec_filename - : _(""))); - if (inf->pid != 0) { if (inf->pid != ptid_get_pid (inferior_ptid)) @@ -746,9 +757,10 @@ inferior_command (char *args, int from_tty) switch_to_thread (tp->ptid); } - printf_filtered (_("[Switching to thread %s (%s)] "), - print_thread_id (inferior_thread ()), - target_pid_to_str (inferior_ptid)); + observer_notify_user_selected_inf_thread_frame + (USER_SELECTED_INFERIOR + | USER_SELECTED_THREAD + | USER_SELECTED_FRAME); } else { @@ -758,14 +770,8 @@ inferior_command (char *args, int from_tty) set_current_inferior (inf); switch_to_thread (null_ptid); set_current_program_space (inf->pspace); - } - if (inf->pid != 0 && is_running (inferior_ptid)) - ui_out_text (current_uiout, "(running)\n"); - else if (inf->pid != 0) - { - ui_out_text (current_uiout, "\n"); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1); + observer_notify_user_selected_inf_thread_frame (USER_SELECTED_INFERIOR); } } diff --git a/gdb/inferior.h b/gdb/inferior.h index 571d26a..54c6f65 100644 --- a/gdb/inferior.h +++ b/gdb/inferior.h @@ -523,4 +523,7 @@ 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); + #endif /* !defined (INFERIOR_H) */ diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 4779832..967b921 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -137,7 +137,8 @@ static struct mi_cmd mi_cmds[] = DEF_MI_CMD_MI ("stack-list-frames", mi_cmd_stack_list_frames), DEF_MI_CMD_MI ("stack-list-locals", mi_cmd_stack_list_locals), DEF_MI_CMD_MI ("stack-list-variables", mi_cmd_stack_list_variables), - DEF_MI_CMD_MI ("stack-select-frame", mi_cmd_stack_select_frame), + DEF_MI_CMD_MI_1 ("stack-select-frame", mi_cmd_stack_select_frame, + &mi_suppress_notification.user_selected_inf_thread_frame), DEF_MI_CMD_MI ("symbol-list-lines", mi_cmd_symbol_list_lines), DEF_MI_CMD_CLI ("target-attach", "attach", 1), DEF_MI_CMD_MI ("target-detach", mi_cmd_target_detach), @@ -149,7 +150,8 @@ static struct mi_cmd mi_cmds[] = DEF_MI_CMD_CLI ("target-select", "target", 1), DEF_MI_CMD_MI ("thread-info", mi_cmd_thread_info), DEF_MI_CMD_MI ("thread-list-ids", mi_cmd_thread_list_ids), - DEF_MI_CMD_MI ("thread-select", mi_cmd_thread_select), + DEF_MI_CMD_MI_1 ("thread-select", mi_cmd_thread_select, + &mi_suppress_notification.user_selected_inf_thread_frame), DEF_MI_CMD_MI ("trace-define-variable", mi_cmd_trace_define_variable), DEF_MI_CMD_MI_1 ("trace-find", mi_cmd_trace_find, &mi_suppress_notification.traceframe), diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c index e3c7dbd..df67c9a 100644 --- a/gdb/mi/mi-interp.c +++ b/gdb/mi/mi-interp.c @@ -769,6 +769,7 @@ struct mi_suppress_notification mi_suppress_notification = 0, 0, 0, + 0, }; /* Emit notification on changing a traceframe. */ @@ -1334,6 +1335,60 @@ mi_memory_changed (struct inferior *inferior, CORE_ADDR memaddr, } } +/* Emit an event about the user selection of inf,thread or frame. */ + +static void +mi_user_selected_inf_thread_frame (user_selected_what selection) +{ + struct switch_thru_all_uis state; + struct thread_info *tp = find_thread_ptid (inferior_ptid); + + /* Don't send an event if we're responding to an MI command. */ + if (mi_suppress_notification.user_selected_inf_thread_frame) + return; + + SWITCH_THRU_ALL_UIS (state) + { + struct mi_interp *mi = as_mi_interp (top_level_interpreter ()); + struct ui_out *mi_uiout; + struct cleanup *old_chain; + + if (mi == NULL) + continue; + + mi_uiout = interp_ui_out (top_level_interpreter ()); + + ui_out_redirect (mi_uiout, mi->event_channel); + + old_chain = make_cleanup_restore_target_terminal (); + target_terminal_ours_for_output (); + + if (selection & USER_SELECTED_INFERIOR) + print_selected_inferior (mi->cli_uiout); + + if (tp != NULL + && ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME)))) + { + print_selected_thread_frame (mi->cli_uiout, selection); + + fprintf_unfiltered (mi->event_channel, + "thread-selected,id=\"%d\"", + tp->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); + } + } + + ui_out_redirect (mi_uiout, NULL); + gdb_flush (mi->event_channel); + do_cleanups (old_chain); + } +} + static int report_initial_inferior (struct inferior *inf, void *closure) { @@ -1466,4 +1521,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_inf_thread_frame + (mi_user_selected_inf_thread_frame); } diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 1913157..ebd5ee8 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -53,6 +53,7 @@ #include "linespec.h" #include "extension.h" #include "gdbcmd.h" +#include "observer.h" #include #include "gdb_sys_time.h" @@ -575,6 +576,9 @@ mi_cmd_thread_select (char *command, char **argv, int argc) make_cleanup (xfree, mi_error_message); error ("%s", mi_error_message); } + + print_selected_thread_frame (interp_ui_out (top_level_interpreter ()), + USER_SELECTED_THREAD | USER_SELECTED_FRAME); } void @@ -2102,6 +2106,7 @@ mi_execute_command (const char *cmd, int from_tty) { char *token; struct mi_parse *command = NULL; + struct cleanup *cleanup = NULL; /* This is to handle EOF (^D). We just quit gdb. */ /* FIXME: we should call some API function here. */ @@ -2161,10 +2166,15 @@ mi_execute_command (const char *cmd, int from_tty) /* Don't try report anything if there are no threads -- the program is dead. */ && thread_count () != 0 - /* -thread-select explicitly changes thread. If frontend uses that - internally, we don't want to emit =thread-selected, since - =thread-selected is supposed to indicate user's intentions. */ - && strcmp (command->command, "thread-select") != 0) + /* For the cli commands thread and inferior, the event is already sent + by the command, don't send it again. */ + && ((command->op == CLI_COMMAND + && strncmp (command->command, "thread", 6) != 0 + && strncmp (command->command, "inferior", 8) != 0) + || (command->op == MI_COMMAND && command->argc <= 1) + || (command->op == MI_COMMAND && command->argc > 1 + && strncmp (command->argv[1], "thread", 6) != 0 + && strncmp (command->argv[1], "inferior", 8) != 0))) { struct mi_interp *mi = (struct mi_interp *) top_level_interpreter_data (); @@ -2185,18 +2195,21 @@ mi_execute_command (const char *cmd, int from_tty) if (report_change) { - struct thread_info *ti = inferior_thread (); - struct cleanup *old_chain; - - old_chain = make_cleanup_restore_target_terminal (); - target_terminal_ours_for_output (); - - fprintf_unfiltered (mi->event_channel, - "thread-selected,id=\"%d\"", - ti->global_num); - gdb_flush (mi->event_channel); - - do_cleanups (old_chain); + /* Make sure we still keep event suppression. This is + handled in mi_cmd_execute so at this point this has been + reset. We still need it here however. */ + if (command->cmd->suppress_notification != NULL) + { + cleanup = make_cleanup_restore_integer + (command->cmd->suppress_notification); + *command->cmd->suppress_notification = 1; + } + + observer_notify_user_selected_inf_thread_frame + (USER_SELECTED_THREAD | USER_SELECTED_FRAME); + + if (cleanup != NULL) + do_cleanups (cleanup); } } diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h index 18000cf..f5b6bcf 100644 --- a/gdb/mi/mi-main.h +++ b/gdb/mi/mi-main.h @@ -49,6 +49,8 @@ struct mi_suppress_notification int traceframe; /* Memory changed notification suppressed? */ int memory; + /* Inferior, thread, frame selected notification suppressed? */ + int user_selected_inf_thread_frame; }; extern struct mi_suppress_notification mi_suppress_notification; diff --git a/gdb/stack.c b/gdb/stack.c index 417e887..80ce00b 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -51,6 +51,7 @@ #include "safe-ctype.h" #include "symfile.h" #include "extension.h" +#include "observer.h" /* The possible choices of "set print frame-arguments", and the value of this setting. */ @@ -141,6 +142,22 @@ frame_show_address (struct frame_info *frame, return get_frame_pc (frame) != sal.pc; } +/* See frame.h */ + +void +print_stack_frame_to_uiout (struct ui_out *uiout, struct frame_info *frame, + int print_level, enum print_what print_what, + int set_current_sal) +{ + struct ui_out *saved_uiout; + saved_uiout = current_uiout; + current_uiout = uiout; + + print_stack_frame (frame, print_level, print_what, set_current_sal); + + current_uiout = saved_uiout; +} + /* Show or print a stack frame FRAME briefly. The output is formatted according to PRINT_LEVEL and PRINT_WHAT printing the frame's relative level, function name, argument list, and file name and @@ -2302,7 +2319,11 @@ find_relative_frame (struct frame_info *frame, int *level_offset_ptr) void select_frame_command (char *level_exp, int from_tty) { + struct frame_info *prev_frame = get_selected_frame_if_set (); + select_frame (parse_frame_specification (level_exp, NULL)); + if (get_selected_frame_if_set () != prev_frame) + observer_notify_user_selected_inf_thread_frame (USER_SELECTED_FRAME); } /* The "frame" command. With no argument, print the selected frame @@ -2312,8 +2333,13 @@ select_frame_command (char *level_exp, int from_tty) static void frame_command (char *level_exp, int from_tty) { - select_frame_command (level_exp, from_tty); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1); + struct frame_info *prev_frame = get_selected_frame_if_set (); + + select_frame (parse_frame_specification (level_exp, NULL)); + if (get_selected_frame_if_set () != prev_frame) + observer_notify_user_selected_inf_thread_frame (USER_SELECTED_FRAME); + else + print_selected_thread_frame (current_uiout, USER_SELECTED_FRAME); } /* Select the frame up one or COUNT_EXP stack levels from the @@ -2344,7 +2370,7 @@ static void up_command (char *count_exp, int from_tty) { up_silently_base (count_exp); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1); + observer_notify_user_selected_inf_thread_frame (USER_SELECTED_FRAME); } /* Select the frame down one or COUNT_EXP stack levels from the previously @@ -2383,7 +2409,7 @@ static void down_command (char *count_exp, int from_tty) { down_silently_base (count_exp); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1); + observer_notify_user_selected_inf_thread_frame (USER_SELECTED_FRAME); } @@ -2616,10 +2642,11 @@ a command file or a user-defined command.")); add_com_alias ("f", "frame", class_stack, 1); - add_com ("select-frame", class_stack, select_frame_command, _("\ + add_com_suppress_notification ("select-frame", class_stack, select_frame_command, _("\ Select a stack frame without printing anything.\n\ An argument specifies the frame to select.\n\ -It can be a stack frame number or the address of the frame.\n")); +It can be a stack frame number or the address of the frame.\n"), + &cli_suppress_notification.user_selected_inf_thread_frame); add_com ("backtrace", class_stack, backtrace_command, _("\ Print backtrace of all stack frames, or innermost COUNT frames.\n\ diff --git a/gdb/testsuite/gdb.mi/mi-pthreads.exp b/gdb/testsuite/gdb.mi/mi-pthreads.exp index 88a600a..a49856e 100644 --- a/gdb/testsuite/gdb.mi/mi-pthreads.exp +++ b/gdb/testsuite/gdb.mi/mi-pthreads.exp @@ -53,7 +53,7 @@ proc check_mi_thread_command_set {} { foreach thread $thread_list { mi_gdb_test "-interpreter-exec console \"thread $thread\"" \ - ".*\\^done\r\n=thread-selected,id=\"$thread\"" \ + ".*=thread-selected,id=\"$thread\".*\[r\n\]+\\^done\[\r\n\]+" \ "check =thread-selected: thread $thread" } } diff --git a/gdb/testsuite/gdb.mi/thread-selected-sync.c b/gdb/testsuite/gdb.mi/thread-selected-sync.c new file mode 100644 index 0000000..4113d26 --- /dev/null +++ b/gdb/testsuite/gdb.mi/thread-selected-sync.c @@ -0,0 +1,64 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2016 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +/* Note that this test is not expected to exit cleanly. All threads will + block at the barrier and they won't be waken up. */ + +#include +#include + +#define NUM_THREADS 2 + +pthread_barrier_t barrier; + +void +child_sub_function () +{ + int test = 0; + test++; /* set break here */ + pthread_barrier_wait (&barrier); + pthread_exit (NULL); +} + +void * +child_function (void *args) +{ + child_sub_function (); /* caller */ +} + +pthread_t child_thread[NUM_THREADS]; + +int +main (void) +{ + int i = 0; + pthread_barrier_init (&barrier, NULL, NUM_THREADS + 1); + + for (i = 0; i < NUM_THREADS; i++) + { + pthread_create (&child_thread[i], NULL, child_function, NULL); + } + + for (i = 0; i < NUM_THREADS; i++) + { + pthread_join (child_thread[i], NULL); + } + + return 0; +} diff --git a/gdb/testsuite/gdb.mi/thread-selected-sync.exp b/gdb/testsuite/gdb.mi/thread-selected-sync.exp new file mode 100644 index 0000000..c36d018 --- /dev/null +++ b/gdb/testsuite/gdb.mi/thread-selected-sync.exp @@ -0,0 +1,1021 @@ +# Copyright 2016 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This test checks that thread, select-frame, frame or inferior selection +# events are properly sent to all uis. +# +# This test considers the case where console and mi are two different uis +# and mi is created with the new-ui command. +# +# It also considers the case were the console commands are sent directly in +# the mi channel as described in PR 20487. +# +# It does so by starting 2 inferiors with 3 threads each. +# Thread 1 is the main thread starting the others. +# Thread 2 is stopped in non-stop mode. +# Thread 3 is the thread used for testing, in non-stop mode this thread is running. + +load_lib mi-support.exp + +standard_testfile + +# Multiple inferiors are needed, therefore only native gdb and extended +# gdbserver modes are supported. +if [use_gdb_stub] { + untested ${testfile}.exp + return +} + +set compile_options "debug pthreads" +if {[build_executable $testfile.exp $testfile ${srcfile} ${compile_options}] == -1} { + untested "failed to compile $testfile" + return -1 +} + +set bp_lineno [gdb_get_line_number "set break here"] +set caller_lineno [gdb_get_line_number "caller"] + +# Make a regular expression for cli. + +proc make_cli_re {mode inf thread frame lineend} { + global srcfile bp_lineno caller_lineno + + set inf_re "\\\[Switching to inferior $inf.*\\\]" + set all_stop_thread_re "\\\[Switching to thread $thread.*\\\]" + set non_stop_thread_re "$all_stop_thread_re\\\(running\\\)" + set frame_re(0) "#0.*child_sub_function.*$srcfile:$bp_lineno\[\r\n\]+.*set break here \\\*/" + set frame_re(1) "#1.*child_function \\\(args=0x0\\\) at .*$srcfile:$caller_lineno\[\r\n\]+$caller_lineno.*caller \\\*/" + # Special frame for main threads. + set frame_re(2) "#0.*" + set end "\[\r\n\]" + + set cli_re "" + + if {$inf != -1} { + append cli_re $inf_re + } + + if {$thread != "-1"} { + if {$inf != -1} { + append cli_re "\[\r\n\]+" + } + set thread_re "" + if {$mode == "all-stop"} { + set thread_re $all_stop_thread_re + } elseif {$mode == "non-stop"} { + set thread_re $non_stop_thread_re + } + append cli_re $thread_re + } + + if {$mode == "all-stop" && $frame != -1} { + if {$thread != -1} { + append cli_re "\[\r\n\]+" + } + append cli_re $frame_re($frame) + } + + if {$lineend == 1} { + append cli_re $end + } + + return $cli_re +} + +# Make a regular expression for mi. + +proc make_mi_re {mode thread event frame lineend} { + global srcfile bp_lineno caller_lineno hex + + set mi_re "" + set thread_event_re "=thread-selected,id=\"$thread\"" + set thread_answer_re ".*\\^done,new-thread-id=\"$thread\"" + set frame_re(0) ",frame=\{level=\"0\",addr=\"$hex\",func=\"child_sub_function\",args=\\\[\\\],file=\".*$srcfile\",.*line=\"$bp_lineno\"\}" + set frame_re(1) ",frame=\{level=\"1\",addr=\"$hex\",func=\"child_function\",args=\\\[\{name=\"args\",value=\"0x0\"\}\\\],file=\".*$srcfile\",.*line=\"$caller_lineno\"\}" + #Special frame for main thread. + set frame_re(2) ",frame=\{level=\"0\",addr=\"$hex\",func=\".*\",args=\\\[\\\],from=\".*\"\}" + set end "\[\r\n\]" + + if {$thread != "-1"} { + if {$event == 1} { + append mi_re $thread_event_re + } elseif {$event == 0} { + append mi_re $thread_answer_re + } + } + + if {$mode == "all-stop" && $frame != -1} { + append mi_re $frame_re($frame) + } + + if {$lineend == 1} { + append mi_re $end + } + + return $mi_re +} + +# Make a regular expression for cli in mi. + +proc make_cli_in_mi_re {command mode inf cli_thread mi_thread event frame lineend} { + global srcfile bp_lineno caller_lineno + + set cli_in_mi_re ".*$command.*\[\r\n\]+" + set frame_re(0) "~\"#0.*child_sub_function.*$srcfile:$bp_lineno\\\\n.*set break here \\\*/\\\\n\"\[\r\n\]+" + set frame_re(1) "~\"#1.*child_function \\\(args=0x0\\\) at .*$srcfile:$caller_lineno\\\\n\"\[\r\n\]+~\"$caller_lineno.*caller \\\*/\\\\n\"\[\r\n\]+" + set frame_re(2) "~\"#0.*\\\\n\"\[\r\n\]+" + + if {$inf != -1} { + append cli_in_mi_re "~\"" + append cli_in_mi_re [make_cli_re $mode $inf "-1" -1 0] + append cli_in_mi_re "\\\\n\"\[\r\n\]+" + } + + if {$cli_thread != "-1"} { + append cli_in_mi_re "~\"" + append cli_in_mi_re [make_cli_re $mode -1 $cli_thread -1 0] + append cli_in_mi_re "\\\\n\"\[\r\n\]+" + } + + if {$mode == "all-stop"} { + append cli_in_mi_re $frame_re($frame) + } + + if {$event != -1} { + append cli_in_mi_re [make_mi_re $mode $mi_thread $event $frame $lineend] + } + append cli_in_mi_re "\[\r\n\]+\\^done" + + return $cli_in_mi_re +} + +# Continue to the breakpoint indicating the start position of the threads. + +proc test_continue_to_start {mode inf} { + global gdb_prompt mi_spawn_id + + if {$mode == "all-stop"} { + for {set i 1} { $i <= 2 } { incr i } { + gdb_continue_to_breakpoint "barrier breakpoint" + } + } else { + set test "continue&" + + gdb_test_multiple $test $test { + -re "Continuing\\.\[\r\n\]+$gdb_prompt " { + pass $test + } + } + + # Wait until we've hit the breakpoint for the 2 threads. + for {set i 1} { $i <= 2 } { incr i } { + set test "thread $i started" + gdb_test_multiple "" $test { + -re "hit Breakpoint" { + # The prompt was already matched in the "continue &" + # test above. We're now consuming asynchronous output + # that comes after the prompt. + pass $test + } + } + } + # Switch to the test thread. + if {$inf == 1} { + gdb_test "thread 3" [make_cli_re "all-stop" -1 "3" 0 -1] "Switch to test thread" + } else { + gdb_test "thread 2.3" [make_cli_re "all-stop" -1 "2\\.3" 0 -1] "Switch to test thread" + } + + gdb_test "continue&" "Continuing\\." + } +} + +# Test context setup. + +proc test_setup {mode} { + global srcfile srcdir subdir testfile + global gdb_main_spawn_id mi_spawn_id + global decimal binfile bp_lineno + global GDBFLAGS + + mi_gdb_exit + + set saved_gdbflags $GDBFLAGS + + if {$mode == "non-stop"} { + set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop 1\""] + } + + if {[mi_gdb_start "separate-mi-tty"] != 0} { + return + } + + mi_delete_breakpoints + mi_gdb_reinitialize_dir $srcdir/$subdir + mi_gdb_load $binfile + + if {[mi_runto main] < 0} { + fail "Can't run to main" + return + } + + with_spawn_id $gdb_main_spawn_id { + + gdb_test "break $srcfile:$bp_lineno" \ + "Breakpoint $decimal .*$srcfile, line $bp_lineno\\\." \ + "set breakpoint" + + test_continue_to_start $mode 1 + + # Add a second inferior to test inferior selection. + gdb_test "add-inferior" "Added inferior 2" "Add inferior 2" + gdb_test "inferior 2" [make_cli_re $mode 2 "-1" -1 -1] + gdb_load ${binfile} + gdb_test "start" "Temporary breakpoint.*Starting program.*" + test_continue_to_start $mode 2 + gdb_test "inferior 1" [make_cli_re $mode 1 "1\\.1" 2 -1] + } + + set GDBFLAGS $saved_gdbflags +} + +# Test selecting an inferior from cli. + +proc test_cli_select_inferior {mode} { + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re $mode "4" 1 2 1] + set cli_re [make_cli_re $mode 2 "2\\.1" 2 0] + + with_spawn_id $gdb_main_spawn_id { + gdb_test "inferior 2" $cli_re "cli select inferior" + } + + with_spawn_id $mi_spawn_id { + set test "mi select inferior" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test thread selection from cli. + +proc test_cli_select_thread {mode} { + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re $mode "3" 1 0 1] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 0] + + with_spawn_id $gdb_main_spawn_id { + gdb_test "thread 1.3" $cli_re "cli select thread" + } + + with_spawn_id $mi_spawn_id { + set test "mi select thread" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test selecting the same thread twice from the cli. + +proc test_cli_select_thread_twice {mode} { + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re $mode "3" 1 0 1] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 0] + + with_spawn_id $gdb_main_spawn_id { + gdb_test "thread 1.3" $cli_re "cli select thread twice, first call" + } + + with_spawn_id $mi_spawn_id { + set test "mi select thread twice, first call" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } + + # No event for that one. + set mi_re "" + set cli_re [make_cli_re $mode -1 "1\\.3" 0 0] + + with_spawn_id $gdb_main_spawn_id { + gdb_test "thread 1.3" $cli_re "cli select thread twice, second call" + } + + with_spawn_id $mi_spawn_id { + set test "mi select thread twice, second call" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } + +} + +# Test frame selection from cli. + +proc test_cli_select_frame {mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set mi_re [make_mi_re $mode "3" 1 $frame 1] + set cli_re [make_cli_re $mode -1 "-1" $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "Selected thread is running\\." + # No output + set mi_re "" + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "frame $frame" $cli_re "cli select frame" + } + + with_spawn_id $mi_spawn_id { + set test "mi select frame" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test frame selection from cli with the select-frame command. + +proc test_cli_select_select_frame {mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set cli_re "" + set mi_re [make_mi_re $mode "3" 1 $frame 1] + } elseif {$mode == "non-stop"} { + set cli_re "Selected thread is running\\." + # No output + set mi_re "" + } + + with_spawn_id $gdb_main_spawn_id { + if {$cli_re != ""} { + gdb_test "select-frame $frame" $cli_re "cli select frame with select-frame" + } else { + gdb_test_no_output "select-frame $frame" $cli_re "cli select frame with select-frame" + } + } + + with_spawn_id $mi_spawn_id { + set test "mi select frame with select-frame" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test doing and up and then down command from cli. + +proc test_cli_up_down {mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set cli_up_re [make_cli_re $mode -1 "-1" 1 0] + set mi_up_re [make_mi_re $mode "3" 1 1 1] + set cli_down_re [make_cli_re $mode -1 "-1" 0 0] + set mi_down_re [make_mi_re $mode "3" 1 0 1] + } elseif {$mode == "non-stop"} { + set cli_up_re "No stack\\." + set mi_up_re "" + set cli_down_re "No stack\\." + set mi_down_re "" + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "up" $cli_up_re "cli up" + } + + with_spawn_id $mi_spawn_id { + set test "mi up" + gdb_test_multiple "" $test { + -re $mi_up_re { + pass $test + } + } + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "down" $cli_down_re "cli down" + } + + with_spawn_id $mi_spawn_id { + set test "mi down" + gdb_test_multiple "" $test { + -re $mi_down_re { + pass $test + } + } + } +} + +# Test same frame selection from cli. + +proc test_cli_select_frame_twice {mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set mi_re [make_mi_re $mode "3" 1 $frame 1] + set cli_re [make_cli_re $mode -1 "-1" $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "Selected thread is running\\." + # No output + set mi_re "" + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "frame $frame" $cli_re "cli select frame twice, first call" + } + + with_spawn_id $mi_spawn_id { + set test "mi select frame twice, first call" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } + + if {$mode == "all-stop"} { + #No output thread has not changed. + set mi_re "" + set cli_re [make_cli_re $mode -1 "-1" $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "Selected thread is running\\." + # No output + set mi_re "" + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "frame $frame" $cli_re "cli select frame twice, second call" + } + + with_spawn_id $mi_spawn_id { + set test "mi select frame, second call" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test thread command without any arguments fromt the cli. + +proc test_cli_select_thread_no_args {mode} { + global gdb_main_spawn_id mi_spawn_id + + set cli_re "\\\[Current thread is 1\\.3.*\\\]" + set mi_re "" + + with_spawn_id $gdb_main_spawn_id { + gdb_test "thread" $cli_re "cli thread no args" + } + + with_spawn_id $mi_spawn_id { + set test "mi thread no args" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + +# Test frame command without any arguments from the cli. + +proc test_cli_select_frame_no_args {mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set mi_re "" + set cli_re [make_cli_re $mode -1 "-1" 0 0] + } elseif {$mode == "non-stop"} { + set cli_re "No stack\\." + # No output + set mi_re "" + } + + with_spawn_id $gdb_main_spawn_id { + gdb_test "frame" $cli_re "cli frame no args" + } + + with_spawn_id $mi_spawn_id { + set test "mi frame no args" + gdb_test_multiple "" $test { + -re $mi_re { + pass $test + } + } + } +} + + +# Test selecting a thread from mi with a thread group. This test verifies +# that even if the thread GDB would switch to is the same has the +# thread-group selected thread, that an event is still sent to cli. +# In this case this is thread 1.2 + +proc test_mi_select_thread_with_thread_group {mode} { + + # This only applies to non-stop mode. + if {$mode == "all-stop"} { + return; + } + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re "all-stop" "2" 0 0 0] + set cli_re [make_cli_re "all-stop" -1 "1\\.2" 0 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-thread-select --thread-group i1 2" $mi_re "mi select thread with thread group" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread with thread group" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting a thread from mi. + +proc test_mi_select_thread {mode} { + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re $mode "3" 0 0 0] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-thread-select 3" $mi_re "mi select thread" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting the same thread from mi. + +proc test_mi_select_thread_twice {mode} { + global gdb_main_spawn_id mi_spawn_id + + set mi_re [make_mi_re $mode "3" 0 0 0] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-thread-select 3" $mi_re "mi select thread twice, first call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread twice, frist call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + + set mi_re [make_mi_re $mode "3" 0 0 0] + # No event here. + set cli_re "" + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-thread-select 3" $mi_re "mi select thread twice, second call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread twice, second call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting a frame from mi. + +proc test_mi_select_frame {mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set cli_re [make_cli_re $mode -1 "-1" $frame 1] + set mi_re ".*\\^done" + } elseif {$mode == "non-stop"} { + # No output + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-stack-select-frame $frame" $mi_re "mi select frame" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting the same frame from mi. + +proc test_mi_select_frame_twice {mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$mode == "all-stop"} { + set cli_re [make_cli_re $mode -1 "-1" $frame 1] + set mi_re ".*\\^done" + } elseif {$mode == "non-stop"} { + # No output + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-stack-select-frame $frame" $mi_re "mi select frame twice, frist call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame twice, frist call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + + if {$mode == "all-stop"} { + set cli_re "" + set mi_re ".*\\^done" + } elseif {$mode == "non-stop"} { + # No output + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test "-stack-select-frame $frame" $mi_re "mi select frame twice, second call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame twice, second call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + +} + +# Test selecting the inferior using a cli command in the mi channel. + +proc test_cli_in_mi_select_inferior {mode exec_mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"inferior 2\"" + } else { + set command "inferior 2" + } + + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode 2 "2\\.1" "4" 1 2 0] + set cli_re [make_cli_re $mode 2 "2\\.1" 2 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select inferior" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select inferior" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting the thread using a cli command in the mi channel. + +proc test_cli_in_mi_select_thread {mode exec_mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"thread 1.3\"" + } else { + set command "thread 1.3" + } + + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "1\\.3" "3" 1 0 0] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select thread" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting the frame using a cli command in the mi channel. + +proc test_cli_in_mi_select_frame {mode exec_mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"frame $frame\"" + } else { + set command "frame $frame" + } + + if {$mode == "all-stop"} { + set cli_re [make_cli_re $mode -1 "-1" $frame 1] + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "-1" 3 1 $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select frame" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test selecting the same thread using a cli command in the mi channel. + +proc test_cli_in_mi_select_thread_twice {mode exec_mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"thread 1.3\"" + } else { + set command "thread 1.3" + } + + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "1\\.3" "3" 1 0 0] + set cli_re [make_cli_re $mode -1 "1\\.3" 0 1] + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select thread twice, first call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread twice, first call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "1\\.3" "3" -1 0 0] + set cli_re "" + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select thread twice, second call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread twice, second call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + +} + +# Test selecting the same frame using a cli command in the mi channel. + +proc test_cli_in_mi_select_frame_twice {mode exec_mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"frame $frame\"" + } else { + set command "frame $frame" + } + + if {$mode == "all-stop"} { + set cli_re [make_cli_re $mode -1 "-1" $frame 1] + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "-1" 3 1 $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select frame twice, first call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame twice, first call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + + if {$mode == "all-stop"} { + set cli_re "" + set mi_re [make_cli_in_mi_re [string_to_regexp $command] $mode -1 "-1" 3 -1 $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "" + set mi_re ".*\\^error,msg=\"Selected thread is running\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select frame twice, second call" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame twice, second call" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } + +} + +# Test printing the current thread using a cli command in the mi channel . + +proc test_cli_in_mi_select_thread_no_args {mode exec_mode} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"thread\"" + } else { + set command "thread" + } + + set mi_re ".*$command.*\[\r\n\]+" + append mi_re "~\"\\\[Current thread is 1\\.3.*\\\]\\\\n\"" + append mi_re "\[\r\n\]+\\^done" + + set cli_re "" + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select thread no args" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select thread no args" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test printing the current frame using a cli command in the mi channel . + +proc test_cli_in_mi_select_frame_no_args {mode exec_mode frame} { + global gdb_main_spawn_id mi_spawn_id + + if {$exec_mode == "interpreter-exec"} { + set command "-interpreter-exec console \"frame\"" + } else { + set command "frame" + } + + if {$mode == "all-stop"} { + set cli_re "" + set mi_re [make_cli_in_mi_re "frame" $mode -1 "-1" -1 -1 $frame 0] + } elseif {$mode == "non-stop"} { + set cli_re "" + set mi_re ".*\\^error,msg=\"No stack\\.\"" + } + + with_spawn_id $mi_spawn_id { + mi_gdb_test $command $mi_re "mi select frame" + } + + with_spawn_id $gdb_main_spawn_id { + set test "cli select frame" + gdb_test_multiple "" $test { + -re $cli_re { + pass $test + } + } + } +} + +# Test all-stop and non-stop mode. + +foreach mode {"all-stop" "non-stop"} { + with_test_prefix $mode { + test_setup $mode + with_test_prefix "from cli" { + test_cli_select_inferior $mode + test_cli_select_thread $mode + test_cli_select_frame $mode 1 + # Reset the frame for up/down test. + test_cli_select_frame $mode 0 + test_cli_select_select_frame $mode 1 + test_cli_select_select_frame $mode 0 + test_cli_up_down $mode + # Reset thread/frame. + test_cli_select_inferior $mode + test_cli_select_thread_twice $mode + test_cli_select_frame_twice $mode 1 + # Reset frame to 0. + test_cli_select_frame $mode 0 + test_cli_select_frame_no_args $mode + test_cli_select_thread_no_args $mode + } + with_test_prefix "from mi" { + # Reset thread/frame. + test_cli_select_inferior $mode + test_mi_select_thread_with_thread_group $mode + test_mi_select_thread $mode + test_mi_select_frame $mode 1 + test_mi_select_frame $mode 0 + # Reset thread/frame. + test_cli_select_inferior $mode + test_mi_select_thread_twice $mode + test_mi_select_frame_twice $mode 1 + } + + # Test with a direct command from cli in mi like "thread 1" + # Or test with the interpreter-exec like -interpreter-exec "thread 1" + foreach exec_mode {"from cli in mi" "interpreter-exec"} { + with_test_prefix $exec_mode { + test_cli_in_mi_select_inferior $mode $exec_mode + test_cli_in_mi_select_thread $mode $exec_mode + test_cli_in_mi_select_frame $mode $exec_mode 1 + test_cli_in_mi_select_frame $mode $exec_mode 0 + # Reset thread/frame. + test_cli_in_mi_select_inferior $mode $exec_mode + test_cli_in_mi_select_thread_twice $mode $exec_mode + test_cli_in_mi_select_frame_twice $mode $exec_mode 1 + test_cli_in_mi_select_frame $mode $exec_mode 0 + test_cli_in_mi_select_thread_no_args $mode $exec_mode + test_cli_in_mi_select_frame_no_args $mode $exec_mode 0 + } + } + } +} diff --git a/gdb/thread.c b/gdb/thread.c index ab98777..f3c4879 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -1923,6 +1923,9 @@ thread_apply_command (char *tidlist, int from_tty) void thread_command (char *tidstr, int from_tty) { + ptid_t previous_ptid = inferior_ptid; + enum gdb_rc result; + if (!tidstr) { if (ptid_equal (inferior_ptid, null_ptid)) @@ -1946,7 +1949,23 @@ thread_command (char *tidstr, int from_tty) return; } - gdb_thread_select (current_uiout, tidstr, NULL); + 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_inf_thread_frame (USER_SELECTED_THREAD + | USER_SELECTED_FRAME); + } } /* Implementation of `thread name'. */ @@ -2058,32 +2077,53 @@ do_captured_thread_select (struct ui_out *uiout, void *tidstr_v) annotate_thread_changed (); - if (ui_out_is_mi_like_p (uiout)) - ui_out_field_int (uiout, "new-thread-id", inferior_thread ()->global_num); - else + /* Since the current thread may have changed, see if there is any + exited thread we can now delete. */ + prune_threads (); + + return GDB_RC_OK; +} + +/* Print thread and frame switch command response. */ + +void +print_selected_thread_frame (struct ui_out *uiout, + user_selected_what selection) +{ + struct thread_info *tp = inferior_thread (); + struct inferior *inf = current_inferior (); + + if (selection & USER_SELECTED_THREAD) { - ui_out_text (uiout, "[Switching to thread "); - ui_out_field_string (uiout, "new-thread-id", print_thread_id (tp)); - ui_out_text (uiout, " ("); - ui_out_text (uiout, target_pid_to_str (inferior_ptid)); - ui_out_text (uiout, ")]"); + if (ui_out_is_mi_like_p (uiout)) + { + ui_out_field_int (uiout, "new-thread-id", + inferior_thread ()->global_num); + } + else + { + ui_out_text (uiout, "[Switching to thread "); + ui_out_field_string (uiout, "new-thread-id", print_thread_id (tp)); + ui_out_text (uiout, " ("); + ui_out_text (uiout, target_pid_to_str (inferior_ptid)); + ui_out_text (uiout, ")]"); + } } - /* Note that we can't reach this with an exited thread, due to the - thread_alive check above. */ if (tp->state == THREAD_RUNNING) - ui_out_text (uiout, "(running)\n"); - else { - ui_out_text (uiout, "\n"); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1); + if (selection & USER_SELECTED_THREAD) + ui_out_text (uiout, "(running)\n"); } + else if (selection & USER_SELECTED_FRAME) + { + if (selection & USER_SELECTED_THREAD) + ui_out_text (uiout, "\n"); - /* Since the current thread may have changed, see if there is any - exited thread we can now delete. */ - prune_threads (); - - return GDB_RC_OK; + if (has_stack_frames ()) + print_stack_frame_to_uiout (uiout, get_selected_frame (NULL), + 1, SRC_AND_LOC, 1); + } } enum gdb_rc diff --git a/gdb/tui/tui-interp.c b/gdb/tui/tui-interp.c index 3856382..22a5dab 100644 --- a/gdb/tui/tui-interp.c +++ b/gdb/tui/tui-interp.c @@ -206,6 +206,35 @@ tui_on_command_error (void) display_gdb_prompt (NULL); } +/* Observer for the user_selected_thread_frame notification. */ + +static void +tui_on_user_selected_inf_thread_frame (user_selected_what selection) +{ + struct switch_thru_all_uis state; + struct thread_info *tp = find_thread_ptid (inferior_ptid); + + /* This event is suppressed. */ + if (cli_suppress_notification.user_selected_inf_thread_frame) + return; + + SWITCH_THRU_ALL_UIS (state) + { + struct interp *tui = as_tui_interp (top_level_interpreter ()); + + if (tui == NULL) + continue; + + if (selection & USER_SELECTED_INFERIOR) + print_selected_inferior (tui_ui_out (tui)); + + if (tp != NULL + && ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME)))) + print_selected_thread_frame (tui_ui_out (tui), selection); + + } +} + /* These implement the TUI interpreter. */ static void * @@ -323,4 +352,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_inf_thread_frame + (tui_on_user_selected_inf_thread_frame); }