#include <readline/history.h>
#include <assert.h>
#include <errno.h>
+#include <setjmp.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);
}
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)
{
return retval;
}
+static jmp_buf gdb_readline_jmp_buf;
+
+static void
+gdb_readline_sigint_handler (int signum)
+{
+ gdb_assert (signum == SIGINT);
+ longjmp (gdb_readline_jmp_buf, 1);
+}
+
+static char *
+gdb_readline (const char *prompt)
+{
+ char *retval;
+ sighandler_t saved_handler;
+ sigset_t mask;
+ int i;
+
+ i = sigprocmask (SIG_SETMASK, NULL, &mask);
+ assert (i == 0);
+ saved_handler = signal (SIGINT, gdb_readline_sigint_handler);
+ if (setjmp (gdb_readline_jmp_buf) != 0)
+ {
+ rl_free_line_state ();
+ rl_cleanup_after_signal ();
+
+ /* GDB prints this. */
+ puts ("Quit");
+
+ i = sigprocmask (SIG_SETMASK, &mask, NULL);
+ assert (i == 0);
+ }
+ retval = readline (prompt);
+ saved_handler = signal (SIGINT, saved_handler);
+ gdb_assert (saved_handler == gdb_readline_sigint_handler);
+ return retval;
+}
+
+static int console_cb_rows, console_cb_columns, console_cb_row, console_cb_column;
+static bool console_cb_drop;
+
static void
console_cb (const char *str, void *data)
{
- fputs (str, stdout);
+ const char *cs;
+
+ if (console_cb_drop)
+ return;
+ if (console_cb_rows == 0)
+ {
+ fputs (str, stdout);
+ return;
+ }
+
+ while (*str != '\0')
+ {
+ int columns;
+ size_t size;
+ char *answer;
+
+ cs = strchr (str, '\n');
+ if (cs == NULL)
+ cs = &str[strlen (str)];
+ columns = min (cs - str, console_cb_columns - console_cb_column);
+ if (columns > 0)
+ {
+ size = fwrite (str, 1, columns, stdout);
+ gdb_assert (size == columns);
+ str += columns;
+ console_cb_column += columns;
+ continue;
+ }
+ if (*str == '\n')
+ str++;
+ else if (console_cb_column < console_cb_columns)
+ gdb_assert_not_reached ("we should not get here");
+ putchar ('\n');
+ console_cb_row++;
+ console_cb_column = 0;
+ if (console_cb_row < console_cb_rows - 1)
+ continue;
+ answer = gdb_readline (_("---Type <return> to continue, "
+ "or q <return> to quit---"));
+ for (cs = answer; isspace (*cs); cs++);
+ if (*cs == 'q')
+ {
+ xfree (answer);
+ puts ("Quit");
+ console_cb_drop = true;
+ return;
+ }
+ xfree (answer);
+ console_cb_row = 0;
+ }
+}
+
+static int
+time_out_cb (void *data)
+{
+ fatal ("GDB response has timed out");
+ /* NOTREACHED */
+ return 0;
}
static ATTRIBUTE_UNUSED void
}
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;
+ gdb_assert (strchr (cmd, '\n') == 0);
mi_send (h, "-interpreter-exec console \"%s\"\n", cmd);
while (count > 0)
count--;
break;
case MI_CL_ERROR:
+ count--;
gdb_assert (rec->c->type == t_const);
puts (rec->c->v.cstr);
break;
}
}
+static void
+quit_command (mi_h *h, const char *cmd)
+{
+ exit (EXIT_SUCCESS);
+}
+
+static char *
+mi_escape (const char *cs)
+{
+ char *d, *retval = xmalloc (strlen (cs) * 2 + 1);
+
+ d = retval;
+ while (*cs)
+ {
+ if (*cs == '"' || *cs == '\\')
+ *d++ = '\\';
+ *d++ = *cs++;
+ }
+ *d = '\0';
+ return retval;
+}
+
+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
+commands_command_console_cb (const char *str, void *data)
+{
+ char **strptr = data;
+
+ gdb_assert (*strptr == NULL);
+ *strptr = xstrdup (str);
+}
+
+static void
+commands_command (mi_h *h, const char *cmd_param)
+{
+ char *cmd = xstrdup ("");
+ size_t cmd_len = 0;
+ mi_output *res;
+ char *bpnum = NULL;
+ int nesting;
+
+ if (*cmd_param == '\0')
+ {
+ mi_set_console_cb (h, commands_command_console_cb, &bpnum);
+ gdb_done (h, "output $bpnum");
+ mi_set_console_cb (h, console_cb, NULL);
+ gdb_assert (bpnum != NULL);
+ cmd_param = bpnum;
+ }
+
+ printf (_("Type commands for breakpoint(s) %s, one per line.\n"
+ "End with a line saying just \"end\".\n"), cmd_param);
+
+ nesting = 0;
+ while (nesting >= 0)
+ {
+ char *prompt, *data, *data2, *start, *end;
+ size_t data2_len;
+ int do_nest = 0;
+
+ prompt = xstrprintf ("%*s>", (int) nesting);
+ data = gdb_readline (prompt);
+ xfree (prompt);
+ if (data == NULL)
+ data = xstrdup ("end");
+
+ for (start = data; isspace (*start); start++);
+ for (end = data + strlen (data); end > start && isspace (end[-1]); end--);
+ data2 = xmalloc (end - start + 1);
+ memcpy (data2, start, end - start);
+ data2[end - start] = '\0';
+ if (strcmp (data2, "python") == 0)
+ do_nest = 1;
+ if (strcmp (data2, "end") == 0)
+ do_nest = -1;
+ for (end = data2; *end && !isspace (*end); end++);
+ *end = '\0';
+
+ /* Here is a bug in GDB, it does not recognize command shortcuts. */
+ if (strcmp (data2, "while") == 0 || strcmp (data2, "if") == 0
+ || strcmp (data2, "commands") == 0
+ || strcmp (data2, "while-stepping") == 0
+ || strcmp (data2, "stepping") == 0 || strcmp (data2, "ws") == 0)
+ do_nest = 1;
+ xfree (data2);
+
+ nesting += do_nest;
+ if (nesting < 0)
+ break;
+
+ data2 = mi_escape (data);
+ xfree (data);
+ data2_len = strlen (data2);
+ cmd = xrealloc (cmd, cmd_len + 2 + data2_len + 2);
+ cmd[cmd_len] = ' ';
+ cmd[cmd_len + 1] = '"';
+ memcpy (&cmd[cmd_len + 2], data2, data2_len);
+ cmd[cmd_len + 2 + data2_len] = '"';
+ cmd[cmd_len + 2 + data2_len + 1] = '\0';
+ cmd_len += 2 + data2_len + 1;
+ xfree (data2);
+ }
+
+ mi_send (h, "-break-commands %s%s\n", cmd_param, cmd);
+ xfree (cmd);
+ xfree (bpnum);
+
+ 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 const struct cmd
{
const char *name;
+ int min_shortcut;
void (*func) (mi_h *h, const char *cmd);
} cmds[] =
{
- { "q", quit_command },
- { "qu", quit_command },
- { "qui", quit_command },
- { "quit", quit_command },
+ { "quit", 1, quit_command },
+ { "commands", 4, commands_command },
};
static void
while (isalnum (*cs))
cs++;
end = cs;
+ while (isspace (*cs))
+ 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);
+ if (cmdp->min_shortcut <= end - start && end - start <= strlen (cmdp->name)
+ && strncmp (start, cmdp->name, end - start) == 0)
+ return cmdp->func (h, cs);
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);
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");
+}
+
+extern char **mi_gdb_argv;
+extern void (*mi_gdb_start_hook) (mi_h *h);
+
+static void
+start_hook (mi_h *h)
+{
+ on_exit (h_disconnect, h);
+ mi_set_console_cb (h, console_cb, NULL);
+ mi_set_time_out_cb (h, time_out_cb, NULL);
+
+// mi_set_to_gdb_cb (h, to_gdb_cb, NULL);
+// mi_set_from_gdb_cb (h, from_gdb_cb, NULL);
+}
+
int
main (int argc, char **argv)
{
mi_h *h;
- char *cmd;
- const char *ex[argc];
- unsigned ex_count = 0, ex_used = 0;
+ mi_output *res;
setbuf (stdout, NULL);
- while (*++argv != NULL)
- {
- if (strcmp (*argv, "-ex") == 0 && argv[1] != NULL)
- ex[ex_count++] = *++argv;
- else
- fatal ("Unknown parameter: %s", *argv);
- }
+ mi_gdb_argv = xmalloc ((argc + 2) * sizeof (*mi_gdb_argv));
+ memcpy (&mi_gdb_argv[2], &argv[1], argc * sizeof (*mi_gdb_argv));
+ mi_gdb_argv[0] = "gdb";
+ mi_gdb_argv[1] = "--interpreter=mi";
- mi_set_workaround (MI_PSYM_SEARCH, 0);
+ mi_gdb_start_hook = start_hook;
h = mi_connect_local ();
if (h == NULL)
fatal ("Cannot connect to GDB");
- on_exit (h_disconnect, h);
+ /* First eat the prompt. Then run empty command so that additional results
+ from -ex or -x during mi_connect_local are flushed. */
+ res = mi_get_response_blk (h);
+ gdb_assert (res == NULL);
+ default_command (h, "echo");
- 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);
+ completion_entry_function_h = h;
+ rl_completion_entry_function = completion_entry_function;
+ rl_readline_name = "gdb";
if (gdb_show_bool (h, "history save"))
{
for (;;)
{
- if (ex_used < ex_count)
- {
- cmd = xstrdup (ex[ex_used++]);
- printf ("(gdb) %s\n", cmd);
- }
+ char *prompt, *cmd;
+
+ prompt = gdb_show_string (h, "prompt");
+ cmd = gdb_readline (prompt);
+ xfree (prompt);
+
+ /* FIXME: -ex commands do not have pagination set. */
+ if (!gdb_show_bool (h, "pagination"))
+ console_cb_rows = 0;
else
- {
- cmd = readline ("(gdb) ");
- if (cmd == NULL)
- cmd = xstrdup ("quit");
- else
- add_history (cmd);
- }
+ rl_get_screen_size (&console_cb_rows, &console_cb_columns);
+ console_cb_drop = false;
+ console_cb_row = console_cb_column = 0;
+
+ if (cmd == NULL)
+ cmd = xstrdup ("quit");
+ else
+ add_history (cmd);
+
executecommand (h, cmd);
xfree (cmd);
}