/* bug-buddy bug submitting program
*
* Copyright (C) Jacob Berkman
*
* Author: Jacob Berkman <jacob@bug-buddy.org>
*
* 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 <config.h>
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtkmain.h>
#include "gdb-buddy.h"
#if 0
#include <libart_lgpl/libart.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1