#define _GNU_SOURCE #include #include #include #include #include #include #include #include #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_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 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) { 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 to continue, " "or q 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 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 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) { 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 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[] = { { "quit", 1, quit_command }, { "commands", 4, commands_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; while (isspace (*cs)) cs++; for (cmdp = cmds; cmdp < &cmds[LENGTH (cmds)]; cmdp++) 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_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"); } 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; mi_output *res; setbuf (stdout, NULL); 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_gdb_start_hook = start_hook; h = mi_connect_local (); if (h == NULL) fatal ("Cannot connect to GDB"); /* 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"); completion_entry_function_h = h; rl_completion_entry_function = completion_entry_function; rl_readline_name = "gdb"; if (gdb_show_bool (h, "history save")) { 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 (;;) { 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 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); } }