/* bug-buddy bug submitting program * * Copyright (C) Jacob Berkman * * Author: Jacob Berkman * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include "gdb-buddy.h" #if 0 #include #endif #define d(x) typedef struct { gint pid; GIOChannel *ioc; GString *stacktrace; gpointer user_data; GdbCallback callback; } GdbData; static void gdb_stop (GIOChannel *ioc, int pid) { if (!ioc) { d(g_message (_("gdb has already exited"))); return; } g_io_channel_shutdown (ioc, 1, NULL); kill (pid, SIGTERM); /* i don't think we need to SIGKILL it */ /*kill (druid_data.gdb_pid, SIGKILL);*/ waitpid (pid, NULL, 0); pid = 0; #if 0 /* sometimes gdb doesn't restart the old app... */ if (druid_data.app_pid) { kill (druid_data.app_pid, SIGCONT); druid_data.app_pid = 0; } #endif } static gboolean gdb_handle_input (GIOChannel *ioc, GIOCondition condition, gpointer data) { gboolean retval = FALSE; gchar buf[1024]; gsize len; GIOStatus io_status; GdbData *gdb_data = (GdbData *)data; while (gtk_events_pending ()) gtk_main_iteration (); gdb_try_read: io_status = g_io_channel_read_chars (ioc, buf, 1024, &len, NULL); switch (io_status) { case G_IO_STATUS_AGAIN: goto gdb_try_read; case G_IO_STATUS_ERROR: d(g_warning (_("Error on read... aborting"))); break; case G_IO_STATUS_NORMAL: retval = TRUE; break; default: break; } if (len > 0) { char *utftext; gsize localelen; gsize utflen; /* gdb charset is ISO-8859-1 */ utftext = g_convert_with_fallback (buf, len, "UTF-8", "ISO-8859-1", NULL, &localelen, &utflen, NULL); gdb_data->stacktrace = g_string_append (gdb_data->stacktrace, utftext); g_free (utftext); } if (!retval || io_status == G_IO_STATUS_EOF) { gdb_stop (ioc, gdb_data->pid); gdb_data->pid = 0; gdb_data->ioc = NULL; /* call the user specified callback function with the string containing * the stacktrace, and the user specified data */ (*(gdb_data->callback))(gdb_data->stacktrace->str, gdb_data->user_data); return FALSE; } return retval; } static void gdb_destroy (gpointer user_data) { GdbData *gdb_data; g_return_if_fail (user_data != NULL); d (g_print ("entering gdb_destroy\n")); gdb_data = (GdbData *)user_data; if (gdb_data->pid != 0 && gdb_data->ioc != NULL) { d (g_print ("stopping gdb, pid = %d\n", gdb_data->pid)); gdb_stop (gdb_data->ioc, gdb_data->pid); } if (gdb_data->pid != 0) gdb_data->pid = 0; if (gdb_data->ioc != NULL) gdb_data->ioc = NULL; if (gdb_data->stacktrace != NULL) g_string_free (gdb_data->stacktrace, TRUE); g_free (gdb_data); return; } GQuark gdb_buddy_error_quark (void) { return g_quark_from_static_string ("gdb_buddy_error"); } /** * @app: the executable name of the program that crashed * @pid: the process id of the application that crashed * @gdb_pid: the pid of the GDB process that is collecting the stacktrace * @user_data: a pointer that will be passed to the gdb_finish function * @gdb_finish: A callback function that is called after gdb finishes getting the stack trace * @err: if the function returns NULL, then *err is populated with a GError * * This function calls the gdb_finish callback function with backtrace obtained from gdb * and the pointer user_data, for a given process id and application name. If there is an error * during processing, *err will be populated with a GError message. * * This function returns the event source id that is added to the main loop, or zero if a failure * occurs in the function. If you need to terminate the application before the gdb_finish callback * has been called, then use g_source_remove(). This will cleanup the GIOChannel and stop any * running gdb processes. */ guint gdb_get_trace (const gchar *app, int pid, gpointer user_data, GdbCallback gdb_finish, GError **err) { char *s; const char *short_app; char *long_app; int gdb_pid; int fd; guint source_id; GIOChannel *ioc; GError *error = NULL; GdbData *gdb_data = NULL; char *args[] = { "gdb", "--batch", "--quiet", "--command=" BUDDY_DATADIR "/gdb-cmd", NULL, NULL, NULL }; g_return_val_if_fail (app != NULL, 0); g_return_val_if_fail (*app != '\0', 0); g_return_val_if_fail (pid != 0, 0); g_return_val_if_fail (gdb_finish != NULL, 0); g_return_val_if_fail (pid != 0, 0); g_return_val_if_fail (err == NULL || *err == NULL, 0); d (g_print ("app=%s\n", app)); /* apply a SIGCONT to the process */ kill (pid, SIGCONT); /* check for absolute or relative path */ if (app[0] == G_DIR_SEPARATOR) { long_app = g_strdup (app); short_app = strrchr (app, G_DIR_SEPARATOR) + 1; } else { /* app is a relative path... get absolute path in long_app */ long_app = g_find_program_in_path (app); if (!long_app) { /* Applets are not in path... */ long_app = g_strconcat(GNOMELIBEXECDIR,"/", app, NULL); } short_app = app; } if (!long_app) { g_set_error (err, GDB_BUDDY_ERROR, GDB_BUDDY_BINARY_NOT_FOUND, _("The binary file could not be found. Try using an absolute path.")); return 0; } args[0] = g_find_program_in_path ("gdb"); args[4] = long_app; if (args[0] == NULL) { d(g_message ("Path: %s", getenv ("PATH"))); g_free (long_app); g_set_error (err, GDB_BUDDY_ERROR, GDB_BUDDY_GDB_NOT_FOUND, _("GDB could not be found on your system. " "Debugging information will not be obtained.")); return 0; } d(g_message ("About to debug '%s'", long_app)); if (!g_file_test (BUDDY_DATADIR "/gdb-cmd", G_FILE_TEST_EXISTS)) { g_set_error (err, GDB_BUDDY_ERROR, GDB_BUDDY_GDB_CMD_NOT_FOUND, _("Could not find the gdb-cmd file.\n" "Please try reinstalling Bug Buddy.")); g_free (args[0]); g_free (long_app); return 0; } args[5] = g_strdup_printf ("%d", pid); if (!g_spawn_async_with_pipes (NULL, args, NULL, 0, NULL, NULL, &gdb_pid, NULL, &fd, NULL, &error)) { g_set_error (err, GDB_BUDDY_ERROR, GDB_BUDDY_GDB_ERROR, _("There was an error running gdb:\n\n%s"), error->message); g_error_free (error); g_free (args[0]); g_free (args[5]); g_free (long_app); return 0; } ioc = g_io_channel_unix_new (fd); g_io_channel_set_encoding (ioc, NULL, NULL); g_io_channel_set_flags (ioc, G_IO_FLAG_NONBLOCK, &error); gdb_data = g_new0 (GdbData, 1); s = g_strdup_printf ("Backtrace was generated from '%s'\n\n", long_app); gdb_data->stacktrace = g_string_new (s); g_free (s); gdb_data->pid = gdb_pid; gdb_data->ioc = ioc; gdb_data->user_data = user_data; gdb_data->callback = gdb_finish; source_id = g_io_add_watch_full (ioc, 100, G_IO_IN | G_IO_HUP, gdb_handle_input, gdb_data, gdb_destroy); g_io_channel_unref (ioc); g_free (args[0]); g_free (args[5]); g_free (long_app); return source_id; }