From 13d03262f25bfbf15e4a5f1d867cca243c7ee3ca Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Fri, 28 Apr 2023 14:27:11 -0400 Subject: [PATCH] gdb: move struct ui and related things to ui.{c,h} I'd like to move some things so they become methods on struct ui. But first, I think that struct ui and the related things are big enough to deserve their own file, instead of being scattered through top.{c,h} and event-top.c. Change-Id: I15594269ace61fd76ef80a7b58f51ff3ab6979bc --- gdb/Makefile.in | 2 + gdb/annotate.c | 3 +- gdb/async-event.c | 1 + gdb/breakpoint.c | 1 + gdb/bt-utils.c | 2 +- gdb/cli-out.c | 2 +- gdb/cli/cli-cmds.c | 1 + gdb/cli/cli-interp.c | 1 + gdb/cli/cli-script.c | 1 + gdb/compile/compile.c | 2 +- gdb/event-top.c | 78 +------------ gdb/exceptions.c | 2 +- gdb/fork-child.c | 2 +- gdb/guile/guile.c | 1 + gdb/guile/scm-ports.c | 2 +- gdb/inf-loop.c | 1 + gdb/infcall.c | 1 + gdb/infcmd.c | 2 +- gdb/infrun.c | 1 + gdb/interps.c | 2 +- gdb/main.c | 1 + gdb/mi/mi-interp.c | 2 +- gdb/mi/mi-main.c | 1 + gdb/python/py-dap.c | 2 +- gdb/python/python.c | 1 + gdb/target.c | 2 +- gdb/top.c | 149 +------------------------ gdb/top.h | 200 +-------------------------------- gdb/tui/tui-interp.c | 2 +- gdb/tui/tui-io.c | 1 + gdb/tui/tui.c | 1 + gdb/ui.c | 251 ++++++++++++++++++++++++++++++++++++++++++ gdb/ui.h | 218 ++++++++++++++++++++++++++++++++++++ gdb/utils.c | 1 + 34 files changed, 512 insertions(+), 428 deletions(-) create mode 100644 gdb/ui.c create mode 100644 gdb/ui.h diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 40497541880..6af65357243 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1209,6 +1209,7 @@ COMMON_SFILES = \ target-float.c \ type-stack.c \ typeprint.c \ + ui.c \ ui-file.c \ ui-out.c \ ui-style.c \ @@ -1496,6 +1497,7 @@ HFILES_NO_SRCDIR = \ tramp-frame.h \ type-stack.h \ typeprint.h \ + ui.h \ ui-file.h \ ui-out.h \ ui-style.h \ diff --git a/gdb/annotate.c b/gdb/annotate.c index 60fe6ccd5c2..d403a47ba2f 100644 --- a/gdb/annotate.c +++ b/gdb/annotate.c @@ -25,11 +25,10 @@ #include "observable.h" #include "inferior.h" #include "infrun.h" -#include "top.h" #include "source.h" #include "objfiles.h" #include "source-cache.h" - +#include "ui.h" /* Prototypes for local functions. */ diff --git a/gdb/async-event.c b/gdb/async-event.c index a190e77f329..a094f314aa3 100644 --- a/gdb/async-event.c +++ b/gdb/async-event.c @@ -21,6 +21,7 @@ #include "ser-event.h" #include "top.h" +#include "ui.h" /* PROC is a function to be invoked when the READY flag is set. This happens when there has been a signal and the corresponding signal diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 20e016e8cb3..54775000919 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -53,6 +53,7 @@ #include "memattr.h" #include "ada-lang.h" #include "top.h" +#include "ui.h" #include "valprint.h" #include "jit.h" #include "parser-defs.h" diff --git a/gdb/bt-utils.c b/gdb/bt-utils.c index 68c3f081675..89440351332 100644 --- a/gdb/bt-utils.c +++ b/gdb/bt-utils.c @@ -19,7 +19,7 @@ #include "bt-utils.h" #include "command.h" #include "gdbcmd.h" -#include "top.h" +#include "ui.h" #include "cli/cli-decode.h" /* See bt-utils.h. */ diff --git a/gdb/cli-out.c b/gdb/cli-out.c index fdfd0f7f0cf..4c598883d4b 100644 --- a/gdb/cli-out.c +++ b/gdb/cli-out.c @@ -26,7 +26,7 @@ #include "completer.h" #include "readline/readline.h" #include "cli/cli-style.h" -#include "top.h" +#include "ui.h" /* These are the CLI output functions */ diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c index 3b1c6a9f4bd..d466cc6c34d 100644 --- a/gdb/cli/cli-cmds.c +++ b/gdb/cli/cli-cmds.c @@ -45,6 +45,7 @@ #include "interps.h" #include "top.h" +#include "ui.h" #include "cli/cli-decode.h" #include "cli/cli-script.h" #include "cli/cli-setshow.h" diff --git a/gdb/cli/cli-interp.c b/gdb/cli/cli-interp.c index 5a515c603c6..84fe34a1014 100644 --- a/gdb/cli/cli-interp.c +++ b/gdb/cli/cli-interp.c @@ -24,6 +24,7 @@ #include "ui-out.h" #include "cli-out.h" #include "top.h" /* for "execute_command" */ +#include "ui.h" #include "infrun.h" #include "observable.h" #include "gdbthread.h" diff --git a/gdb/cli/cli-script.c b/gdb/cli/cli-script.c index b96dd74330c..8ec5689ebcf 100644 --- a/gdb/cli/cli-script.c +++ b/gdb/cli/cli-script.c @@ -23,6 +23,7 @@ #include "ui-out.h" #include "top.h" +#include "ui.h" #include "breakpoint.h" #include "tracepoint.h" #include "cli/cli-cmds.h" diff --git a/gdb/compile/compile.c b/gdb/compile/compile.c index c07686c32e0..a93c9404660 100644 --- a/gdb/compile/compile.c +++ b/gdb/compile/compile.c @@ -18,7 +18,7 @@ along with this program. If not, see . */ #include "defs.h" -#include "top.h" +#include "ui.h" #include "ui-out.h" #include "command.h" #include "cli/cli-script.h" diff --git a/gdb/event-top.c b/gdb/event-top.c index 53ddd515be7..193ea5363ff 100644 --- a/gdb/event-top.c +++ b/gdb/event-top.c @@ -21,6 +21,7 @@ #include "defs.h" #include "top.h" +#include "ui.h" #include "inferior.h" #include "infrun.h" #include "target.h" @@ -480,12 +481,6 @@ top_level_prompt (void) return prompt; } -/* See top.h. */ - -struct ui *main_ui; -struct ui *current_ui; -struct ui *ui_list; - /* Get a reference to the current UI's line buffer. This is used to construct a whole line of input from partial input. */ @@ -495,77 +490,6 @@ get_command_line_buffer (void) return current_ui->line_buffer; } -/* When there is an event ready on the stdin file descriptor, instead - of calling readline directly throught the callback function, or - instead of calling gdb_readline_no_editing_callback, give gdb a - chance to detect errors and do something. */ - -static void -stdin_event_handler (int error, gdb_client_data client_data) -{ - struct ui *ui = (struct ui *) client_data; - - if (error) - { - /* Switch to the main UI, so diagnostics always go there. */ - current_ui = main_ui; - - ui->unregister_file_handler (); - if (main_ui == ui) - { - /* If stdin died, we may as well kill gdb. */ - gdb_printf (gdb_stderr, _("error detected on stdin\n")); - quit_command ((char *) 0, 0); - } - else - { - /* Simply delete the UI. */ - delete ui; - } - } - else - { - /* Switch to the UI whose input descriptor woke up the event - loop. */ - current_ui = ui; - - /* This makes sure a ^C immediately followed by further input is - always processed in that order. E.g,. with input like - "^Cprint 1\n", the SIGINT handler runs, marks the async - signal handler, and then select/poll may return with stdin - ready, instead of -1/EINTR. The - gdb.base/double-prompt-target-event-error.exp test exercises - this. */ - QUIT; - - do - { - call_stdin_event_handler_again_p = 0; - ui->call_readline (client_data); - } - while (call_stdin_event_handler_again_p != 0); - } -} - -/* See top.h. */ - -void -ui::register_file_handler () -{ - if (input_fd != -1) - add_file_handler (input_fd, stdin_event_handler, this, - string_printf ("ui-%d", num), true); -} - -/* See top.h. */ - -void -ui::unregister_file_handler () -{ - if (input_fd != -1) - delete_file_handler (input_fd); -} - /* Re-enable stdin after the end of an execution command in synchronous mode, or after an error from the target, and we aborted the exec operation. */ diff --git a/gdb/exceptions.c b/gdb/exceptions.c index 8b7858578a9..2ba5a9c9fad 100644 --- a/gdb/exceptions.c +++ b/gdb/exceptions.c @@ -26,7 +26,7 @@ #include "ui-out.h" #include "serial.h" #include "gdbthread.h" -#include "top.h" +#include "ui.h" #include "gdbsupport/gdb_optional.h" static void diff --git a/gdb/fork-child.c b/gdb/fork-child.c index d210427f4b0..9e6f80a7953 100644 --- a/gdb/fork-child.c +++ b/gdb/fork-child.c @@ -24,7 +24,7 @@ #include "gdbcmd.h" #include "terminal.h" #include "gdbthread.h" -#include "top.h" +#include "ui.h" #include "gdbsupport/job-control.h" #include "gdbsupport/filestuff.h" #include "nat/fork-inferior.h" diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c index 887b7fa5dc8..b45081fe1cc 100644 --- a/gdb/guile/guile.c +++ b/gdb/guile/guile.c @@ -28,6 +28,7 @@ #include "command.h" #include "gdbcmd.h" #include "top.h" +#include "ui.h" #include "extension-priv.h" #include "utils.h" #include "gdbsupport/version.h" diff --git a/gdb/guile/scm-ports.c b/gdb/guile/scm-ports.c index 48b60759234..a27ea8b3c80 100644 --- a/gdb/guile/scm-ports.c +++ b/gdb/guile/scm-ports.c @@ -23,7 +23,7 @@ #include "defs.h" #include "gdbsupport/gdb_select.h" -#include "top.h" +#include "ui.h" #include "target.h" #include "guile-internal.h" #include "gdbsupport/gdb_optional.h" diff --git a/gdb/inf-loop.c b/gdb/inf-loop.c index b9f25008247..8e7bcb67b3e 100644 --- a/gdb/inf-loop.c +++ b/gdb/inf-loop.c @@ -28,6 +28,7 @@ #include "gdbthread.h" #include "interps.h" #include "top.h" +#include "ui.h" #include "observable.h" /* General function to handle events in the inferior. */ diff --git a/gdb/infcall.c b/gdb/infcall.c index e6cc6ed1a21..233ef5f29e9 100644 --- a/gdb/infcall.c +++ b/gdb/infcall.c @@ -38,6 +38,7 @@ #include "event-top.h" #include "observable.h" #include "top.h" +#include "ui.h" #include "interps.h" #include "thread-fsm.h" #include diff --git a/gdb/infcmd.c b/gdb/infcmd.c index dd3675e37c5..b8134665f3f 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -49,7 +49,7 @@ #include "inf-loop.h" #include "linespec.h" #include "thread-fsm.h" -#include "top.h" +#include "ui.h" #include "interps.h" #include "skip.h" #include "gdbsupport/gdb_optional.h" diff --git a/gdb/infrun.c b/gdb/infrun.c index 2f1c6cd694b..efe2c00c489 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -34,6 +34,7 @@ #include "annotate.h" #include "symfile.h" #include "top.h" +#include "ui.h" #include "inf-loop.h" #include "regcache.h" #include "value.h" diff --git a/gdb/interps.c b/gdb/interps.c index ee451f27a63..a066c4acfed 100644 --- a/gdb/interps.c +++ b/gdb/interps.c @@ -36,7 +36,7 @@ #include "event-top.h" #include "interps.h" #include "completer.h" -#include "top.h" /* For command_loop. */ +#include "ui.h" #include "main.h" #include "gdbsupport/buildargv.h" #include "gdbsupport/scope-exit.h" diff --git a/gdb/main.c b/gdb/main.c index 9fb6dceb9ca..5c23714f52e 100644 --- a/gdb/main.c +++ b/gdb/main.c @@ -19,6 +19,7 @@ #include "defs.h" #include "top.h" +#include "ui.h" #include "target.h" #include "inferior.h" #include "symfile.h" diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c index ce78b0c2b58..ad33a21374a 100644 --- a/gdb/mi/mi-interp.c +++ b/gdb/mi/mi-interp.c @@ -27,7 +27,7 @@ #include "inferior.h" #include "infrun.h" #include "ui-out.h" -#include "top.h" +#include "ui.h" #include "mi-main.h" #include "mi-cmds.h" #include "mi-out.h" diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 19cdf47a283..35c74c407ee 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -25,6 +25,7 @@ #include "inferior.h" #include "infrun.h" #include "top.h" +#include "ui.h" #include "gdbthread.h" #include "mi-cmds.h" #include "mi-parse.h" diff --git a/gdb/python/py-dap.c b/gdb/python/py-dap.c index 32f927214d7..52188406982 100644 --- a/gdb/python/py-dap.c +++ b/gdb/python/py-dap.c @@ -21,7 +21,7 @@ #include "python-internal.h" #include "interps.h" #include "cli-out.h" -#include "top.h" +#include "ui.h" class dap_interp final : public interp { diff --git a/gdb/python/python.c b/gdb/python/python.c index ea51766ec3e..168a0009f1b 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -64,6 +64,7 @@ static const char *gdbpy_should_print_stack = python_excp_message; #include "cli/cli-decode.h" #include "charset.h" #include "top.h" +#include "ui.h" #include "python-internal.h" #include "linespec.h" #include "source.h" diff --git a/gdb/target.c b/gdb/target.c index 0cebecfafc3..80d2c80d4bf 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -44,7 +44,7 @@ #include "gdbsupport/agent.h" #include "auxv.h" #include "target-debug.h" -#include "top.h" +#include "ui.h" #include "event-top.h" #include #include "gdbsupport/byte-vector.h" diff --git a/gdb/top.c b/gdb/top.c index 81f74f72f61..0b819091d11 100644 --- a/gdb/top.c +++ b/gdb/top.c @@ -39,6 +39,7 @@ #include "annotate.h" #include "completer.h" #include "top.h" +#include "ui.h" #include "gdbsupport/version.h" #include "serial.h" #include "main.h" @@ -253,13 +254,9 @@ void (*deprecated_call_command_hook) (struct cmd_list_element * c, void (*deprecated_context_hook) (int id); -/* The highest UI number ever assigned. */ -static int highest_ui_num; - -/* Unbuffer STREAM. This is a wrapper around setbuf(STREAM, nullptr) - which applies some special rules for MS-Windows hosts. */ +/* See top.h. */ -static void +void unbuffer_stream (FILE *stream) { /* Unbuffer the input stream so that in gdb_readline_no_editing_callback, @@ -291,118 +288,6 @@ unbuffer_stream (FILE *stream) #endif } -/* See top.h. */ - -ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_) - : num (++highest_ui_num), - stdin_stream (instream_), - instream (instream_), - outstream (outstream_), - errstream (errstream_), - input_fd (fileno (instream)), - m_input_interactive_p (ISATTY (instream)), - m_gdb_stdout (new pager_file (new stdio_file (outstream))), - m_gdb_stdin (new stdio_file (instream)), - m_gdb_stderr (new stderr_file (errstream)), - m_gdb_stdlog (new timestamped_file (m_gdb_stderr)) -{ - unbuffer_stream (instream_); - - if (ui_list == NULL) - ui_list = this; - else - { - struct ui *last; - - for (last = ui_list; last->next != NULL; last = last->next) - ; - last->next = this; - } -} - -ui::~ui () -{ - struct ui *ui, *uiprev; - - uiprev = NULL; - - for (ui = ui_list; ui != NULL; uiprev = ui, ui = ui->next) - if (ui == this) - break; - - gdb_assert (ui != NULL); - - if (uiprev != NULL) - uiprev->next = next; - else - ui_list = next; - - delete m_gdb_stdin; - delete m_gdb_stdout; - delete m_gdb_stderr; -} - -/* Open file named NAME for read/write, making sure not to make it the - controlling terminal. */ - -static gdb_file_up -open_terminal_stream (const char *name) -{ - scoped_fd fd = gdb_open_cloexec (name, O_RDWR | O_NOCTTY, 0); - if (fd.get () < 0) - perror_with_name (_("opening terminal failed")); - - return fd.to_file ("w+"); -} - -/* Implementation of the "new-ui" command. */ - -static void -new_ui_command (const char *args, int from_tty) -{ - int argc; - const char *interpreter_name; - const char *tty_name; - - dont_repeat (); - - gdb_argv argv (args); - argc = argv.count (); - - if (argc < 2) - error (_("Usage: new-ui INTERPRETER TTY")); - - interpreter_name = argv[0]; - tty_name = argv[1]; - - { - scoped_restore save_ui = make_scoped_restore (¤t_ui); - - /* Open specified terminal. Note: we used to open it three times, - once for each of stdin/stdout/stderr, but that does not work - with Windows named pipes. */ - gdb_file_up stream = open_terminal_stream (tty_name); - - std::unique_ptr ui - (new struct ui (stream.get (), stream.get (), stream.get ())); - - ui->async = 1; - - current_ui = ui.get (); - - set_top_level_interpreter (interpreter_name); - - interp_pre_command_loop (top_level_interpreter ()); - - /* Make sure the file is not closed. */ - stream.release (); - - ui.release (); - } - - gdb_printf ("New UI allocated\n"); -} - /* Handler for SIGHUP. */ #ifdef SIGHUP @@ -1917,8 +1802,9 @@ quit_force (int *exit_arg, int from_tty) exit (exit_code); } -/* The value of the "interactive-mode" setting. */ -static enum auto_boolean interactive_mode = AUTO_BOOLEAN_AUTO; +/* See top.h. */ + +auto_boolean interactive_mode = AUTO_BOOLEAN_AUTO; /* Implement the "show interactive-mode" option. */ @@ -1935,20 +1821,6 @@ show_interactive_mode (struct ui_file *file, int from_tty, gdb_printf (file, "Debugger's interactive mode is %s.\n", value); } -/* Returns whether GDB is running on an interactive terminal. */ - -bool -ui::input_interactive_p () const -{ - if (batch_flag) - return false; - - if (interactive_mode != AUTO_BOOLEAN_AUTO) - return interactive_mode == AUTO_BOOLEAN_TRUE; - - return m_input_interactive_p; -} - static void dont_repeat_command (const char *ignored, int from_tty) { @@ -2241,8 +2113,6 @@ show_startup_quiet (struct ui_file *file, int from_tty, static void init_main (void) { - struct cmd_list_element *c; - /* Initialize the prompt to a simple "(gdb) " prompt or to whatever the DEFAULT_PROMPT is. */ set_prompt (DEFAULT_PROMPT); @@ -2395,13 +2265,6 @@ affect future GDB sessions."), show_startup_quiet, &setlist, &showlist); - c = add_cmd ("new-ui", class_support, new_ui_command, _("\ -Create a new UI.\n\ -Usage: new-ui INTERPRETER TTY\n\ -The first argument is the name of the interpreter to run.\n\ -The second argument is the terminal the UI runs on."), &cmdlist); - set_cmd_completer (c, interpreter_completer); - struct internalvar *major_version_var = create_internalvar ("_gdb_major"); struct internalvar *minor_version_var = create_internalvar ("_gdb_minor"); int vmajor = 0, vminor = 0, vrevision = 0; diff --git a/gdb/top.h b/gdb/top.h index 5c1ccfee736..47e16ca104e 100644 --- a/gdb/top.h +++ b/gdb/top.h @@ -24,203 +24,10 @@ #include "gdbsupport/next-iterator.h" #include "value.h" -/* Prompt state. */ - -enum prompt_state -{ - /* The command line is blocked simulating synchronous execution. - This is used to implement the foreground execution commands - ('run', 'continue', etc.). We won't display the prompt and - accept further commands until the execution is actually over. */ - PROMPT_BLOCKED, - - /* The command finished; display the prompt before returning back to - the top level. */ - PROMPT_NEEDED, - - /* We've displayed the prompt already, ready for input. */ - PROMPTED, -}; - -/* All about a user interface instance. Each user interface has its - own I/O files/streams, readline state, its own top level - interpreter (for the main UI, this is the interpreter specified - with -i on the command line) and secondary interpreters (for - interpreter-exec ...), etc. There's always one UI associated with - stdin/stdout/stderr, but the user can create secondary UIs, for - example, to create a separate MI channel on its own stdio - streams. */ - -struct ui -{ - /* Create a new UI. */ - ui (FILE *instream, FILE *outstream, FILE *errstream); - ~ui (); - - DISABLE_COPY_AND_ASSIGN (ui); - - /* Pointer to next in singly-linked list. */ - struct ui *next = nullptr; - - /* Convenient handle (UI number). Unique across all UIs. */ - int num; - - /* The UI's command line buffer. This is to used to accumulate - input until we have a whole command line. */ - std::string line_buffer; - - /* The callback used by the event loop whenever an event is detected - on the UI's input file descriptor. This function incrementally - builds a buffer where it accumulates the line read up to the - point of invocation. In the special case in which the character - read is newline, the function invokes the INPUT_HANDLER callback - (see below). */ - void (*call_readline) (gdb_client_data) = nullptr; - - /* The function to invoke when a complete line of input is ready for - processing. */ - void (*input_handler) (gdb::unique_xmalloc_ptr &&) = nullptr; - - /* True if this UI is using the readline library for command - editing; false if using GDB's own simple readline emulation, with - no editing support. */ - int command_editing = 0; - - /* Each UI has its own independent set of interpreters. */ - struct ui_interp_info *interp_info = nullptr; - - /* True if the UI is in async mode, false if in sync mode. If in - sync mode, a synchronous execution command (e.g, "next") does not - return until the command is finished. If in async mode, then - running a synchronous command returns right after resuming the - target. Waiting for the command's completion is later done on - the top event loop. For the main UI, this starts out disabled, - until all the explicit command line arguments (e.g., `gdb -ex - "start" -ex "next"') are processed. */ - int async = 0; - - /* The number of nested readline secondary prompts that are - currently active. */ - int secondary_prompt_depth = 0; - - /* The UI's stdin. Set to stdin for the main UI. */ - FILE *stdin_stream; - - /* stdio stream that command input is being read from. Set to stdin - normally. Set by source_command to the file we are sourcing. - Set to NULL if we are executing a user-defined command or - interacting via a GUI. */ - FILE *instream; - /* Standard output stream. */ - FILE *outstream; - /* Standard error stream. */ - FILE *errstream; - - /* The file descriptor for the input stream, so that we can register - it with the event loop. This can be set to -1 to prevent this - registration. */ - int input_fd; - - /* Whether ISATTY returns true on input_fd. Cached here because - quit_force needs to know this _after_ input_fd might be - closed. */ - bool m_input_interactive_p; - - /* See enum prompt_state's description. */ - enum prompt_state prompt_state = PROMPT_NEEDED; - - /* The fields below that start with "m_" are "private". They're - meant to be accessed through wrapper macros that make them look - like globals. */ - - /* The ui_file streams. */ - /* Normal results */ - struct ui_file *m_gdb_stdout; - /* Input stream */ - struct ui_file *m_gdb_stdin; - /* Serious error notifications */ - struct ui_file *m_gdb_stderr; - /* Log/debug/trace messages that should bypass normal stdout/stderr - filtering. */ - struct ui_file *m_gdb_stdlog; - - /* The current ui_out. */ - struct ui_out *m_current_uiout = nullptr; - - /* Register the UI's input file descriptor in the event loop. */ - void register_file_handler (); - - /* Unregister the UI's input file descriptor from the event loop. */ - void unregister_file_handler (); - - /* Return true if this UI's input fd is a tty. */ - bool input_interactive_p () const; -}; - -/* The main UI. This is the UI that is bound to stdin/stdout/stderr. - It always exists and is created automatically when GDB starts - up. */ -extern struct ui *main_ui; - -/* The current UI. */ -extern struct ui *current_ui; - -/* The list of all UIs. */ -extern struct ui *ui_list; - -/* State for SWITCH_THRU_ALL_UIS. */ -class switch_thru_all_uis -{ -public: - - switch_thru_all_uis () : m_iter (ui_list), m_save_ui (¤t_ui) - { - current_ui = ui_list; - } - - DISABLE_COPY_AND_ASSIGN (switch_thru_all_uis); - - /* If done iterating, return true; otherwise return false. */ - bool done () const - { - return m_iter == NULL; - } - - /* Move to the next UI, setting current_ui if iteration is not yet - complete. */ - void next () - { - m_iter = m_iter->next; - if (m_iter != NULL) - current_ui = m_iter; - } - - private: - - /* Used to iterate through the UIs. */ - struct ui *m_iter; - - /* Save and restore current_ui. */ - scoped_restore_tmpl m_save_ui; -}; - - /* Traverse through all UI, and switch the current UI to the one - being iterated. */ -#define SWITCH_THRU_ALL_UIS() \ - for (switch_thru_all_uis stau_state; !stau_state.done (); stau_state.next ()) - -using ui_range = next_range; - -/* An adapter that can be used to traverse over all UIs. */ -static inline -ui_range all_uis () -{ - return ui_range (ui_list); -} - /* From top.c. */ extern bool confirm; extern int inhibit_gdbinit; +extern auto_boolean interactive_mode; /* Print the GDB version banner to STREAM. If INTERACTIVE is false, then information referring to commands (e.g., "show configuration") @@ -297,4 +104,9 @@ extern const char *handle_line_of_input (std::string &cmd_line_buffer, extern bool check_quiet_mode (); +/* Unbuffer STREAM. This is a wrapper around setbuf(STREAM, nullptr) + which applies some special rules for MS-Windows hosts. */ + +extern void unbuffer_stream (FILE *stream); + #endif diff --git a/gdb/tui/tui-interp.c b/gdb/tui/tui-interp.c index 812c62c6422..299cc4caea0 100644 --- a/gdb/tui/tui-interp.c +++ b/gdb/tui/tui-interp.c @@ -20,7 +20,7 @@ #include "defs.h" #include "cli/cli-interp.h" #include "interps.h" -#include "top.h" +#include "ui.h" #include "event-top.h" #include "gdbsupport/event-loop.h" #include "ui-out.h" diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c index 7752701378e..a1eadcd937d 100644 --- a/gdb/tui/tui-io.c +++ b/gdb/tui/tui-io.c @@ -25,6 +25,7 @@ #include "event-top.h" #include "command.h" #include "top.h" +#include "ui.h" #include "tui/tui.h" #include "tui/tui-data.h" #include "tui/tui-io.h" diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c index 3604194a760..10cf811a41e 100644 --- a/gdb/tui/tui.c +++ b/gdb/tui/tui.c @@ -41,6 +41,7 @@ #include "source.h" #include "terminal.h" #include "top.h" +#include "ui.h" #include #include diff --git a/gdb/ui.c b/gdb/ui.c new file mode 100644 index 00000000000..5fe001262a8 --- /dev/null +++ b/gdb/ui.c @@ -0,0 +1,251 @@ +/* Copyright (C) 2023 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 . */ + +#include "defs.h" +#include "ui.h" + +#include "cli/cli-cmds.h" +#include "event-top.h" +#include "gdbsupport/buildargv.h" +#include "gdbsupport/filestuff.h" +#include "gdbsupport/gdb_file.h" +#include "gdbsupport/scoped_fd.h" +#include "interps.h" +#include "pager.h" +#include "main.h" +#include "top.h" + +/* See top.h. */ + +struct ui *main_ui; +struct ui *current_ui; +struct ui *ui_list; + +/* The highest UI number ever assigned. */ + +static int highest_ui_num; + +/* See top.h. */ + +ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_) + : num (++highest_ui_num), + stdin_stream (instream_), + instream (instream_), + outstream (outstream_), + errstream (errstream_), + input_fd (fileno (instream)), + m_input_interactive_p (ISATTY (instream)), + m_gdb_stdout (new pager_file (new stdio_file (outstream))), + m_gdb_stdin (new stdio_file (instream)), + m_gdb_stderr (new stderr_file (errstream)), + m_gdb_stdlog (new timestamped_file (m_gdb_stderr)) +{ + unbuffer_stream (instream_); + + if (ui_list == NULL) + ui_list = this; + else + { + struct ui *last; + + for (last = ui_list; last->next != NULL; last = last->next) + ; + last->next = this; + } +} + +ui::~ui () +{ + struct ui *ui, *uiprev; + + uiprev = NULL; + + for (ui = ui_list; ui != NULL; uiprev = ui, ui = ui->next) + if (ui == this) + break; + + gdb_assert (ui != NULL); + + if (uiprev != NULL) + uiprev->next = next; + else + ui_list = next; + + delete m_gdb_stdin; + delete m_gdb_stdout; + delete m_gdb_stderr; +} + + +/* Returns whether GDB is running on an interactive terminal. */ + +bool +ui::input_interactive_p () const +{ + if (batch_flag) + return false; + + if (interactive_mode != AUTO_BOOLEAN_AUTO) + return interactive_mode == AUTO_BOOLEAN_TRUE; + + return m_input_interactive_p; +} + + +/* When there is an event ready on the stdin file descriptor, instead + of calling readline directly throught the callback function, or + instead of calling gdb_readline_no_editing_callback, give gdb a + chance to detect errors and do something. */ + +static void +stdin_event_handler (int error, gdb_client_data client_data) +{ + struct ui *ui = (struct ui *) client_data; + + if (error) + { + /* Switch to the main UI, so diagnostics always go there. */ + current_ui = main_ui; + + ui->unregister_file_handler (); + if (main_ui == ui) + { + /* If stdin died, we may as well kill gdb. */ + gdb_printf (gdb_stderr, _("error detected on stdin\n")); + quit_command ((char *) 0, 0); + } + else + { + /* Simply delete the UI. */ + delete ui; + } + } + else + { + /* Switch to the UI whose input descriptor woke up the event + loop. */ + current_ui = ui; + + /* This makes sure a ^C immediately followed by further input is + always processed in that order. E.g,. with input like + "^Cprint 1\n", the SIGINT handler runs, marks the async + signal handler, and then select/poll may return with stdin + ready, instead of -1/EINTR. The + gdb.base/double-prompt-target-event-error.exp test exercises + this. */ + QUIT; + + do + { + call_stdin_event_handler_again_p = 0; + ui->call_readline (client_data); + } + while (call_stdin_event_handler_again_p != 0); + } +} + +/* See top.h. */ + +void +ui::register_file_handler () +{ + if (input_fd != -1) + add_file_handler (input_fd, stdin_event_handler, this, + string_printf ("ui-%d", num), true); +} + +/* See top.h. */ + +void +ui::unregister_file_handler () +{ + if (input_fd != -1) + delete_file_handler (input_fd); +} + +/* Open file named NAME for read/write, making sure not to make it the + controlling terminal. */ + +static gdb_file_up +open_terminal_stream (const char *name) +{ + scoped_fd fd = gdb_open_cloexec (name, O_RDWR | O_NOCTTY, 0); + if (fd.get () < 0) + perror_with_name (_("opening terminal failed")); + + return fd.to_file ("w+"); +} + +/* Implementation of the "new-ui" command. */ + +static void +new_ui_command (const char *args, int from_tty) +{ + int argc; + const char *interpreter_name; + const char *tty_name; + + dont_repeat (); + + gdb_argv argv (args); + argc = argv.count (); + + if (argc < 2) + error (_("Usage: new-ui INTERPRETER TTY")); + + interpreter_name = argv[0]; + tty_name = argv[1]; + + { + scoped_restore save_ui = make_scoped_restore (¤t_ui); + + /* Open specified terminal. Note: we used to open it three times, + once for each of stdin/stdout/stderr, but that does not work + with Windows named pipes. */ + gdb_file_up stream = open_terminal_stream (tty_name); + + std::unique_ptr ui + (new struct ui (stream.get (), stream.get (), stream.get ())); + + ui->async = 1; + + current_ui = ui.get (); + + set_top_level_interpreter (interpreter_name); + + interp_pre_command_loop (top_level_interpreter ()); + + /* Make sure the file is not closed. */ + stream.release (); + + ui.release (); + } + + gdb_printf ("New UI allocated\n"); +} + +void _initialize_ui (); +void +_initialize_ui () +{ + cmd_list_element *c = add_cmd ("new-ui", class_support, new_ui_command, _("\ +Create a new UI.\n\ +Usage: new-ui INTERPRETER TTY\n\ +The first argument is the name of the interpreter to run.\n\ +The second argument is the terminal the UI runs on."), &cmdlist); + set_cmd_completer (c, interpreter_completer); +} diff --git a/gdb/ui.h b/gdb/ui.h new file mode 100644 index 00000000000..8da4b2d8eee --- /dev/null +++ b/gdb/ui.h @@ -0,0 +1,218 @@ +/* Copyright (C) 2023 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 . */ + +#ifndef UI_H +#define UI_H + +#include "gdbsupport/event-loop.h" +#include "gdbsupport/next-iterator.h" + +/* Prompt state. */ + +enum prompt_state +{ + /* The command line is blocked simulating synchronous execution. + This is used to implement the foreground execution commands + ('run', 'continue', etc.). We won't display the prompt and + accept further commands until the execution is actually over. */ + PROMPT_BLOCKED, + + /* The command finished; display the prompt before returning back to + the top level. */ + PROMPT_NEEDED, + + /* We've displayed the prompt already, ready for input. */ + PROMPTED, +}; + +/* All about a user interface instance. Each user interface has its + own I/O files/streams, readline state, its own top level + interpreter (for the main UI, this is the interpreter specified + with -i on the command line) and secondary interpreters (for + interpreter-exec ...), etc. There's always one UI associated with + stdin/stdout/stderr, but the user can create secondary UIs, for + example, to create a separate MI channel on its own stdio + streams. */ + +struct ui +{ + /* Create a new UI. */ + ui (FILE *instream, FILE *outstream, FILE *errstream); + ~ui (); + + DISABLE_COPY_AND_ASSIGN (ui); + + /* Pointer to next in singly-linked list. */ + struct ui *next = nullptr; + + /* Convenient handle (UI number). Unique across all UIs. */ + int num; + + /* The UI's command line buffer. This is to used to accumulate + input until we have a whole command line. */ + std::string line_buffer; + + /* The callback used by the event loop whenever an event is detected + on the UI's input file descriptor. This function incrementally + builds a buffer where it accumulates the line read up to the + point of invocation. In the special case in which the character + read is newline, the function invokes the INPUT_HANDLER callback + (see below). */ + void (*call_readline) (gdb_client_data) = nullptr; + + /* The function to invoke when a complete line of input is ready for + processing. */ + void (*input_handler) (gdb::unique_xmalloc_ptr &&) = nullptr; + + /* True if this UI is using the readline library for command + editing; false if using GDB's own simple readline emulation, with + no editing support. */ + int command_editing = 0; + + /* Each UI has its own independent set of interpreters. */ + struct ui_interp_info *interp_info = nullptr; + + /* True if the UI is in async mode, false if in sync mode. If in + sync mode, a synchronous execution command (e.g, "next") does not + return until the command is finished. If in async mode, then + running a synchronous command returns right after resuming the + target. Waiting for the command's completion is later done on + the top event loop. For the main UI, this starts out disabled, + until all the explicit command line arguments (e.g., `gdb -ex + "start" -ex "next"') are processed. */ + int async = 0; + + /* The number of nested readline secondary prompts that are + currently active. */ + int secondary_prompt_depth = 0; + + /* The UI's stdin. Set to stdin for the main UI. */ + FILE *stdin_stream; + + /* stdio stream that command input is being read from. Set to stdin + normally. Set by source_command to the file we are sourcing. + Set to NULL if we are executing a user-defined command or + interacting via a GUI. */ + FILE *instream; + /* Standard output stream. */ + FILE *outstream; + /* Standard error stream. */ + FILE *errstream; + + /* The file descriptor for the input stream, so that we can register + it with the event loop. This can be set to -1 to prevent this + registration. */ + int input_fd; + + /* Whether ISATTY returns true on input_fd. Cached here because + quit_force needs to know this _after_ input_fd might be + closed. */ + bool m_input_interactive_p; + + /* See enum prompt_state's description. */ + enum prompt_state prompt_state = PROMPT_NEEDED; + + /* The fields below that start with "m_" are "private". They're + meant to be accessed through wrapper macros that make them look + like globals. */ + + /* The ui_file streams. */ + /* Normal results */ + struct ui_file *m_gdb_stdout; + /* Input stream */ + struct ui_file *m_gdb_stdin; + /* Serious error notifications */ + struct ui_file *m_gdb_stderr; + /* Log/debug/trace messages that should bypass normal stdout/stderr + filtering. */ + struct ui_file *m_gdb_stdlog; + + /* The current ui_out. */ + struct ui_out *m_current_uiout = nullptr; + + /* Register the UI's input file descriptor in the event loop. */ + void register_file_handler (); + + /* Unregister the UI's input file descriptor from the event loop. */ + void unregister_file_handler (); + + /* Return true if this UI's input fd is a tty. */ + bool input_interactive_p () const; +}; + +/* The main UI. This is the UI that is bound to stdin/stdout/stderr. + It always exists and is created automatically when GDB starts + up. */ +extern struct ui *main_ui; + +/* The current UI. */ +extern struct ui *current_ui; + +/* The list of all UIs. */ +extern struct ui *ui_list; + +/* State for SWITCH_THRU_ALL_UIS. */ +class switch_thru_all_uis +{ +public: + + switch_thru_all_uis () : m_iter (ui_list), m_save_ui (¤t_ui) + { + current_ui = ui_list; + } + + DISABLE_COPY_AND_ASSIGN (switch_thru_all_uis); + + /* If done iterating, return true; otherwise return false. */ + bool done () const + { + return m_iter == NULL; + } + + /* Move to the next UI, setting current_ui if iteration is not yet + complete. */ + void next () + { + m_iter = m_iter->next; + if (m_iter != NULL) + current_ui = m_iter; + } + + private: + + /* Used to iterate through the UIs. */ + struct ui *m_iter; + + /* Save and restore current_ui. */ + scoped_restore_tmpl m_save_ui; +}; + + /* Traverse through all UI, and switch the current UI to the one + being iterated. */ +#define SWITCH_THRU_ALL_UIS() \ + for (switch_thru_all_uis stau_state; !stau_state.done (); stau_state.next ()) + +using ui_range = next_range; + +/* An adapter that can be used to traverse over all UIs. */ +static inline +ui_range all_uis () +{ + return ui_range (ui_list); +} + +#endif /* UI_H */ diff --git a/gdb/utils.c b/gdb/utils.c index e10198accd0..3cd2287a52e 100644 --- a/gdb/utils.c +++ b/gdb/utils.c @@ -52,6 +52,7 @@ #include "gdbsupport/gdb_obstack.h" #include "gdbcore.h" #include "top.h" +#include "ui.h" #include "main.h" #include "solist.h" -- 2.30.2