[23/23] Add new command to create extra console/mi UI channels

Message ID 1454517838-7784-24-git-send-email-palves@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves Feb. 3, 2016, 4:43 p.m. UTC
  With all the previous plumbing in place, it's now easy to add a
command that actually creates a new console/mi UI.

The intended use case is to make it possible and easy for MI frontends
to provide a fully featured GDB console to users, with readline
support, command line editing, history, etc., just like if gdb was
started on the command line.  Currently MI frontends have to try to
implement all of that theirselves and make use of "-interpreter-exec
console ...", which is far from perfect.  If you ever tried Eclipse's
gdb console window, you'll know what I mean...

Instead of trying to multiplex console through MI, this command let's
just leverage all the built in readline/editing support already inside
gdb.

The plan is for the MI frontend to start GDB in regular console mode,
running inside a terminal emulator widget embedded in Eclipse (which
already exists, for supporting the shell widget; other frontends have
similar widgets), and then tell GDB to run a full MI interpreter on an
extra / separate side channel, independent of the console.

My original prototype planned to do things the other way around --
start GDB in MI mode, and then start an extra CLI console on separate
tty.  I handed over that prototype to Marc Khouzam @ Eclipse CDT, and
after experimentation and discussion, we ended up concluding that
starting GDB in CLI mode instead was both easier and actually also
supported an interesting use case -- connect an Eclipse frontend to a
GDB that is already running outside Eclipse.

The current usage is "new-ui <interpreter> <tty>".

E.g., on a terminal run this scriplet:

 $ cat gdb-client
 #!/bin/bash

 reset
 tty
 tail -f /dev/null

 $ gdb-client
 /dev/pts/15

Now run gdb on another terminal, and tell it to start a MI interpreter
on the tty of the other terminal:

 ...
 (gdb) new-ui mi /dev/pts/15
 New UI allocated

Now back to the the gdb-client terminal, we'll get an MI prompt, ready
for MI input:

 /dev/pts/15
 =thread-group-added,id="i1"
 (gdb)

You can also start a new UI running a CLI, with:

 (gdb) new-ui console /dev/pts/15

Though note that this console won't support readline command editing
(yet).  It works as if "set editing off" was entered.

Starting more than one MI UI is probably very broken, because there
are still globals in the MI code that should be made per UI.  Should
be trivial to fix, though much lower priority than getting one MI
working.

(The change to gdb_main to stop using catch_errors is necessary
because catch_errors references current_uiout, which expands to
current_ui->m_current_ui, which crashes, because current_ui is not
initialized yet at that point.  It didn't trigger earlier in the
series because before this patch, main_ui/current_ui always start out
non-NULL.  I should probably move this bit along with making main_ui
head allcoated earlier in the series.)
---
 gdb/event-top.c |  7 ++---
 gdb/interps.c   | 15 +++++++++-
 gdb/interps.h   |  5 ++++
 gdb/main.c      | 39 +++++++++-----------------
 gdb/top.c       | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/top.h       |  2 ++
 6 files changed, 122 insertions(+), 31 deletions(-)
  

Patch

diff --git a/gdb/event-top.c b/gdb/event-top.c
index 10c69f7..5da61cb 100644
--- a/gdb/event-top.c
+++ b/gdb/event-top.c
@@ -364,11 +364,10 @@  top_level_prompt (void)
   return xstrdup (prompt);
 }
 
-static struct ui main_ui_;
 
-struct ui *main_ui = &main_ui_;
-struct ui *current_ui = &main_ui_;
-struct ui *ui_list = &main_ui_;
+struct ui *main_ui;
+struct ui *current_ui;
+struct ui *ui_list;
 
 void
 restore_ui_cleanup (void *data)
diff --git a/gdb/interps.c b/gdb/interps.c
index 90d0de8..c9de292 100644
--- a/gdb/interps.c
+++ b/gdb/interps.c
@@ -291,6 +291,19 @@  interp_lookup (const char *name)
   return NULL;
 }
 
+void
+set_top_level_interpreter (const char *name)
+{
+  /* Find it.  */
+  struct interp *interp = interp_lookup (name);
+
+  if (interp == NULL)
+    error (_("Interpreter `%s' unrecognized"), name);
+  /* Install it.  */
+  if (!interp_set (interp, 1))
+    error (_("Interpreter `%s' failed to initialize."), name);
+}
+
 /* Returns the current interpreter.  */
 
 struct ui_out *
@@ -523,7 +536,7 @@  interpreter_exec_cmd (char *args, int from_tty)
 }
 
 /* List the possible interpreters which could complete the given text.  */
-static VEC (char_ptr) *
+VEC (char_ptr) *
 interpreter_completer (struct cmd_list_element *ignore,
 		       const char *text, const char *word)
 {
diff --git a/gdb/interps.h b/gdb/interps.h
index d33d632..0bad83d 100644
--- a/gdb/interps.h
+++ b/gdb/interps.h
@@ -86,6 +86,7 @@  extern struct interp *interp_new (const char *name,
 extern void interp_add (struct interp *interp);
 extern int interp_set (struct interp *interp, int top_level);
 extern struct interp *interp_lookup (const char *name);
+extern void set_top_level_interpreter (const char *name);
 extern struct ui_out *interp_ui_out (struct interp *interp);
 extern void *interp_data (struct interp *interp);
 extern const char *interp_name (struct interp *interp);
@@ -119,6 +120,10 @@  extern int interp_supports_command_editing (struct interp *interp);
    chance to e.g., print a prompt.  */
 extern void interp_pre_command_loop (struct interp *interp);
 
+extern VEC (char_ptr) *interpreter_completer (struct cmd_list_element *ignore,
+					      const char *text,
+					      const char *word);
+
 /* well-known interpreters */
 #define INTERP_CONSOLE		"console"
 #define INTERP_MI1             "mi1"
diff --git a/gdb/main.c b/gdb/main.c
index e107f60..a982752 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -441,7 +441,6 @@  DEF_VEC_O (cmdarg_s);
 static int
 captured_main (void *data)
 {
-  struct ui *ui = current_ui;
   struct captured_main_args *context = (struct captured_main_args *) data;
   int argc = context->argc;
   char **argv = context->argv;
@@ -513,26 +512,15 @@  captured_main (void *data)
   clear_quit_flag ();
   saved_command_line = (char *) xstrdup ("");
 
-  ui->instream = stdin;
-  ui->outstream = stdout;
-  ui->errstream = stderr;
-
-  ui->input_fd = fileno (stdin);
-
-  ui->prompt_state = PROMPT_NEEDED;
-
 #ifdef __MINGW32__
   /* Ensure stderr is unbuffered.  A Cygwin pty or pipe is implemented
      as a Windows pipe, and Windows buffers on pipes.  */
   setvbuf (stderr, NULL, _IONBF, BUFSIZ);
 #endif
 
-  gdb_stdout = stdio_fileopen (stdout);
-  gdb_stderr = stderr_fileopen (stderr);
+  main_ui = new_ui (stdin, stdout, stderr);
+  current_ui = main_ui;
 
-  gdb_stdlog = gdb_stderr;	/* for moment */
-  gdb_stdtarg = gdb_stderr;	/* for moment */
-  gdb_stdin = stdio_fileopen (stdin);
   gdb_stdtargerr = gdb_stderr;	/* for moment */
   gdb_stdtargin = gdb_stdin;	/* for moment */
 
@@ -974,17 +962,7 @@  captured_main (void *data)
 
   /* Install the default UI.  All the interpreters should have had a
      look at things by now.  Initialize the default interpreter.  */
-
-  {
-    /* Find it.  */
-    struct interp *interp = interp_lookup (interpreter_p);
-
-    if (interp == NULL)
-      error (_("Interpreter `%s' unrecognized"), interpreter_p);
-    /* Install it.  */
-    if (!interp_set (interp, 1))
-      error (_("Interpreter `%s' failed to initialize."), interpreter_p);
-  }
+  set_top_level_interpreter (interpreter_p);
 
   /* FIXME: cagney/2003-02-03: The big hack (part 2 of 2) that lets
      GDB retain the old MI1 interpreter startup behavior.  Output the
@@ -1173,7 +1151,16 @@  captured_main (void *data)
 int
 gdb_main (struct captured_main_args *args)
 {
-  catch_errors (captured_main, args, "", RETURN_MASK_ALL);
+  TRY
+    {
+      captured_main (args);
+    }
+  CATCH (ex, RETURN_MASK_ALL)
+    {
+      exception_print (gdb_stderr, ex);
+    }
+  END_CATCH
+
   /* The only way to end up here is by an error (normal exit is
      handled by quit_force()), hence always return an error status.  */
   return 1;
diff --git a/gdb/top.c b/gdb/top.c
index 079db94..874d160 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -245,6 +245,83 @@  void (*deprecated_call_command_hook) (struct cmd_list_element * c,
 
 void (*deprecated_context_hook) (int id);
 
+struct ui *
+new_ui (FILE *instream, FILE *outstream, FILE *errstream)
+{
+  struct ui *ui;
+
+  ui = XCNEW (struct ui);
+
+  ui->instream = instream;
+  ui->outstream = outstream;
+  ui->errstream = errstream;
+
+  ui->input_fd = fileno (ui->instream);
+
+  ui->m_gdb_stdin = stdio_fileopen (ui->instream);
+  ui->m_gdb_stdout = stdio_fileopen (ui->outstream);
+  ui->m_gdb_stderr = stderr_fileopen (ui->errstream);
+  ui->m_gdb_stdlog = ui->m_gdb_stderr;
+
+  ui->prompt_state = PROMPT_NEEDED;
+
+  ui->next = ui_list;
+  ui_list = ui;
+
+  return ui;
+}
+
+static void
+new_ui_command (char *args, int from_tty)
+{
+  struct ui *ui;
+  struct interp *interp;
+  FILE *stream;
+  int tty;
+  int res;
+  int argc;
+  char **argv;
+  const char *interpreter_name;
+  const char *tty_name;
+  struct cleanup *back_to;
+
+  argv = gdb_buildargv (args);
+  back_to = make_cleanup_freeargv (argv);
+
+  for (argc = 0; argv[argc] != NULL; argc++)
+    ;
+
+  if (argc < 2)
+    error (_("usage: new-ui <interpreter> <tty>"));
+
+  interpreter_name = argv[0];
+  tty_name = argv[1];
+
+  /* Open the specified terminal.  */
+  tty = open (tty_name, O_RDWR | O_NOCTTY);
+  if (tty < 0)
+    perror_with_name  (_("opening terminal failed"));
+
+  stream = fdopen (tty, "w+");
+
+  ui = new_ui (stream, stream, stream);
+
+  initialize_stdin_serial (ui);
+  ui->async = 1;
+
+  make_cleanup (restore_ui_cleanup, current_ui);
+  current_ui = ui;
+
+  set_top_level_interpreter (interpreter_name);
+
+  interp_pre_command_loop (top_level_interpreter ());
+
+  /* This restores the previous UI.  */
+  do_cleanups (back_to);
+
+  printf_unfiltered ("New UI allocated\n");
+}
+
 /* Handler for SIGHUP.  */
 
 #ifdef SIGHUP
@@ -1802,6 +1879,8 @@  set_history_filename (char *args, int from_tty, struct cmd_list_element *c)
 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);
@@ -1925,6 +2004,12 @@  When set, GDB uses the specified path to search for data files."),
                            set_gdb_datadir, show_gdb_datadir,
                            &setlist,
                            &showlist);
+
+  c = add_cmd ("new-ui", class_support, new_ui_command, _("\
+Create a new UI.  It takes two arguments:\n\
+The first argument is the name of the interpreter to run.\n\
+The second argument is the terminal the UI runs on.\n"), &cmdlist);
+  set_cmd_completer (c, interpreter_completer);
 }
 
 void
diff --git a/gdb/top.h b/gdb/top.h
index 6eb9cc6..72407b3 100644
--- a/gdb/top.h
+++ b/gdb/top.h
@@ -160,6 +160,8 @@  extern void switch_thru_all_uis_next (struct switch_thru_all_uis *state);
 #define ALL_UIS(UI)				\
   for (UI = ui_list; UI; UI = UI->next)		\
 
+extern struct ui *new_ui (FILE *instream, FILE *outstream, FILE *errstream);
+
 extern void restore_ui_cleanup (void *data);
 
 /* From top.c.  */