+#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 gdb_assert assert
+
static ATTRIBUTE_NORETURN void
fatal (const char *fmt, ...)
{
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
+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
+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:
+ 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;
+}
+
int
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 (;;)
+ 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 = strdup (ex[ex_used++]);
+ cmd = xstrdup (ex[ex_used++]);
printf ("(gdb) %s\n", cmd);
}
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);
}
}