Support file/core/pid arguments.
[gdbmicli.git] / main.c
diff --git a/main.c b/main.c
index 880c1fe..040c6f1 100644 (file)
--- a/main.c
+++ b/main.c
+#define _GNU_SOURCE
 #include <stdio.h>
 #include <stdarg.h>
+#include <signal.h>
 #include <readline/readline.h>
+#include <readline/history.h>
+#include <assert.h>
+#include <errno.h>
 #include "ansidecl.h"
 #include "mi_gdb.h"
 
+#define fatal(fmt...) fatal_func (__FILE__, __LINE__, __PRETTY_FUNCTION__, fmt)
+#define gdb_assert assert
+#define gdb_assert_not_reached(msg) fatal ("%s", (msg))
+#define _(x) x
+
 static ATTRIBUTE_NORETURN void
-fatal (const char *fmt, ...)
+fatal_func (const char *file, int line, const char *func, const char *fmt, ...)
 {
   va_list ap;
 
+  fprintf (stderr, "%s:%d:%s:", file, line, func);
   va_start (ap, fmt);
   vfprintf (stderr, fmt, ap);
   va_end (ap);
+  fputc ('\n', stderr);
+raise (SIGABRT);
   exit (EXIT_FAILURE);
 }
 
+static char *
+xstrdup (const char *s)
+{
+  char *retval;
+
+  gdb_assert (s != NULL);
+  retval = strdup (s);
+  gdb_assert (retval != NULL);
+  return retval;
+}
+
+static void *
+xmalloc (size_t size)
+{
+  void *p;
+
+  gdb_assert (size != 0);
+  p = malloc (size);
+  gdb_assert (p != NULL);
+  return p;
+}
+
+static void *
+xrealloc (void *p, size_t size)
+{
+  if (p == NULL)
+    return xmalloc (size);
+  gdb_assert (size != 0);
+  p = realloc (p, size);
+  gdb_assert (p != NULL);
+  return p;
+}
+
+static void
+xfree (void *p)
+{
+  free (p);
+}
+
+static char *
+xstrprintf (const char *fmt, ...)
+{
+  va_list ap;
+  char *retval;
+  int i;
+
+  va_start (ap, fmt);
+  i = vasprintf (&retval, fmt, ap);
+  va_end (ap);
+  gdb_assert (i > 0);
+  gdb_assert (i == strlen (retval));
+  return retval;
+}
+
 static void
 console_cb (const char *str, void *data)
 {
   fputs (str, stdout);
 }
 
+static ATTRIBUTE_UNUSED void
+to_gdb_cb (const char *str, void *data)
+{
+  printf ("to_gdb: %s\n", str);
+}
+
+static ATTRIBUTE_UNUSED void
+from_gdb_cb (const char *str, void *data)
+{
+  printf ("from_gdb: %s\n", str);
+}
+
+static void
+h_disconnect (int rc, void *arg)
+{
+  mi_h *h = arg;
+
+  mi_disconnect (h);
+}
+
+static void
+history_save (int rc, void *arg)
+{
+  const char *history_filename = arg;
+
+  write_history (history_filename);
+}
+
+static void
+quit_command (mi_h *h, const char *cmd)
+{
+  exit (EXIT_SUCCESS);
+}
+
+static void
+default_command (mi_h *h, const char *cmd)
+{
+  int count = 1;
+
+  mi_send (h, "-interpreter-exec console \"%s\"\n", cmd);
+
+  while (count > 0)
+    {
+      mi_output *rec, *res;
+
+      res = mi_get_response_blk (h);
+      gdb_assert (res != NULL);
+
+      for (rec = res; rec != NULL; rec = rec->next)
+       switch (rec->tclass)
+       {
+         case MI_CL_DONE:
+           count--;
+           break;
+         case MI_CL_RUNNING:
+           /* We do not get MI_CL_DONE here, wait for MI_CL_STOPPED.  */
+           break;
+         case MI_CL_STOPPED:
+           count--;
+           break;
+         case MI_CL_ERROR:
+           count--;
+           gdb_assert (rec->c->type == t_const);
+           puts (rec->c->v.cstr);
+           break;
+         default:
+           fatal ("mi_get_rrecord == MI_CL_??? (%d)", rec->tclass);
+       }
+
+      mi_free_output (res);
+    }
+}
+
+static const struct cmd
+{
+  const char *name;
+  void (*func) (mi_h *h, const char *cmd);
+} cmds[] =
+{
+  { "q", quit_command },
+  { "qu", quit_command },
+  { "qui", quit_command },
+  { "quit", quit_command },
+};
+
+static void
+executecommand (mi_h *h, const char *cmd)
+{
+  const char *start, *end, *cs;
+  const struct cmd *cmdp;
+
+  cs = cmd;
+  while (isspace (*cs))
+    cs++;
+  start = cs;
+  while (isalnum (*cs))
+    cs++;
+  end = cs;
+
+  for (cmdp = cmds; cmdp < &cmds[LENGTH (cmds)]; cmdp++)
+    if (strlen (cmdp->name) == end - start
+        && strncmp (cmd, cmdp->name, end - start) == 0)
+      return cmdp->func (h, cmd);
+
+  return default_command (h, cmd);
+}
+
+static void
+gdb_done (mi_h *h, const char *command)
+{
+  mi_output *res;
+
+  mi_send (h, "%s\n", command);
+  res = mi_get_response_blk (h);
+  gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+             && res->c == NULL);
+  mi_free_output (res);
+}
+
+static void
+gdb_set_string (mi_h *h, const char *setting, const char *value)
+{
+  char *cmd = xstrprintf ("-gdb-set %s %s", setting, value);
+
+  gdb_done (h, cmd);
+  xfree (cmd);
+}
+
+static void
+gdb_set_bool (mi_h *h, const char *setting, bool value)
+{
+  gdb_set_string (h, setting, value ? "on" : "off");
+}
+
+static char *
+gdb_show_string (mi_h *h, const char *setting)
+{
+  mi_output *res;
+  char *retval;
+
+  mi_send (h, "-gdb-show %s\n", setting);
+  res = mi_get_response_blk (h);
+  gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+             && res->c != NULL && res->c->next == NULL
+             && res->c->type == t_const && strcmp (res->c->var, "value") == 0);
+  retval = xstrdup (res->c->v.cstr);
+  mi_free_output (res);
+  return retval;
+}
+
+static int
+gdb_show_int (mi_h *h, const char *setting)
+{
+  char *string = gdb_show_string (h, setting);
+  long l;
+  int retval;
+  char *end;
+
+  errno = 0;
+  retval = l = strtol (string, &end, 10);
+  gdb_assert (errno == 0 && (end == NULL || *end == '\0') && retval == l);
+  xfree (string);
+  return retval;
+}
+
+static bool
+gdb_show_bool (mi_h *h, const char *setting)
+{
+  char *string = gdb_show_string (h, setting);
+  bool retval;
+
+  retval = strcmp (string, "on") == 0;
+  gdb_assert (retval || strcmp (string, "off") == 0);
+  xfree (string);
+  return retval;
+}
+
+static mi_h *completion_entry_function_h;
+
+static char **completion_entry_function_data;
+static size_t completion_entry_function_data_used;
+static size_t completion_entry_function_data_allocated;
+
+static void
+completion_entry_function_console_cb (const char *str, void *data)
+{
+  int line_start = (intptr_t) data;
+  const char *cs;
+  char *s;
+
+  if (completion_entry_function_data_used
+      == completion_entry_function_data_allocated)
+    {
+      if (completion_entry_function_data_allocated > 0)
+       completion_entry_function_data_allocated *= 2;
+      else
+       completion_entry_function_data_allocated = 0x100;
+      completion_entry_function_data = xrealloc (completion_entry_function_data,
+                                 (sizeof (*completion_entry_function_data)
+                                  * completion_entry_function_data_allocated));
+    }
+
+  cs = strchr (str, '\n');
+  if (cs == NULL || cs[1] != '\0')
+    fatal ("Invalid GDB data: %s", str);
+
+  if (strncmp (rl_line_buffer, str, rl_point) != 0)
+    fatal ("Completion GDB data do not match, have \"%.*s\", got \"%.*s\".",
+          (int) rl_point, rl_line_buffer, (int) (cs - str), str);
+
+  s = xmalloc (cs - str - line_start + 1);
+  memcpy (s, &str[line_start], cs - str - line_start);
+  s[cs - str - line_start] = '\0';
+  completion_entry_function_data[completion_entry_function_data_used++] = s;
+}
+
+static char *
+completion_entry_function (const char *text, int matches)
+{
+  mi_h *h = completion_entry_function_h;
+
+  gdb_assert (matches >= 0);
+  if (matches == 0)
+    {
+      mi_output *res;
+      int line_start;
+
+      while (completion_entry_function_data_used)
+       xfree (completion_entry_function_data
+              [--completion_entry_function_data_used]);
+      xfree (completion_entry_function_data);
+      completion_entry_function_data = NULL;
+      completion_entry_function_data_used = 0;
+      completion_entry_function_data_allocated = 0;
+
+      gdb_assert (rl_point >= 0);
+      gdb_assert (strlen (rl_line_buffer) >= rl_point);
+      gdb_assert (strlen (text) <= rl_point);
+      line_start = rl_point - strlen (text);
+      gdb_assert (strncmp (text, &rl_line_buffer[line_start],
+                          strlen (text)) == 0);
+      mi_send (h, "-interpreter-exec console \"complete %.*s\"\n",
+              (int) rl_point, rl_line_buffer);
+
+      mi_set_console_cb (h, completion_entry_function_console_cb,
+                        (void *) (intptr_t) line_start);
+      res = mi_get_response_blk (h);
+      gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+                 && res->c == NULL);
+      mi_free_output (res);
+      mi_set_console_cb (h, console_cb, NULL);
+    }
+
+  if (matches < completion_entry_function_data_used)
+    return xstrdup (completion_entry_function_data[matches]);
+  else if (matches == completion_entry_function_data_used)
+    return NULL;
+  else
+    gdb_assert_not_reached ("too many matches");
+}
+
 int
 main (int argc, char **argv)
 {
   mi_h *h;
   char *cmd;
-  const char *ex[argc];
+  const char *ex[argc], *arg_file = NULL, *arg_core = NULL, *arg_pid = NULL;
   unsigned ex_count = 0, ex_used = 0;
 
   setbuf (stdout, NULL);
@@ -35,9 +363,32 @@ main (int argc, char **argv)
     {
       if (strcmp (*argv, "-ex") == 0 && argv[1] != NULL)
        ex[ex_count++] = *++argv;
+      else if (strcmp (*argv, "-c") == 0 && argv[1] != NULL)
+       arg_core = *++argv;
+      else if (strncmp (*argv, "--core=", strlen ("--core=")) == 0)
+       arg_core = xstrdup (&(*argv)[strlen ("--core=")]);
+      else if (strcmp (*argv, "-p") == 0 && argv[1] != NULL)
+       arg_pid = *++argv;
+      else if (strncmp (*argv, "--pid=", strlen ("--pid=")) == 0)
+       arg_pid = xstrdup (&(*argv)[strlen ("--pid=")]);
+      else if (arg_file == NULL)
+       arg_file = *argv;
+      else if (arg_core == NULL && arg_pid == NULL)
+       {
+         char *end = NULL;
+
+         strtol (*argv, &end, 0);
+         if (isdigit ((*argv)[0]) && (end == NULL || *end == '\0'))
+           arg_pid = *argv;
+         else
+           arg_core = *argv;
+       }
       else
-       fatal ("Unknown parameter: %s", *argv);
+       fatal (_("Excess command line argument: %s"), *argv);
     }
+  if (arg_core != NULL && arg_pid != NULL)
+    fatal (_("Can't attach to process and specify a core file "
+            "at the same time."));
 
   mi_set_workaround (MI_PSYM_SEARCH, 0);
 
@@ -45,37 +396,56 @@ main (int argc, char **argv)
   if (h == NULL)
     fatal ("Cannot connect to GDB");
 
+  on_exit (h_disconnect, h);
+
   mi_set_console_cb (h, console_cb, NULL);
+//  mi_set_to_gdb_cb (h, to_gdb_cb, NULL);
+//  mi_set_from_gdb_cb (h, from_gdb_cb, NULL);
 
-  for (;;)
+  completion_entry_function_h = h;
+  rl_completion_entry_function = completion_entry_function;
+  rl_readline_name = "gdb";        
+
+  if (gdb_show_bool (h, "history save"))
     {
-      mi_output *rec, *res;
+      int history_size = gdb_show_int (h, "history size");
+      char *history_filename = gdb_show_string (h, "history filename");
+
+      gdb_set_bool (h, "history save", false);
+      stifle_history (history_size);
+      read_history (history_filename);
+      on_exit (history_save, history_filename);
+      /* Do not free HISTORY_FILENAME.  */
+    }
 
+  for (;;)
+    {
       if (ex_used < ex_count)
+       cmd = xstrdup (ex[ex_used++]);
+      else if (arg_file != NULL)
+       {
+         cmd = xstrprintf ("file %s", arg_file);
+         arg_file = NULL;
+       }
+      else if (arg_core != NULL)
        {
-         cmd = strdup (ex[ex_used++]);
-         printf ("(gdb) %s\n", cmd);
+         cmd = xstrprintf ("core-file %s", arg_core);
+         arg_core = NULL;
+       }
+      else if (arg_pid != NULL)
+       {
+         cmd = xstrprintf ("attach %s", arg_pid);
+         arg_pid = NULL;
        }
       else
        {
          cmd = readline ("(gdb) ");
          if (cmd == NULL)
-           cmd = strdup ("quit");
+           cmd = xstrdup ("quit");
+         else
+           add_history (cmd);
        }
-
-      mi_send (h, "-interpreter-exec console \"%s\"\n", cmd);
-      free (cmd);
-
-      res = mi_get_response_blk (h);
-      if (res == NULL)
-       fatal ("mi_get_response_blk == NULL");
-
-      rec = mi_get_rrecord (res);
-      if (rec == NULL)
-       fatal ("mi_get_rrecord == NULL");
-      if (rec->tclass != MI_CL_DONE)
-       fatal ("mi_get_rrecord != MI_CL_DONE");
-
-      mi_free_output (res);
+      executecommand (h, cmd);
+      xfree (cmd);
     }
 }