mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-27 04:52:05 +08:00
cd074e0415
My initial goal was to fix our gdb/testsuite/lib/tuiterm.exp such that it would correctly support (some limited) scrolling of the command window. What I observe is that when sending commands to the tui command window in a test script with: Term::command "p 1" The command window would be left looking like this: (gdb) (gdb) p 1$1 = 1 (gdb) When I would have expected it to look like this: (gdb) p 1 $1 = 1 (gdb) Obviously a bug in our tuiterm.exp library, right??? Wrong! Turns out there's a bug in GDB. If in GDB I enable the tui and then type (slowly) the 'p 1\r' (the \r is pressing the return key at the end of the string), then you do indeed get the "expected" terminal output. However, if instead I copy the 'p 1\r' string and paste it into the tui in one go then I now see the same corrupted output as we do when using tuiterm.exp. It turns out the problem is that GDB fails when handling lots of input arriving quickly with a \r (or \n) on the end. The reason for this bug is as follows: When the tui is active the terminal is in no-echo mode, so characters sent to the terminal are not echoed out again. This means that when the user types \r, this is not echoed to the terminal. The characters read in are passed to readline and \r indicates that the command line is complete and ready to be processed. However, the \r is not included in readlines command buffer, and is NOT printed by readline when is displays its buffer to the screen. So, in GDB we have to manually spot the \r when it is read in and update the display. Printing a newline character to the output and moving the cursor to the next line. This is done in tui_getc_1. Now readline tries to reduce the number of write calls. So if we very quickly (as in paste in one go) the text 'p 1' to readline (this time with no \r on the end), then readline will fetch the fist character and add it to its internal buffer. But before printing the character out readline checks to see if there's more input incoming. As we pasted multiple characters, then yes, readline sees the ' ' and adds this to its buffer, and finally the '1', this too is added to the buffer. Now if at this point we take a break, readline sees there is no more input available, and so prints its buffer out. Now when we press \r the code in tui_getc_1 kicks in, adds a \n to the output and moves the cursor to the next line. But, if instead we paste 'p 1\r' in one go then readline adds 'p 1' to its buffer as before, but now it sees that there is still more input available. Now it fetches the '\r', but this triggers the newline behaviour, we print '\n' and move to the next line - however readline has not printed its buffer yet! So finally we end up on the next line. There's no more input available so readline prints its buffer, then GDB gets passed the buffer, handles it, and prints the result. The solution I think is to put of our special newline insertion code until we know that readline has finished printing its buffer. Handily we know when this is - the next thing readline does is pass us the command line buffer for processing. So all we need to do is hook in to the command line processing, and before we pass the command line to GDB's internals we do all of the magic print a newline and move the cursor to the next line stuff. Luckily, GDB's interpreter mechanism already provides the hooks we need to do this. So all I do here is move the newline printing code from tui_getc_1 into a new function, setup a new input_handler hook for the tui, and call my new newline printing function. After this I can enable the tui and paste in 'p 1\r' and see the correct output. Also the tuiterm.exp library will now see non-corrupted output. gdb/ChangeLog: * tui/tui-interp.c (tui_command_line_handler): New function. (tui_interp::resume): Register tui_command_line_handler as the input_handler. * tui/tui-io.c (tui_inject_newline_into_command_window): New function. (tui_getc_1): Delete handling of '\n' and '\r'. * tui-io.h (tui_inject_newline_into_command_window): Declare. gdb/testsuite/ChangeLog: * gdb.tui/scroll.exp: Tighten expected results. Remove comment about bug in GDB, update expected results, and add more tests.
357 lines
8.4 KiB
C
357 lines
8.4 KiB
C
/* TUI Interpreter definitions for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2003-2021 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/cli-interp.h"
|
|
#include "interps.h"
|
|
#include "top.h"
|
|
#include "event-top.h"
|
|
#include "gdbsupport/event-loop.h"
|
|
#include "ui-out.h"
|
|
#include "cli-out.h"
|
|
#include "tui/tui-data.h"
|
|
#include "tui/tui-win.h"
|
|
#include "tui/tui.h"
|
|
#include "tui/tui-io.h"
|
|
#include "infrun.h"
|
|
#include "observable.h"
|
|
#include "gdbthread.h"
|
|
#include "inferior.h"
|
|
#include "main.h"
|
|
|
|
/* Set to true when the TUI mode must be activated when we first start
|
|
gdb. */
|
|
static bool tui_start_enabled = false;
|
|
|
|
class tui_interp final : public cli_interp_base
|
|
{
|
|
public:
|
|
explicit tui_interp (const char *name)
|
|
: cli_interp_base (name)
|
|
{}
|
|
|
|
void init (bool top_level) override;
|
|
void resume () override;
|
|
void suspend () override;
|
|
gdb_exception exec (const char *command_str) override;
|
|
ui_out *interp_ui_out () override;
|
|
};
|
|
|
|
/* Returns the INTERP if the INTERP is a TUI, and returns NULL
|
|
otherwise. */
|
|
|
|
static tui_interp *
|
|
as_tui_interp (struct interp *interp)
|
|
{
|
|
return dynamic_cast<tui_interp *> (interp);
|
|
}
|
|
|
|
/* Cleanup the tui before exiting. */
|
|
|
|
static void
|
|
tui_exit (void)
|
|
{
|
|
/* Disable the tui. Curses mode is left leaving the screen in a
|
|
clean state (see endwin()). */
|
|
tui_disable ();
|
|
}
|
|
|
|
/* 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
|
|
tui_on_normal_stop (struct bpstats *bs, int print_frame)
|
|
{
|
|
if (!print_frame)
|
|
return;
|
|
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *interp = top_level_interpreter ();
|
|
struct interp *tui = as_tui_interp (interp);
|
|
struct thread_info *thread;
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
thread = inferior_thread ();
|
|
if (should_print_stop_to_console (interp, thread))
|
|
print_stop_event (tui->interp_ui_out ());
|
|
}
|
|
}
|
|
|
|
/* Observer for the signal_received notification. */
|
|
|
|
static void
|
|
tui_on_signal_received (enum gdb_signal siggnal)
|
|
{
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
print_signal_received_reason (tui->interp_ui_out (), siggnal);
|
|
}
|
|
}
|
|
|
|
/* Observer for the end_stepping_range notification. */
|
|
|
|
static void
|
|
tui_on_end_stepping_range (void)
|
|
{
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
print_end_stepping_range_reason (tui->interp_ui_out ());
|
|
}
|
|
}
|
|
|
|
/* Observer for the signal_exited notification. */
|
|
|
|
static void
|
|
tui_on_signal_exited (enum gdb_signal siggnal)
|
|
{
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
print_signal_exited_reason (tui->interp_ui_out (), siggnal);
|
|
}
|
|
}
|
|
|
|
/* Observer for the exited notification. */
|
|
|
|
static void
|
|
tui_on_exited (int exitstatus)
|
|
{
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
print_exited_reason (tui->interp_ui_out (), exitstatus);
|
|
}
|
|
}
|
|
|
|
/* Observer for the no_history notification. */
|
|
|
|
static void
|
|
tui_on_no_history (void)
|
|
{
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
print_no_history_reason (tui->interp_ui_out ());
|
|
}
|
|
}
|
|
|
|
/* Observer for the sync_execution_done notification. */
|
|
|
|
static void
|
|
tui_on_sync_execution_done (void)
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
return;
|
|
|
|
display_gdb_prompt (NULL);
|
|
}
|
|
|
|
/* Observer for the command_error notification. */
|
|
|
|
static void
|
|
tui_on_command_error (void)
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
return;
|
|
|
|
display_gdb_prompt (NULL);
|
|
}
|
|
|
|
/* Observer for the user_selected_context_changed notification. */
|
|
|
|
static void
|
|
tui_on_user_selected_context_changed (user_selected_what selection)
|
|
{
|
|
/* This event is suppressed. */
|
|
if (cli_suppress_notification.user_selected_context)
|
|
return;
|
|
|
|
thread_info *tp = inferior_ptid != null_ptid ? inferior_thread () : NULL;
|
|
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct interp *tui = as_tui_interp (top_level_interpreter ());
|
|
|
|
if (tui == NULL)
|
|
continue;
|
|
|
|
if (selection & USER_SELECTED_INFERIOR)
|
|
print_selected_inferior (tui->interp_ui_out ());
|
|
|
|
if (tp != NULL
|
|
&& ((selection & (USER_SELECTED_THREAD | USER_SELECTED_FRAME))))
|
|
print_selected_thread_frame (tui->interp_ui_out (), selection);
|
|
|
|
}
|
|
}
|
|
|
|
/* These implement the TUI interpreter. */
|
|
|
|
void
|
|
tui_interp::init (bool top_level)
|
|
{
|
|
/* Install exit handler to leave the screen in a good shape. */
|
|
atexit (tui_exit);
|
|
|
|
tui_initialize_io ();
|
|
tui_initialize_win ();
|
|
if (gdb_stdout->isatty ())
|
|
tui_ensure_readline_initialized ();
|
|
}
|
|
|
|
/* Used as the command handler for the tui. */
|
|
|
|
static void
|
|
tui_command_line_handler (gdb::unique_xmalloc_ptr<char> &&rl)
|
|
{
|
|
/* When a tui enabled GDB is running in either tui mode or cli mode then
|
|
it is always the tui interpreter that is in use. As a result we end
|
|
up in here even in standard cli mode.
|
|
|
|
We only need to do any special actions when the tui is in use
|
|
though. When the tui is active the users return is not echoed to the
|
|
screen as a result the display will not automatically move us to the
|
|
next line. Here we manually insert a newline character and move the
|
|
cursor. */
|
|
if (tui_active)
|
|
tui_inject_newline_into_command_window ();
|
|
|
|
/* Now perform GDB's standard CLI command line handling. */
|
|
command_line_handler (std::move (rl));
|
|
}
|
|
|
|
void
|
|
tui_interp::resume ()
|
|
{
|
|
struct ui *ui = current_ui;
|
|
struct ui_file *stream;
|
|
|
|
/* gdb_setup_readline will change gdb_stdout. If the TUI was
|
|
previously writing to gdb_stdout, then set it to the new
|
|
gdb_stdout afterwards. */
|
|
|
|
stream = tui_old_uiout->set_stream (gdb_stdout);
|
|
if (stream != gdb_stdout)
|
|
{
|
|
tui_old_uiout->set_stream (stream);
|
|
stream = NULL;
|
|
}
|
|
|
|
gdb_setup_readline (1);
|
|
|
|
ui->input_handler = tui_command_line_handler;
|
|
|
|
if (stream != NULL)
|
|
tui_old_uiout->set_stream (gdb_stdout);
|
|
|
|
if (tui_start_enabled)
|
|
tui_enable ();
|
|
}
|
|
|
|
void
|
|
tui_interp::suspend ()
|
|
{
|
|
tui_start_enabled = tui_active;
|
|
tui_disable ();
|
|
}
|
|
|
|
ui_out *
|
|
tui_interp::interp_ui_out ()
|
|
{
|
|
if (tui_active)
|
|
return tui_out;
|
|
else
|
|
return tui_old_uiout;
|
|
}
|
|
|
|
gdb_exception
|
|
tui_interp::exec (const char *command_str)
|
|
{
|
|
internal_error (__FILE__, __LINE__, _("tui_exec called"));
|
|
}
|
|
|
|
|
|
/* Factory for TUI interpreters. */
|
|
|
|
static struct interp *
|
|
tui_interp_factory (const char *name)
|
|
{
|
|
return new tui_interp (name);
|
|
}
|
|
|
|
void _initialize_tui_interp ();
|
|
void
|
|
_initialize_tui_interp ()
|
|
{
|
|
interp_factory_register (INTERP_TUI, tui_interp_factory);
|
|
|
|
if (interpreter_p && strcmp (interpreter_p, INTERP_TUI) == 0)
|
|
tui_start_enabled = true;
|
|
|
|
if (interpreter_p && strcmp (interpreter_p, INTERP_CONSOLE) == 0)
|
|
{
|
|
xfree (interpreter_p);
|
|
interpreter_p = xstrdup (INTERP_TUI);
|
|
}
|
|
|
|
/* If changing this, remember to update cli-interp.c as well. */
|
|
gdb::observers::normal_stop.attach (tui_on_normal_stop);
|
|
gdb::observers::signal_received.attach (tui_on_signal_received);
|
|
gdb::observers::end_stepping_range.attach (tui_on_end_stepping_range);
|
|
gdb::observers::signal_exited.attach (tui_on_signal_exited);
|
|
gdb::observers::exited.attach (tui_on_exited);
|
|
gdb::observers::no_history.attach (tui_on_no_history);
|
|
gdb::observers::sync_execution_done.attach (tui_on_sync_execution_done);
|
|
gdb::observers::command_error.attach (tui_on_command_error);
|
|
gdb::observers::user_selected_context_changed.attach
|
|
(tui_on_user_selected_context_changed);
|
|
}
|