From ca7062be9c8832cfeacc3a46c2b368dc5de9f5a7 Mon Sep 17 00:00:00 2001 From: Vincent Povirk Date: Wed, 12 Mar 2008 10:08:23 -0400 Subject: [PATCH] add crashcatcher for generating logs after crashes --- .gitignore | 4 + SConstruct | 12 +- {loader => crashcatcher}/SConscript | 16 +- crashcatcher/crashcatcher.c | 436 +++++++++++++++++++++++++++ loader/main.c => crashcatcher/crashcatcher.h | 26 +- loader/SConscript | 9 +- loader/main.c | 33 +- 7 files changed, 505 insertions(+), 31 deletions(-) copy {loader => crashcatcher}/SConscript (74%) create mode 100644 crashcatcher/crashcatcher.c copy loader/main.c => crashcatcher/crashcatcher.h (65%) diff --git a/.gitignore b/.gitignore index 23ab108..92969b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ include/lucca-layouttile.h include/lucca-display.h include/lucca-core.h include/lucca-bin.h +include/crashcatcher.h bin/lucca-bin.o layout/lucca-layout.o layout/lucca-layouttile.o @@ -24,3 +25,6 @@ core/lucca-workarea.o core/lucca-graphicaltile.o loader/main loader/main.o +crashcatcher/crashcatcher.o +lucca_crash.log + diff --git a/SConstruct b/SConstruct index 75b50c7..71c4b6c 100644 --- a/SConstruct +++ b/SConstruct @@ -26,10 +26,13 @@ import subprocess project_env = Environment() Export('project_env') -if ARGUMENTS.get('debug', 0) not in [0, '0']: - project_env.MergeFlags('-g') +if ARGUMENTS.get('debug', 1) not in [0, '0']: + project_env.MergeFlags('-g -DLUCCA_DEBUG') + debug = True else: project_env.MergeFlags('-O') + debug = False +Export('debug') project_env.MergeFlags('-Wall -Wno-unused-function -Werror') @@ -60,6 +63,11 @@ Export('layout_files layout_config') display_files, display_config = SConscript('xwm/SConscript') Export('display_files display_config') +if debug: + #crashcatcher + cc_files, cc_config = SConscript('crashcatcher/SConscript') + Export('cc_files cc_config') + #Core module core_files, core_config = SConscript('core/SConscript') Export('core_files core_config') diff --git a/loader/SConscript b/crashcatcher/SConscript similarity index 74% copy from loader/SConscript copy to crashcatcher/SConscript index 5028293..f675d8c 100644 --- a/loader/SConscript +++ b/crashcatcher/SConscript @@ -23,11 +23,21 @@ Import('*') -#make a copy of the project's environment for building the loader +#copy or hard-link module-specific headers to include/ +project_env.Install(project_include, 'crashcatcher.h') + +#module users will run this function on their environment +def cc_config(env): + pass + +#make a copy of the project's environment for building the core module env = project_env.Clone() #configure the environment for the core module -core_config(env) +cc_config(env) + +#module users will use these object files +cc_files = [env.Object('crashcatcher.c')] -main_program = env.Program(['main.c']+core_files) +Return('cc_files cc_config') diff --git a/crashcatcher/crashcatcher.c b/crashcatcher/crashcatcher.c new file mode 100644 index 0000000..b39941b --- /dev/null +++ b/crashcatcher/crashcatcher.c @@ -0,0 +1,436 @@ +/* Copyright (c) 2008 Chris Robinson, Vincent Povirk + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *cc_logfile = NULL; + +static char respfile[256]; +static char buf[256]; +static char user_info_buf[1024]; + +static const struct { + const char *name; + int signum; +} signals[] = { + { "Segmentation fault", SIGSEGV }, + { "Illegal instruction", SIGILL }, + { "FPU exception", SIGFPE }, + { "System BUS error", SIGBUS }, + { NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + +static void gdb_info(pid_t pid) +{ + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd = mkstemp(respfile)) >= 0 && (f = fdopen(fd, "w"))) + { + fprintf(f, "signal SIGCHLD\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Bytes near %%eip:\"\n" + "x/x $eip-3\n" + "x/x $eip\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "backtrace full\n" +#if 0 /* This sorta works to print out the core, but is too slow and skips 0's.. */ + "shell echo \"\"\n" + "shell echo \"* Stack\"\n" + "set var $_sp = $esp\n" + "while $_sp <= $ebp - 12\n" + " printf \"%%08x: \", $_sp\n" + " set var $_i = $_sp\n" + " while $_i < $_sp + 16\n" + " printf \"%%08x \", {int} $_i\n" + " set $_i += 4\n" + " end\n" + " set var $_i = $_sp\n" + " while $_i < $_sp + 16\n" + " printf \"%%c\", {int} $_i\n" + " set ++$_i\n" + " end\n" + " set var $_sp += 16\n" + " printf \"\\n\"\n" + "end\n" + "if $_sp <= $ebp\n" + " printf \"%%08x: \", $esp\n" + " while $_sp <= $ebp\n" + " printf \"%%08x \", {int} $_i\n" + " set $_sp += 4\n" + " end\n" + " printf \"\\n\"\n" + "end\n" +#endif + "kill\n" + "quit\n"); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(buf, sizeof(buf), "gdb --quiet --batch --command=%s --pid=%i", respfile, pid); + printf("Executing: %s\n", buf); + fflush(stdout); + system(buf); + + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("Could not create gdb command file\n"); + } + fflush(stdout); +} + + +/* Generic system info */ +static void sys_info(void) +{ +#if (defined __unix__) + system("echo \"System: `uname -a`\""); +#endif + system("echo \"GCC version: `gcc -dumpversion`\""); + putchar('\n'); + fflush(stdout); +} + +void cc_crash_log(const char* errdesc) +{ + pid_t pid, dbg_pid; + FILE *f; + +#if 0 /* Do we need this? */ + /* Make sure the effective uid is the real uid */ + if (getuid() != geteuid()) + { + fprintf(stderr, "%s (signal %i)\ngetuid() does not match geteuid().\n", errdesc, signum); + _exit(-1); + } +#endif + + /* Create crash log file */ + if(cc_logfile) + { + f = fopen(cc_logfile, "w"); + if(!f) + { + fprintf(stderr, "Could not create %s following signal.\n", cc_logfile); + return; + } + } + else + f = stderr; + + /* Get current process id and fork off */ + pid = getpid(); + switch ((dbg_pid = fork())) + { + /* Error */ + case -1: + break; + + /* Child process, start writing an error log */ + case 0: + fprintf(stderr, "\n\n*** Fatal Error ***\n" + "%s\n", errdesc); + + /* Redirect shell output */ + close(STDOUT_FILENO); + dup2(fileno(f), STDOUT_FILENO); + + if(f != stderr) + { + fprintf(stderr, "\nGenerating %s and killing process %i, please wait... ", cc_logfile, pid); + fprintf(f, "*** Fatal Error ***\n" + "%s\n", errdesc); + } + fputc('\n', f); + fflush(f); + + /* Get info */ + sys_info(); + if(cc_user_info) + { + if(cc_user_info(user_info_buf, user_info_buf+sizeof(user_info_buf)) > 0) + { + fprintf(f, "%s\n", user_info_buf); + fflush(f); + } + } + gdb_info(pid); + +#if 0 /* Why won't this work? */ + if(ucontext) + { + unsigned char *ptr = ucontext->uc_stack.ss_sp; + size_t len; + + fprintf(f, "\n* Stack\n"); + for(len = ucontext->uc_stack.ss_size/4;len > 0; len -= 4) + { + fprintf(f, "0x%08x:", (int)ptr); + for(i = 0;i < ((len < 4) ? len : 4);++i) + { + fprintf(f, " %02x%02x%02x%02x", ptr[i*4 + 0], ptr[i*4 + 1], + ptr[i*4 + 2], ptr[i*4 + 3]); + } + fputc(' ', f); + fflush(f); + for(i = 0;i < ((len < 4) ? len : 4);++i) + { + fprintf(f, "%c", *(ptr++)); + fprintf(f, "%c", *(ptr++)); + fprintf(f, "%c", *(ptr++)); + fprintf(f, "%c", *(ptr++)); + } + fputc('\n', f); + fflush(f); + } + } +#endif + +#if 0 /* Create a pop-up window for the error; this might not make sense for a window manager crash. */ + if(f != stderr) + { + fclose(f); +#if (defined __unix__) + if(cc_logfile) + { + static char buf[1024]; + snprintf(buf, sizeof(buf), + "if which gxmessage &>/dev/null ; " + "then gxmessage -buttons \"Damn it:0\" -center -title \"Very Fatal Error\" -file %s ; " + "elif which xmessage &>/dev/null ; " + "then xmessage -buttons \"Damn it:0\" -center -file %s ; " + "fi", cc_logfile, cc_logfile); + system(buf); + } +#endif + } +#endif + + _exit(0); + + default: + /* Wait and let the child attach gdb */ + waitpid(dbg_pid, NULL, 0); + } +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ +#if 0 + ucontext_t *ucontext = (ucontext_t*)context; +#endif + const char *sigdesc = NULL; + char addrdesc[512]; + char errdesc[512]; + int i; + + /* Get the signal description */ + if(!siginfo) + { + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == signum) + { + sigdesc = signals[i].name; + break; + } + } + } + else + { + switch(signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == siginfo->si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == siginfo->si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == siginfo->si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == siginfo->si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + + if(!sigdesc) + { + /* Unknown signal, let the default handler deal with it */ + raise(signum); + return; + } + + if(siginfo) + snprintf(addrdesc, sizeof(addrdesc)/sizeof(char), " at Address: %p\n", siginfo->si_addr); + else + addrdesc[0] = '\0'; + + snprintf(errdesc, sizeof(errdesc)/sizeof(char), "%s (signal %i)%s\n", sigdesc, signum, addrdesc); + + cc_crash_log(errdesc); + + raise(signum); +} + +int cc_install_handlers(int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + int retval = 0; + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_SIGINFO; + + cc_logfile = logfile; + cc_user_info = user_info; + + while(num_signals--) + { + if(sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + + return retval; +} + diff --git a/loader/main.c b/crashcatcher/crashcatcher.h similarity index 65% copy from loader/main.c copy to crashcatcher/crashcatcher.h index aa7355e..5e16fcd 100644 --- a/loader/main.c +++ b/crashcatcher/crashcatcher.h @@ -22,29 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "lucca-display.h" -#include "lucca-core.h" +void cc_crash_log(const char* sigdesc); -int main(int argc, char** argv) { - LuccaDisplay* display; - LuccaApplication* application; - GError* err=NULL; - - gtk_init(&argc, &argv); - - display = g_object_new(LUCCA_TYPE_DISPLAY, NULL); - - application = g_object_new(LUCCA_TYPE_APPLICATION, "display", display, NULL); - - lucca_display_connect(display, gdk_display_get_default(), "LuccaWM", &err); - - if (err != NULL) { - g_printerr("%s\n", err->message); - return -1; - } - - gtk_main(); - - return 0; -} +int cc_install_handlers(int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)); diff --git a/loader/SConscript b/loader/SConscript index 5028293..caa148f 100644 --- a/loader/SConscript +++ b/loader/SConscript @@ -29,5 +29,12 @@ env = project_env.Clone() #configure the environment for the core module core_config(env) -main_program = env.Program(['main.c']+core_files) +#configure the environment for the cc module +if debug: + cc_config(env) + +if debug: + main_program = env.Program(['main.c']+core_files+cc_files) +else: + main_program = env.Program(['main.c']+core_files) diff --git a/loader/main.c b/loader/main.c index aa7355e..b76c5fe 100644 --- a/loader/main.c +++ b/loader/main.c @@ -25,6 +25,29 @@ OTHER DEALINGS IN THE SOFTWARE. #include "lucca-display.h" #include "lucca-core.h" +#ifdef LUCCA_DEBUG +#include +#include "crashcatcher.h" + +const int debug_signals[] = {SIGSEGV, SIGILL, SIGFPE, SIGBUS}; +#endif + +#include + +void on_hotkey(gpointer instance, gpointer user_data) { + g_print("%s\n", (char*)user_data); +} + +#ifdef LUCCA_DEBUG +void log_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { + char errdesc[512]; + + snprintf(errdesc, sizeof(errdesc)/sizeof(char), "Fatal error in %s: %s", log_domain, message); + + cc_crash_log(errdesc); +} +#endif + int main(int argc, char** argv) { LuccaDisplay* display; LuccaApplication* application; @@ -32,12 +55,20 @@ int main(int argc, char** argv) { gtk_init(&argc, &argv); +#ifdef LUCCA_DEBUG + /* debug fatal signals */ + cc_install_handlers(sizeof(debug_signals)/sizeof(int), (int*)debug_signals, "lucca_crash.log", NULL); + + /* debug fatal log messages */ + g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL, log_handler, NULL); +#endif + display = g_object_new(LUCCA_TYPE_DISPLAY, NULL); application = g_object_new(LUCCA_TYPE_APPLICATION, "display", display, NULL); lucca_display_connect(display, gdk_display_get_default(), "LuccaWM", &err); - + if (err != NULL) { g_printerr("%s\n", err->message); return -1; -- 2.11.4.GIT