mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-15 04:31:49 +08:00
a350efd4fb
PR tui/25348 points out that, when "gdb -tui" is used, then exiting
the TUI will cause a crash.
This happens because tui_setup_io stashes some readline variables --
but because this happens before readline is initialized, some of these
are NULL. Then, when exiting the TUI, the NULL values are "restored",
causing a crash in readline.
This patch fixes the problem by ensuring that readline is initialized
first. Back in commit 11061048d
("Give a name to the TUI SingleKey
keymap"), a call to rl_initialize was removed from
tui_initialize_readline; this patch resurrects the call, but moves it
to the end of the function, so as not to remove the ability to modify
the SingleKey map from .inputrc.
gdb/ChangeLog
2020-06-16 Tom Tromey <tom@tromey.com>
PR tui/25348:
* tui/tui.c (tui_ensure_readline_initialized): Rename from
tui_initialize_readline. Only run once. Call rl_initialize.
* tui/tui.h (tui_ensure_readline_initialized): Rename from
tui_initialize_readline.
* tui/tui-io.c (tui_setup_io): Call
tui_ensure_readline_initialized.
* tui/tui-interp.c (tui_interp::init): Update.
582 lines
15 KiB
C
582 lines
15 KiB
C
/* General functions for the WDB TUI.
|
|
|
|
Copyright (C) 1998-2020 Free Software Foundation, Inc.
|
|
|
|
Contributed by Hewlett-Packard Company.
|
|
|
|
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 "gdbcmd.h"
|
|
#include "tui/tui.h"
|
|
#include "tui/tui-hooks.h"
|
|
#include "tui/tui-command.h"
|
|
#include "tui/tui-data.h"
|
|
#include "tui/tui-layout.h"
|
|
#include "tui/tui-io.h"
|
|
#include "tui/tui-regs.h"
|
|
#include "tui/tui-stack.h"
|
|
#include "tui/tui-win.h"
|
|
#include "tui/tui-wingeneral.h"
|
|
#include "tui/tui-winsource.h"
|
|
#include "tui/tui-source.h"
|
|
#include "target.h"
|
|
#include "frame.h"
|
|
#include "breakpoint.h"
|
|
#include "inferior.h"
|
|
#include "symtab.h"
|
|
#include "source.h"
|
|
#include "terminal.h"
|
|
#include "top.h"
|
|
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "gdb_curses.h"
|
|
#include "interps.h"
|
|
|
|
/* This redefines CTRL if it is not already defined, so it must come
|
|
after terminal state releated include files like <term.h> and
|
|
"gdb_curses.h". */
|
|
#include "readline/readline.h"
|
|
|
|
/* Tells whether the TUI is active or not. */
|
|
bool tui_active = false;
|
|
static bool tui_finish_init = true;
|
|
|
|
enum tui_key_mode tui_current_key_mode = TUI_COMMAND_MODE;
|
|
|
|
struct tui_char_command
|
|
{
|
|
unsigned char key;
|
|
const char *cmd;
|
|
};
|
|
|
|
/* Key mapping to gdb commands when the TUI is using the single key
|
|
mode. */
|
|
static const struct tui_char_command tui_commands[] = {
|
|
{ 'c', "continue" },
|
|
{ 'd', "down" },
|
|
{ 'f', "finish" },
|
|
{ 'n', "next" },
|
|
{ 'o', "nexti" },
|
|
{ 'r', "run" },
|
|
{ 's', "step" },
|
|
{ 'i', "stepi" },
|
|
{ 'u', "up" },
|
|
{ 'v', "info locals" },
|
|
{ 'w', "where" },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
static Keymap tui_keymap;
|
|
static Keymap tui_readline_standard_keymap;
|
|
|
|
/* TUI readline command.
|
|
Switch the output mode between TUI/standard gdb. */
|
|
static int
|
|
tui_rl_switch_mode (int notused1, int notused2)
|
|
{
|
|
|
|
/* Don't let exceptions escape. We're in the middle of a readline
|
|
callback that isn't prepared for that. */
|
|
try
|
|
{
|
|
if (tui_active)
|
|
{
|
|
tui_disable ();
|
|
rl_prep_terminal (0);
|
|
}
|
|
else
|
|
{
|
|
/* If tui_enable throws, we'll re-prep below. */
|
|
rl_deprep_terminal ();
|
|
tui_enable ();
|
|
}
|
|
}
|
|
catch (const gdb_exception &ex)
|
|
{
|
|
exception_print (gdb_stderr, ex);
|
|
|
|
if (!tui_active)
|
|
rl_prep_terminal (0);
|
|
}
|
|
|
|
/* Clear the readline in case switching occurred in middle of
|
|
something. */
|
|
if (rl_end)
|
|
rl_kill_text (0, rl_end);
|
|
|
|
/* Since we left the curses mode, the terminal mode is restored to
|
|
some previous state. That state may not be suitable for readline
|
|
to work correctly (it may be restored in line mode). We force an
|
|
exit of the current readline so that readline is re-entered and
|
|
it will be able to setup the terminal for its needs. By
|
|
re-entering in readline, we also redisplay its prompt in the
|
|
non-curses mode. */
|
|
rl_newline (1, '\n');
|
|
|
|
/* Make sure the \n we are returning does not repeat the last
|
|
command. */
|
|
dont_repeat ();
|
|
return 0;
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Change the TUI layout to show a next layout.
|
|
This function is bound to CTRL-X 2. It is intended to provide
|
|
a functionality close to the Emacs split-window command. */
|
|
static int
|
|
tui_rl_change_windows (int notused1, int notused2)
|
|
{
|
|
if (!tui_active)
|
|
tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
|
|
|
|
if (tui_active)
|
|
tui_next_layout ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Delete the second TUI window to only show one. */
|
|
static int
|
|
tui_rl_delete_other_windows (int notused1, int notused2)
|
|
{
|
|
if (!tui_active)
|
|
tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
|
|
|
|
if (tui_active)
|
|
tui_remove_some_windows ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Switch the active window to give the focus to a next window. */
|
|
static int
|
|
tui_rl_other_window (int count, int key)
|
|
{
|
|
struct tui_win_info *win_info;
|
|
|
|
if (!tui_active)
|
|
tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
|
|
|
|
win_info = tui_next_win (tui_win_with_focus ());
|
|
if (win_info)
|
|
{
|
|
tui_set_win_focus_to (win_info);
|
|
keypad (TUI_CMD_WIN->handle.get (), win_info != TUI_CMD_WIN);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Execute the gdb command bound to the specified key. */
|
|
static int
|
|
tui_rl_command_key (int count, int key)
|
|
{
|
|
int i;
|
|
|
|
reinitialize_more_filter ();
|
|
for (i = 0; tui_commands[i].cmd; i++)
|
|
{
|
|
if (tui_commands[i].key == key)
|
|
{
|
|
/* Insert the command in the readline buffer.
|
|
Avoid calling the gdb command here since it creates
|
|
a possible recursion on readline if prompt_for_continue
|
|
is called (See PR 9584). The command will also appear
|
|
in the readline history which turns out to be better. */
|
|
rl_insert_text (tui_commands[i].cmd);
|
|
rl_newline (1, '\n');
|
|
|
|
/* Switch to gdb command mode while executing the command.
|
|
This way the gdb's continue prompty will be displayed. */
|
|
tui_set_key_mode (TUI_ONE_COMMAND_MODE);
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Temporarily leave the TUI SingleKey mode to allow editing
|
|
a gdb command with the normal readline. Once the command
|
|
is executed, the TUI SingleKey mode is installed back. */
|
|
static int
|
|
tui_rl_command_mode (int count, int key)
|
|
{
|
|
tui_set_key_mode (TUI_ONE_COMMAND_MODE);
|
|
return rl_insert (count, key);
|
|
}
|
|
|
|
/* TUI readline command.
|
|
Switch between TUI SingleKey mode and gdb readline editing. */
|
|
static int
|
|
tui_rl_next_keymap (int notused1, int notused2)
|
|
{
|
|
if (!tui_active)
|
|
tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
|
|
|
|
tui_set_key_mode (tui_current_key_mode == TUI_COMMAND_MODE
|
|
? TUI_SINGLE_KEY_MODE : TUI_COMMAND_MODE);
|
|
return 0;
|
|
}
|
|
|
|
/* Readline hook to redisplay ourself the gdb prompt.
|
|
In the SingleKey mode, the prompt is not printed so that
|
|
the command window is cleaner. It will be displayed if
|
|
we temporarily leave the SingleKey mode. */
|
|
static int
|
|
tui_rl_startup_hook (void)
|
|
{
|
|
rl_already_prompted = 1;
|
|
if (tui_current_key_mode != TUI_COMMAND_MODE
|
|
&& !gdb_in_secondary_prompt_p (current_ui))
|
|
tui_set_key_mode (TUI_SINGLE_KEY_MODE);
|
|
tui_redisplay_readline ();
|
|
return 0;
|
|
}
|
|
|
|
/* Change the TUI key mode by installing the appropriate readline
|
|
keymap. */
|
|
void
|
|
tui_set_key_mode (enum tui_key_mode mode)
|
|
{
|
|
tui_current_key_mode = mode;
|
|
rl_set_keymap (mode == TUI_SINGLE_KEY_MODE
|
|
? tui_keymap : tui_readline_standard_keymap);
|
|
tui_show_locator_content ();
|
|
}
|
|
|
|
/* Initialize readline and configure the keymap for the switching
|
|
key shortcut. */
|
|
void
|
|
tui_ensure_readline_initialized ()
|
|
{
|
|
static bool initialized;
|
|
|
|
if (initialized)
|
|
return;
|
|
initialized = true;
|
|
|
|
int i;
|
|
Keymap tui_ctlx_keymap;
|
|
|
|
rl_add_defun ("tui-switch-mode", tui_rl_switch_mode, -1);
|
|
rl_add_defun ("next-keymap", tui_rl_next_keymap, -1);
|
|
rl_add_defun ("tui-delete-other-windows", tui_rl_delete_other_windows, -1);
|
|
rl_add_defun ("tui-change-windows", tui_rl_change_windows, -1);
|
|
rl_add_defun ("tui-other-window", tui_rl_other_window, -1);
|
|
|
|
tui_keymap = rl_make_bare_keymap ();
|
|
|
|
/* The named keymap feature was added in Readline 8.0. */
|
|
#if RL_READLINE_VERSION >= 0x800
|
|
rl_set_keymap_name ("SingleKey", tui_keymap);
|
|
#endif
|
|
|
|
tui_ctlx_keymap = rl_make_bare_keymap ();
|
|
tui_readline_standard_keymap = rl_get_keymap ();
|
|
|
|
for (i = 0; tui_commands[i].cmd; i++)
|
|
rl_bind_key_in_map (tui_commands[i].key, tui_rl_command_key, tui_keymap);
|
|
|
|
rl_generic_bind (ISKMAP, "\\C-x", (char*) tui_ctlx_keymap, tui_keymap);
|
|
|
|
/* Bind all other keys to tui_rl_command_mode so that we switch
|
|
temporarily from SingleKey mode and can enter a gdb command. */
|
|
for (i = ' '; i < 0x7f; i++)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; tui_commands[j].cmd; j++)
|
|
if (tui_commands[j].key == i)
|
|
break;
|
|
|
|
if (tui_commands[j].cmd)
|
|
continue;
|
|
|
|
rl_bind_key_in_map (i, tui_rl_command_mode, tui_keymap);
|
|
}
|
|
|
|
rl_bind_key_in_map ('a', tui_rl_switch_mode, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('a', tui_rl_switch_mode, tui_ctlx_keymap);
|
|
rl_bind_key_in_map ('A', tui_rl_switch_mode, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('A', tui_rl_switch_mode, tui_ctlx_keymap);
|
|
rl_bind_key_in_map (CTRL ('A'), tui_rl_switch_mode, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map (CTRL ('A'), tui_rl_switch_mode, tui_ctlx_keymap);
|
|
rl_bind_key_in_map ('1', tui_rl_delete_other_windows, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('1', tui_rl_delete_other_windows, tui_ctlx_keymap);
|
|
rl_bind_key_in_map ('2', tui_rl_change_windows, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('2', tui_rl_change_windows, tui_ctlx_keymap);
|
|
rl_bind_key_in_map ('o', tui_rl_other_window, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('o', tui_rl_other_window, tui_ctlx_keymap);
|
|
rl_bind_key_in_map ('q', tui_rl_next_keymap, tui_keymap);
|
|
rl_bind_key_in_map ('s', tui_rl_next_keymap, emacs_ctlx_keymap);
|
|
rl_bind_key_in_map ('s', tui_rl_next_keymap, tui_ctlx_keymap);
|
|
|
|
/* Initialize readline after the above. */
|
|
rl_initialize ();
|
|
}
|
|
|
|
/* Return the TERM variable from the environment, or "<unset>"
|
|
if not set. */
|
|
|
|
static const char *
|
|
gdb_getenv_term (void)
|
|
{
|
|
const char *term;
|
|
|
|
term = getenv ("TERM");
|
|
if (term != NULL)
|
|
return term;
|
|
return "<unset>";
|
|
}
|
|
|
|
/* Enter in the tui mode (curses).
|
|
When in normal mode, it installs the tui hooks in gdb, redirects
|
|
the gdb output, configures the readline to work in tui mode.
|
|
When in curses mode, it does nothing. */
|
|
void
|
|
tui_enable (void)
|
|
{
|
|
if (tui_active)
|
|
return;
|
|
|
|
/* To avoid to initialize curses when gdb starts, there is a deferred
|
|
curses initialization. This initialization is made only once
|
|
and the first time the curses mode is entered. */
|
|
if (tui_finish_init)
|
|
{
|
|
WINDOW *w;
|
|
SCREEN *s;
|
|
#ifndef __MINGW32__
|
|
const char *cap;
|
|
#endif
|
|
const char *interp;
|
|
|
|
/* If the top level interpreter is not the console/tui (e.g.,
|
|
MI), enabling curses will certainly lose. */
|
|
interp = top_level_interpreter ()->name ();
|
|
if (strcmp (interp, INTERP_TUI) != 0)
|
|
error (_("Cannot enable the TUI when the interpreter is '%s'"), interp);
|
|
|
|
/* Don't try to setup curses (and print funny control
|
|
characters) if we're not outputting to a terminal. */
|
|
if (!gdb_stderr->isatty ())
|
|
error (_("Cannot enable the TUI when output is not a terminal"));
|
|
|
|
s = newterm (NULL, stdout, stdin);
|
|
#ifdef __MINGW32__
|
|
/* The MinGW port of ncurses requires $TERM to be unset in order
|
|
to activate the Windows console driver. */
|
|
if (s == NULL)
|
|
s = newterm ((char *) "unknown", stdout, stdin);
|
|
#endif
|
|
if (s == NULL)
|
|
{
|
|
error (_("Cannot enable the TUI: error opening terminal [TERM=%s]"),
|
|
gdb_getenv_term ());
|
|
}
|
|
w = stdscr;
|
|
if (has_colors ())
|
|
{
|
|
#ifdef HAVE_USE_DEFAULT_COLORS
|
|
/* Ncurses extension to help with resetting to the default
|
|
color. */
|
|
use_default_colors ();
|
|
#endif
|
|
start_color ();
|
|
}
|
|
|
|
/* Check required terminal capabilities. The MinGW port of
|
|
ncurses does have them, but doesn't expose them through "cup". */
|
|
#ifndef __MINGW32__
|
|
cap = tigetstr ((char *) "cup");
|
|
if (cap == NULL || cap == (char *) -1 || *cap == '\0')
|
|
{
|
|
endwin ();
|
|
delscreen (s);
|
|
error (_("Cannot enable the TUI: "
|
|
"terminal doesn't support cursor addressing [TERM=%s]"),
|
|
gdb_getenv_term ());
|
|
}
|
|
#endif
|
|
|
|
cbreak ();
|
|
noecho ();
|
|
/* timeout (1); */
|
|
nodelay(w, FALSE);
|
|
nl();
|
|
keypad (w, TRUE);
|
|
tui_set_term_height_to (LINES);
|
|
tui_set_term_width_to (COLS);
|
|
def_prog_mode ();
|
|
|
|
tui_show_frame_info (0);
|
|
tui_set_initial_layout ();
|
|
tui_set_win_focus_to (TUI_SRC_WIN);
|
|
keypad (TUI_CMD_WIN->handle.get (), TRUE);
|
|
wrefresh (TUI_CMD_WIN->handle.get ());
|
|
tui_finish_init = false;
|
|
}
|
|
else
|
|
{
|
|
/* Save the current gdb setting of the terminal.
|
|
Curses will restore this state when endwin() is called. */
|
|
def_shell_mode ();
|
|
clearok (stdscr, TRUE);
|
|
}
|
|
|
|
if (tui_update_variables ())
|
|
tui_rehighlight_all ();
|
|
|
|
tui_setup_io (1);
|
|
|
|
tui_active = true;
|
|
|
|
/* Resize windows before anything might display/refresh a
|
|
window. */
|
|
if (tui_win_resized ())
|
|
{
|
|
tui_set_win_resized_to (false);
|
|
tui_resize_all ();
|
|
}
|
|
|
|
if (deprecated_safe_get_selected_frame ())
|
|
tui_show_frame_info (deprecated_safe_get_selected_frame ());
|
|
else
|
|
tui_display_main ();
|
|
|
|
/* Install the TUI specific hooks. This must be done after the call to
|
|
tui_display_main so that we don't detect the symtab changed event it
|
|
can cause. */
|
|
tui_install_hooks ();
|
|
rl_startup_hook = tui_rl_startup_hook;
|
|
|
|
/* Restore TUI keymap. */
|
|
tui_set_key_mode (tui_current_key_mode);
|
|
|
|
/* Refresh the screen. */
|
|
tui_refresh_all_win ();
|
|
|
|
/* Update gdb's knowledge of its terminal. */
|
|
gdb_save_tty_state ();
|
|
tui_update_gdb_sizes ();
|
|
}
|
|
|
|
/* Leave the tui mode.
|
|
Remove the tui hooks and configure the gdb output and readline
|
|
back to their original state. The curses mode is left so that
|
|
the terminal setting is restored to the point when we entered. */
|
|
void
|
|
tui_disable (void)
|
|
{
|
|
if (!tui_active)
|
|
return;
|
|
|
|
/* Restore initial readline keymap. */
|
|
rl_set_keymap (tui_readline_standard_keymap);
|
|
|
|
/* Remove TUI hooks. */
|
|
tui_remove_hooks ();
|
|
rl_startup_hook = 0;
|
|
rl_already_prompted = 0;
|
|
|
|
/* Leave curses and restore previous gdb terminal setting. */
|
|
endwin ();
|
|
|
|
/* gdb terminal has changed, update gdb internal copy of it
|
|
so that terminal management with the inferior works. */
|
|
tui_setup_io (0);
|
|
|
|
/* Update gdb's knowledge of its terminal. */
|
|
gdb_save_tty_state ();
|
|
|
|
tui_active = false;
|
|
tui_update_gdb_sizes ();
|
|
}
|
|
|
|
/* Command wrapper for enabling tui mode. */
|
|
|
|
static void
|
|
tui_enable_command (const char *args, int from_tty)
|
|
{
|
|
tui_enable ();
|
|
}
|
|
|
|
/* Command wrapper for leaving tui mode. */
|
|
|
|
static void
|
|
tui_disable_command (const char *args, int from_tty)
|
|
{
|
|
tui_disable ();
|
|
}
|
|
|
|
void
|
|
tui_show_assembly (struct gdbarch *gdbarch, CORE_ADDR addr)
|
|
{
|
|
tui_suppress_output suppress;
|
|
tui_add_win_to_layout (DISASSEM_WIN);
|
|
tui_update_source_windows_with_addr (gdbarch, addr);
|
|
}
|
|
|
|
bool
|
|
tui_is_window_visible (enum tui_win_type type)
|
|
{
|
|
if (!tui_active)
|
|
return false;
|
|
|
|
if (tui_win_list[type] == 0)
|
|
return false;
|
|
|
|
return tui_win_list[type]->is_visible ();
|
|
}
|
|
|
|
bool
|
|
tui_get_command_dimension (unsigned int *width,
|
|
unsigned int *height)
|
|
{
|
|
if (!tui_active || (TUI_CMD_WIN == NULL))
|
|
return false;
|
|
|
|
*width = TUI_CMD_WIN->width;
|
|
*height = TUI_CMD_WIN->height;
|
|
return true;
|
|
}
|
|
|
|
void _initialize_tui ();
|
|
void
|
|
_initialize_tui ()
|
|
{
|
|
struct cmd_list_element **tuicmd;
|
|
|
|
tuicmd = tui_get_cmd_list ();
|
|
|
|
add_cmd ("enable", class_tui, tui_enable_command,
|
|
_("Enable TUI display mode.\n\
|
|
Usage: tui enable"),
|
|
tuicmd);
|
|
add_cmd ("disable", class_tui, tui_disable_command,
|
|
_("Disable TUI display mode.\n\
|
|
Usage: tui disable"),
|
|
tuicmd);
|
|
}
|