
Due to the way that readline's API works (based on globals), we can only have one instance of readline in a process. So the goal of this patch is to only allow editing in the main UI, and make sure that only one UI calls into readline. Some MI paths touch readline variables currently, which is bad as that is changing variables that matter for the main console UI. This patch fixes those. This actually fixes a nasty bug -- starting gdb in MI mode ("gdb -i=mi"), and then doing "set editing on" crashes GDB, because MI is not prepared to use readline: set editing on &"set editing on\n" =cmd-param-changed,param="editing",value="on" ^done (gdb) p 1 readline: readline_callback_read_char() called with no handler! Aborted (core dumped) The fix for that was to add an interp_proc method to query the interpreter whether it actually supports editing. New test included. gdb/ChangeLog: 2016-06-21 Pedro Alves <palves@redhat.com> PR mi/20034 * cli/cli-interp.c: Include cli-interp.h and event-top.h. (cli_interpreter_resume): Pass 1 to gdb_setup_readline. Set the UI's input_handler here. (cli_interpreter_supports_command_editing): New function. (cli_interp_procs): Install it. * cli/cli-interp.h: New file. * event-top.c (async_command_editing_p): Rename to ... (set_editing_cmd_var): ... this. (change_line_handler): Add parameter 'editing', and use it. Bail early if the interpreter doesn't support editing. Don't touch readline state if editing is off. (gdb_rl_callback_handler_remove, gdb_rl_callback_handler_install) (gdb_rl_callback_handler_reinstall): Assert the current UI is the main UI. (display_gdb_prompt): Don't call gdb_rl_callback_handler_remove if not using readline. Check whether the current UI is using command editing instead of checking the async_command_editing_p global. (set_async_editing_command): Delete. (gdb_setup_readline): Add 'editing' parameter. Only allow editing on the main UI. Don't touch readline state if editing is off. (gdb_disable_readline): Don't touch readline state if editing is off. * event-top.h (gdb_setup_readline): Add 'int' parameter. (set_async_editing_command): Delete declaration. (change_line_handler, command_line_handler): Declare. (async_command_editing_p): Rename to ... (set_editing_cmd_var): ... this. * infrun.c (reinstall_readline_callback_handler_cleanup): Check whether the current UI has editing enabled rather than checking the async_command_editing_p global. * interps.c (interp_supports_command_editing): New function. * interps.h (interp_supports_command_editing_ftype): New typedef. (struct interp_procs) <supports_command_editing_proc>: New field. (interp_supports_command_editing): Declare. * mi/mi-interp.c (mi_interpreter_resume): Pass 0 to gdb_setup_readline. Don't clear the async_command_editing_p global. Update comments. * top.c (gdb_readline_wrapper_line, gdb_readline_wrapper): Check whether the current UI has editing enabled rather than checking the async_command_editing_p global. Don't touch readline state if editing is off. (undo_terminal_modifications_before_exit): Switch to the main UI. Unconditionally call gdb_disable_readline. (set_editing): New function. (show_async_command_editing_p): Rename to ... (show_editing): ... this. Show the state of the current UI. (_initialize_top): Adjust. * top.h (struct ui) <command_editing>: New field. * tui/tui-interp.c: Include cli/cli-interp.h. (tui_resume): Pass 1 to gdb_setup_readline. Set the UI's input_handler. (tui_interp_procs): Install cli_interpreter_supports_command_editing. * tui/tui-io.c (tui_getc): Check whether the current UI has editing enabled rather than checking the async_command_editing_p global. gdb/testsuite/ChangeLog: 2016-06-21 Pedro Alves <palves@redhat.com> PR mi/20034 * gdb.mi/mi-editing.exp: New file.
349 lines
8.5 KiB
C
349 lines
8.5 KiB
C
/* CLI Definitions for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2002-2016 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "defs.h"
|
|
#include "cli-interp.h"
|
|
#include "interps.h"
|
|
#include "event-top.h"
|
|
#include "ui-out.h"
|
|
#include "cli-out.h"
|
|
#include "top.h" /* for "execute_command" */
|
|
#include "event-top.h"
|
|
#include "infrun.h"
|
|
#include "observer.h"
|
|
|
|
/* The console interpreter. */
|
|
struct cli_interp
|
|
{
|
|
/* The ui_out for the console interpreter. */
|
|
struct ui_out *cli_uiout;
|
|
};
|
|
|
|
/* Returns the INTERP's data cast as cli_interp if INTERP is a CLI,
|
|
and returns NULL otherwise. */
|
|
|
|
static struct cli_interp *
|
|
as_cli_interp (struct interp *interp)
|
|
{
|
|
if (strcmp (interp_name (interp), INTERP_CONSOLE) == 0)
|
|
return (struct cli_interp *) interp_data (interp);
|
|
return NULL;
|
|
}
|
|
|
|
/* Longjmp-safe wrapper for "execute_command". */
|
|
static struct gdb_exception safe_execute_command (struct ui_out *uiout,
|
|
char *command,
|
|
int from_tty);
|
|
|
|
/* Observers for several run control events. If the interpreter is
|
|
quiet (i.e., another interpreter is being run with
|
|
interpreter-exec), print nothing. */
|
|
|
|
/* Observer for the normal_stop notification. */
|
|
|
|
static void
|
|
cli_on_normal_stop (struct bpstats *bs, int print_frame)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
if (print_frame)
|
|
print_stop_event (cli->cli_uiout);
|
|
}
|
|
}
|
|
|
|
/* Observer for the signal_received notification. */
|
|
|
|
static void
|
|
cli_on_signal_received (enum gdb_signal siggnal)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
print_signal_received_reason (cli->cli_uiout, siggnal);
|
|
}
|
|
}
|
|
|
|
/* Observer for the end_stepping_range notification. */
|
|
|
|
static void
|
|
cli_on_end_stepping_range (void)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
print_end_stepping_range_reason (cli->cli_uiout);
|
|
}
|
|
}
|
|
|
|
/* Observer for the signalled notification. */
|
|
|
|
static void
|
|
cli_on_signal_exited (enum gdb_signal siggnal)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
print_signal_exited_reason (cli->cli_uiout, siggnal);
|
|
}
|
|
}
|
|
|
|
/* Observer for the exited notification. */
|
|
|
|
static void
|
|
cli_on_exited (int exitstatus)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
print_exited_reason (cli->cli_uiout, exitstatus);
|
|
}
|
|
}
|
|
|
|
/* Observer for the no_history notification. */
|
|
|
|
static void
|
|
cli_on_no_history (void)
|
|
{
|
|
struct switch_thru_all_uis state;
|
|
|
|
SWITCH_THRU_ALL_UIS (state)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
continue;
|
|
|
|
print_no_history_reason (cli->cli_uiout);
|
|
}
|
|
}
|
|
|
|
/* Observer for the sync_execution_done notification. */
|
|
|
|
static void
|
|
cli_on_sync_execution_done (void)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
return;
|
|
|
|
display_gdb_prompt (NULL);
|
|
}
|
|
|
|
/* Observer for the command_error notification. */
|
|
|
|
static void
|
|
cli_on_command_error (void)
|
|
{
|
|
struct cli_interp *cli = as_cli_interp (top_level_interpreter ());
|
|
|
|
if (cli == NULL)
|
|
return;
|
|
|
|
display_gdb_prompt (NULL);
|
|
}
|
|
|
|
/* These implement the cli out interpreter: */
|
|
|
|
static void *
|
|
cli_interpreter_init (struct interp *self, int top_level)
|
|
{
|
|
return interp_data (self);
|
|
}
|
|
|
|
static int
|
|
cli_interpreter_resume (void *data)
|
|
{
|
|
struct ui *ui = current_ui;
|
|
struct cli_interp *cli = (struct cli_interp *) data;
|
|
struct ui_file *stream;
|
|
|
|
/*sync_execution = 1; */
|
|
|
|
/* gdb_setup_readline will change gdb_stdout. If the CLI was
|
|
previously writing to gdb_stdout, then set it to the new
|
|
gdb_stdout afterwards. */
|
|
|
|
stream = cli_out_set_stream (cli->cli_uiout, gdb_stdout);
|
|
if (stream != gdb_stdout)
|
|
{
|
|
cli_out_set_stream (cli->cli_uiout, stream);
|
|
stream = NULL;
|
|
}
|
|
|
|
gdb_setup_readline (1);
|
|
|
|
ui->input_handler = command_line_handler;
|
|
|
|
if (stream != NULL)
|
|
cli_out_set_stream (cli->cli_uiout, gdb_stdout);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
cli_interpreter_suspend (void *data)
|
|
{
|
|
gdb_disable_readline ();
|
|
return 1;
|
|
}
|
|
|
|
static struct gdb_exception
|
|
cli_interpreter_exec (void *data, const char *command_str)
|
|
{
|
|
struct cli_interp *cli = (struct cli_interp *) data;
|
|
struct ui_file *old_stream;
|
|
struct gdb_exception result;
|
|
|
|
/* FIXME: cagney/2003-02-01: Need to const char *propogate
|
|
safe_execute_command. */
|
|
char *str = (char *) alloca (strlen (command_str) + 1);
|
|
strcpy (str, command_str);
|
|
|
|
/* gdb_stdout could change between the time cli_uiout was
|
|
initialized and now. Since we're probably using a different
|
|
interpreter which has a new ui_file for gdb_stdout, use that one
|
|
instead of the default.
|
|
|
|
It is important that it gets reset everytime, since the user
|
|
could set gdb to use a different interpreter. */
|
|
old_stream = cli_out_set_stream (cli->cli_uiout, gdb_stdout);
|
|
result = safe_execute_command (cli->cli_uiout, str, 1);
|
|
cli_out_set_stream (cli->cli_uiout, old_stream);
|
|
return result;
|
|
}
|
|
|
|
int
|
|
cli_interpreter_supports_command_editing (struct interp *interp)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static struct gdb_exception
|
|
safe_execute_command (struct ui_out *command_uiout, char *command, int from_tty)
|
|
{
|
|
struct gdb_exception e = exception_none;
|
|
struct ui_out *saved_uiout;
|
|
|
|
/* Save and override the global ``struct ui_out'' builder. */
|
|
saved_uiout = current_uiout;
|
|
current_uiout = command_uiout;
|
|
|
|
TRY
|
|
{
|
|
execute_command (command, from_tty);
|
|
}
|
|
CATCH (exception, RETURN_MASK_ALL)
|
|
{
|
|
e = exception;
|
|
}
|
|
END_CATCH
|
|
|
|
/* Restore the global builder. */
|
|
current_uiout = saved_uiout;
|
|
|
|
/* FIXME: cagney/2005-01-13: This shouldn't be needed. Instead the
|
|
caller should print the exception. */
|
|
exception_print (gdb_stderr, e);
|
|
return e;
|
|
}
|
|
|
|
static struct ui_out *
|
|
cli_ui_out (struct interp *self)
|
|
{
|
|
struct cli_interp *cli = (struct cli_interp *) interp_data (self);
|
|
|
|
return cli->cli_uiout;
|
|
}
|
|
|
|
/* The CLI interpreter's vtable. */
|
|
|
|
static const struct interp_procs cli_interp_procs = {
|
|
cli_interpreter_init, /* init_proc */
|
|
cli_interpreter_resume, /* resume_proc */
|
|
cli_interpreter_suspend, /* suspend_proc */
|
|
cli_interpreter_exec, /* exec_proc */
|
|
cli_ui_out, /* ui_out_proc */
|
|
NULL, /* set_logging_proc */
|
|
cli_command_loop, /* command_loop_proc */
|
|
cli_interpreter_supports_command_editing, /* supports_command_editing_proc */
|
|
};
|
|
|
|
/* Factory for CLI interpreters. */
|
|
|
|
static struct interp *
|
|
cli_interp_factory (const char *name)
|
|
{
|
|
struct cli_interp *cli = XNEW (struct cli_interp);
|
|
|
|
/* Create a default uiout builder for the CLI. */
|
|
cli->cli_uiout = cli_out_new (gdb_stdout);
|
|
|
|
return interp_new (name, &cli_interp_procs, cli);
|
|
}
|
|
|
|
/* Standard gdb initialization hook. */
|
|
extern initialize_file_ftype _initialize_cli_interp; /* -Wmissing-prototypes */
|
|
|
|
void
|
|
_initialize_cli_interp (void)
|
|
{
|
|
interp_factory_register (INTERP_CONSOLE, cli_interp_factory);
|
|
|
|
/* If changing this, remember to update tui-interp.c as well. */
|
|
observer_attach_normal_stop (cli_on_normal_stop);
|
|
observer_attach_end_stepping_range (cli_on_end_stepping_range);
|
|
observer_attach_signal_received (cli_on_signal_received);
|
|
observer_attach_signal_exited (cli_on_signal_exited);
|
|
observer_attach_exited (cli_on_exited);
|
|
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);
|
|
}
|