#define _GNU_SOURCE #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 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], *arg_file = NULL, *arg_core = NULL, *arg_pid = NULL; unsigned ex_count = 0, ex_used = 0; setbuf (stdout, NULL); while (*++argv != NULL) { 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 (_("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); h = mi_connect_local (); 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); 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 (;;) { 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 = 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 = xstrdup ("quit"); else add_history (cmd); } executecommand (h, cmd); xfree (cmd); } }