diff --git a/stepmania/src/archutils/Unix/CrashHandler.cc b/stepmania/src/archutils/Unix/CrashHandler.cc new file mode 100644 index 0000000000..e956d5419b --- /dev/null +++ b/stepmania/src/archutils/Unix/CrashHandler.cc @@ -0,0 +1,428 @@ +#include "global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StepMania.h" /* for g_argv */ + +#include "RageLog.h" /* for RageLog::GetAdditionalLog, etc, only */ +#include + +#include +#include + +#include "CrashHandler.h" +#include "CrashHandlerInternal.h" + +static void safe_print(int fd, ...) +{ + va_list ap; + va_start(ap, fd); + + while(1) + { + const char *p = va_arg(ap, const char *); + if(p == NULL) + break; + write(fd, p, strlen(p)); + } + va_end(ap); +} + + +static void spawn_child_process(int from_parent) +{ + /* We need to re-exec ourself, to get a clean process. Close all + * FDs except for 0-2 and to_child, and then assign to_child to 3. */ + for(int fd = 3; fd < 1024; ++fd) + if(fd != from_parent) close(fd); + + if(from_parent != 3) + { + int ret = dup2(from_parent, 3); + close(from_parent); + } + + execl(g_argv[0], g_argv[0], CHILD_MAGIC_PARAMETER, NULL); + + /* If we got here, the exec failed. */ + safe_print(fileno(stderr), "Crash handler execl(", g_argv[0], ") failed: ", strerror(errno), "\n", NULL); + _exit(1); +} + + +static void parent_write(int to_child, const void *p, size_t size) +{ + int ret = write(to_child, p, size); + if(ret != size) + { + safe_print(fileno(stderr), "Unexpected write() result (", strerror(errno), ")\n", NULL); + exit(1); + } +} + +static void parent_process( int to_child, void **BacktracePointers, int SignalReceived ) +{ + /* 1. Write the backtrace pointers. */ + parent_write(to_child, BacktracePointers, sizeof(BacktracePointers)); + + /* 2. Write the signal. */ + parent_write(to_child, &SignalReceived, sizeof(SignalReceived)); + + /* 3. Write info. */ + const char *p = RageLog::GetInfo(); + int size = strlen(p)+1; + parent_write(to_child, &size, sizeof(size)); + parent_write(to_child, p, size); + + /* 4. Write AdditionalLog. */ + p = RageLog::GetAdditionalLog(); + size = strlen(p)+1; + parent_write(to_child, &size, sizeof(size)); + parent_write(to_child, p, size); + + /* 5. Write RecentLogs. */ + int cnt = 0; + const char *ps[1024]; + while( cnt < 1024 && (ps[cnt] = RageLog::GetRecentLog( cnt )) != NULL ) + ++cnt; + + parent_write(to_child, &cnt, sizeof(cnt)); + for( int i = 0; i < cnt; ++i ) + { + size = strlen(ps[i])+1; + parent_write(to_child, &size, sizeof(size)); + parent_write(to_child, ps[i], size); + } + + close(to_child); +} + + +/* The parent process is the crashed process. It'll send data to the + * child, who will do stuff with it. The parent then waits for the + * child to quit, and exits. + * + * We can do whatever fancy things we want in the child process. However, + * let's not open any windows until we at least try to shut down OpenGL, + * since it may cause problems. We don't want to try to shut down OpenGL + * until we've sent all of our data, since it might explode. + * + * So, first fork off the error reporting child, send data to it, shut down + * OpenGL, close the socket and wait for the child to shut down. + * + * The child reads the data from the parent, waits for the socket to close + * (EOF), and it's then free to open windows and stuff. + * + * XXX: make sure the parent dying doesn't take out the child + */ + +#if BACKTRACE_METHOD == X86_LINUX +/* The x86 backtrace() in glibc doesn't make any effort at all to decode + * signal trampolines. The result is that it doesn't properly show the + * function that actually caused the signal--which is the most important + * one! So, we have to do it all ourself. */ +static const char *itoa(unsigned n) +{ + static char ret[32]; + char *p = ret; + for( int div = 1000000000; div > 0; div /= 10 ) + { + *p++ = (n / div) + '0'; + n %= div; + } + *p = 0; + p = ret; + while( p[0] == '0' && p[1] ) + ++p; + return p; +} + +static int xtoi( const char *hex ) +{ + int ret = 0; + while( 1 ) + { + int val = -1; + if( *hex >= '0' && *hex <= '9' ) + val = *hex - '0'; + else if( *hex >= 'A' && *hex <= 'F' ) + val = *hex - 'A' + 10; + else if( *hex >= 'a' && *hex <= 'f' ) + val = *hex - 'a' + 10; + else + break; + hex++; + + ret *= 16; + ret += val; + } + return ret; +} + +static int get_readable_ranges( const void **starts, const void **ends, int size ) +{ + char path[PATH_MAX] = "/proc/"; + strcat( path, itoa(getpid()) ); + strcat( path, "/maps" ); + + int fd = open(path, O_RDONLY); + if( fd == -1 ) + return false; + + /* Format: + * + * 402dd000-402de000 rw-p 00010000 03:03 16815669 /lib/libnsl-2.3.1.so + * or + * bfffb000-c0000000 rwxp ffffc000 00:00 0 + * + * Look for the range that includes the stack pointer. */ + char file[1024]; + int file_used = 0; + bool eof = false; + int got = 0; + while(!eof && got < size-1 ) + { + int ret = read( fd, file+file_used, sizeof(file) - file_used); + if( ret < int(sizeof(file)) - file_used) + eof = true; + + file_used += ret; + + /* Parse lines. */ + while( got < size ) + { + char *p = (char *) memchr( file, '\n', file_used ); + if( p == NULL ) + break; + *p++ = 0; + + /* Search for the hyphen. */ + char *hyphen = strchr( file, '-' ); + if( hyphen == NULL ) + { + /* Parse error. */ + continue; + } + + + /* Search for the space. */ + char *space = strchr( hyphen, ' ' ); + if( space == NULL ) + { + /* Parse error. */ + continue; + } + + /* " rwxp". If space[1] isn't 'r', then the block isn't readable. */ + if( strlen(space) < 2 || space[1] != 'r' ) + continue; + + *starts++ = (const void *) xtoi( file ); + *ends++ = (const void *) xtoi( hyphen+1 ); + + file_used -= p-file; + memmove(file, p, file_used); + } + + if( file_used == sizeof(file) ) + { + /* Line longer than the buffer. Weird; bail. */ + close(fd); + return false; + } + } + + *starts++ = NULL; + *ends++ = NULL; + + return got; +} + +/* If the address is readable (eg. reading it won't cause a segfault), return + * the block it's in. Otherwise, return -1. */ +static int find_address( void *p, const void **starts, const void **ends ) +{ + for( int i = 0; starts[i]; ++i ) + { + /* Found it. */ + if( starts[i] <= p && p <= ends[i] ) + return i; + } + + return -1; +} + +/* backtrace() for x86 Linux, tested with kernel 2.4.18, glibc 2.3.1. */ +static void do_backtrace( void **buf, size_t size, bool ignore_before_sig = true ) +{ + /* Make room for a NULL terminator. */ + --size; + + /* Read /proc/pid/maps to find the address range of the stack. */ + const void *readable_begin[1024], *readable_end[1024]; + get_readable_ranges( readable_begin, readable_end, 1024 ); + + /* Find the stack memory block. */ + register void *esp __asm__ ("esp"); + int stack_block=find_address(esp, readable_begin, readable_end); + + /* This matches the layout of the stack. The frame pointer makes the + * stack a linked list. */ + struct StackFrame + { + StackFrame *link; + char *return_address; + + /* These are only relevant if the frame is a signal trampoline. */ + int signal; + sigcontext sig; + }; + + struct StackFrame *frame; + asm("mov %%ebp, %%eax": "=a" (frame) ); + + unsigned i=0; + /* If ignore_before_sig is true, don't return stack frames before we find a signal trampoline. */ + bool got_signal_return = !ignore_before_sig; + while( i < size ) + { + /* Make sure that this frame address is readable, and is on the stack. */ + int val = find_address(frame, readable_begin, readable_end); + if( val != stack_block ) + break; + + if( frame->return_address == (void*) CrashSignalHandler ) + continue; + + /* + * The stack return stub is: + * + * 0x401139d8 : pop %eax + * 0x401139d9 : mov $0x77,%eax + * 0x401139de : int $0x80 + * + * This will be different if using realtime signals, as will the stack layout. + * + * If we detect this, it means this is a stack frame returning from a signal. + * Ignore the return_address and use the sigcontext instead. + */ + const char comp[] = { 0x58, 0xb8, 0x77, 0x0, 0x0, 0x0, 0xcd, 0x80, 0x55, 0x89 }; + bool is_signal_return = true; + if( find_address(frame->return_address, readable_begin, readable_end) == -1) + is_signal_return = false; + + for( unsigned pos = 0; is_signal_return && pos < sizeof(comp); ++pos ) + if(frame->return_address[pos] != comp[pos]) + is_signal_return = false; + + void *to_add = NULL; + if( is_signal_return ) + { + got_signal_return = true; + to_add = (void *) frame->sig.eip; + } + /* Ignore stack frames before the signal. */ + else if( got_signal_return ) + to_add = frame->return_address; + + if( to_add ) + buf[i++] = to_add; + + /* frame always goes down. Make sure it doesn't go up; that could + * cause an infinite loop. */ + if( frame->link <= frame ) + break; + + frame = frame->link; + } + + buf[i] = NULL; +} +#elif BACKTRACE_METHOD == BACKTRACE +static void do_backtrace(void **buf, size_t size) +{ + int retsize = backtrace( buf, size-1 ); + + /* Remove any NULL entries. We want to null-terminate the list, and null entries are useless. */ + for( int i = retsize-1; i >= 0; --i ) + { + if( buf[i] != NULL ) + continue; + + memmove( &buf[i], &buf[i]+1, retsize-i-1 ); + } + + buf[retsize] = NULL; +} +#else +#error BACKTRACE_METHOD required +#endif + + +void CrashSignalHandler( int signal ) +{ + /* Don't dump a debug file if the user just hit ^C. */ + if( signal == SIGINT ) + return; + + static bool received = false; + + if( received ) + { + /* We've received a second signal. This may mean that another thread + * crashed before we stopped it, or it may mean that the crash handler + * crashed. */ + const char *str = "Oops! Fatal signal received while still in the crash handler\n"; + write( fileno(stdout), str, strlen(str) ); + _exit(1); + } + received = true; + + RageThread::HaltAllThreads(); + + /* Do this early, so functions called below don't end up on the backtrace. */ + void *BacktracePointers[BACKTRACE_MAX_SIZE]; + do_backtrace(BacktracePointers, BACKTRACE_MAX_SIZE); + + /* We need to be very careful, since we're under crash conditions. Let's fork + * a process and exec ourself to get a clean environment to work in. */ + const char *my_fn = g_argv[0]; + + int fds[2]; + if(pipe(fds) != 0) + { + safe_print(fileno(stderr), "Crash handler pipe() failed: ", strerror(errno), "\n", NULL); + exit(1); + } + + pid_t pid = fork(); + if(pid == -1) + { + safe_print(fileno(stderr), "Crash handler fork() failed: ", strerror(errno), "\n", NULL); + exit(1); + } + + if(pid == 0) + { + close(fds[1]); + spawn_child_process(fds[0]); + } + else + { + close(fds[0]); + parent_process( fds[1], BacktracePointers, signal ); + waitpid(pid, NULL, 0); + /* Now that we're done, actually kill all threads. */ + RageThread::HaltAllThreads( true ); + } +} + + diff --git a/stepmania/src/archutils/Unix/CrashHandler.h b/stepmania/src/archutils/Unix/CrashHandler.h new file mode 100644 index 0000000000..7f2fce7edb --- /dev/null +++ b/stepmania/src/archutils/Unix/CrashHandler.h @@ -0,0 +1,7 @@ +#ifndef CRASH_HANDLER_H + +void CrashSignalHandler(int signal); +void CrashHandlerHandleArgs(); + +#endif + diff --git a/stepmania/src/archutils/Unix/CrashHandlerChild.cc b/stepmania/src/archutils/Unix/CrashHandlerChild.cc new file mode 100644 index 0000000000..b12f2e14f6 --- /dev/null +++ b/stepmania/src/archutils/Unix/CrashHandlerChild.cc @@ -0,0 +1,286 @@ +/* for dladdr: */ +#define __USE_GNU +#include "global.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "StepMania.h" /* for g_argv */ +#include "RageUtil.h" +#include "CrashHandler.h" +#include "CrashHandlerInternal.h" +#include "RageLog.h" /* for RageLog::GetAdditionalLog, etc. only */ + +struct BacktraceNames +{ + CString Symbol, File; + int Address; + int Offset; + void FromAddr( void *p ); + void FromString( CString str ); + void Demangle(); + CString Format() const; +}; + +#if defined(HAVE_LIBIBERTY) +#include "libiberty.h" + +/* This is in libiberty. Where is it declared? */ +extern "C" { +char *cplus_demangle (const char *mangled, int options); +} + +void BacktraceNames::Demangle() +{ + char *f = cplus_demangle(Symbol, 0); + if(!f) + return; + Symbol = f; + free(f); +} +#else +CString BacktraceNames::Demangle() { } +#endif + + +CString BacktraceNames::Format() const +{ + CString ShortenedPath = File; + if( Symbol != "" ) + { + /* We have some sort of symbol name, so abbreviate or elide the module name. */ + if( ShortenedPath == g_argv[0] ) + ShortenedPath = ""; + else + { + unsigned slash = ShortenedPath.rfind('/'); + if( slash != ShortenedPath.npos ) + ShortenedPath = ShortenedPath.substr(slash+1); + ShortenedPath = CString("(") + ShortenedPath + ")"; + } + } + + CString ret = ssprintf( "%08x: ", Address ); + if( Symbol != "" ) + ret += Symbol + " "; + ret += ShortenedPath; + + return ret; +} + + +#if BACKTRACE_LOOKUP_METHOD == DLADDR +/* This version simply asks libdl, which is more robust. */ +#include +void BacktraceNames::FromAddr( void *p ) +{ + Dl_info di; + if( !dladdr(p, &di) ) + return; + + Symbol = di.dli_sname; + File = di.dli_fname; + Address = (int) p; + Offset = (char*)(p)-(char*)di.dli_saddr; +} + +#elif BACKTRACE_LOOKUP_METHOD == BACKTRACE_SYMBOLS +/* This version parses backtrace_symbols(), an doesn't need libdl. */ +#include +void BacktraceNames::FromAddr( void *p ) +{ + char **foo = backtrace_symbols(&p, 1); + if( foo == NULL ) + return; + FromString( foo[0] ); + Address = (int) p; + free(foo); +} + +/* "path(mangled name+offset) [address]" */ +void BacktraceNames::FromString( CString s ) +{ + /* Hacky parser. I don't want to use regexes in the crash handler. */ + CString MangledAndOffset, sAddress; + unsigned pos = 0; + while( pos < s.size() && s[pos] != '(' && s[pos] != '[' ) + File += s[pos++]; + TrimRight( File ); + TrimLeft( File ); + + if( pos < s.size() && s[pos] == '(' ) + { + pos++; + while( pos < s.size() && s[pos] != ')' ) + MangledAndOffset += s[pos++]; + } + + if( MangledAndOffset != "" ) + { + unsigned plus = MangledAndOffset.rfind('+'); + + if(plus == MangledAndOffset.npos) + { + Symbol = MangledAndOffset; + Offset = 0; + } + else + { + Symbol = MangledAndOffset.substr(0, plus); + CString str = MangledAndOffset.substr(plus); + if( sscanf(str, "%i", &Offset) != 1 ) + Offset=0; + } + } +} +#else +#error BACKTRACE_LOOKUP_METHOD required +#endif + + +static void output_stack_trace( FILE *out, void **BacktracePointers ) +{ + for( int i = 0; BacktracePointers[i]; ++i) + { + BacktraceNames bn; + bn.FromAddr( BacktracePointers[i] ); + bn.Demangle(); + + if( bn.Symbol == "__libc_start_main" ) + break; + + fprintf( out, "%s\n", bn.Format().c_str() ); + } +} + +/* Once we get here, we should be * safe to do whatever we want; + * heavyweights like malloc and CString are OK. (Don't crash!) */ +static void child_process() +{ + int ret; + + /* 1. Read the backtrace pointers. */ + void *BacktracePointers[BACKTRACE_MAX_SIZE]; + ret = read(3, BacktracePointers, sizeof(BacktracePointers)); + + /* 2. Read the signal. */ + int SignalReceived; + ret = read(3, &SignalReceived, sizeof(SignalReceived)); + + /* 3. Read info. */ + int size; + ret = read(3, &size, sizeof(size)); + char *Info = new char [size]; + ret = read(3, Info, size); + + /* 4. Read AdditionalLog. */ + ret = read(3, &size, sizeof(size)); + char *AdditionalLog = new char [size]; + ret = read(3, AdditionalLog, size); + + /* 5. Read RecentLogs. */ + int cnt = 0; + ret = read(3, &cnt, sizeof(cnt)); + char *Recent[1024]; + for( int i = 0; i < cnt; ++i ) + { + ret = read(3, &size, sizeof(size)); + Recent[i] = new char [size]; + ret = read(3, Recent[i], size); + } + + /* Wait for the child to either finish cleaning up or die. XXX: + * This should time out, in case something deadlocks. */ + + char x; + ret = read(3, &x, sizeof(x)); + if( (ret == -1 && errno != EPIPE) || ret != 0 ) + { + /* We expect an EOF or EPIPE. What happened? */ + fprintf(stderr, "Unexpected child read() result: %i (%s)\n", ret, strerror(errno)); + /* keep going */ + } + + FILE *CrashDump = fopen("/tmp/crashinfo.txt", "w+"); + if(CrashDump == NULL) + { + fprintf(stderr, "Couldn't open crashinfo.txt: %s", strerror(errno)); + exit(1); + } + + fprintf(CrashDump, "StepMania crash report\n"); + fprintf(CrashDump, "--------------------------------------\n"); + fprintf(CrashDump, "\n"); + + CString Signal; + +#define X(a) case a: Signal = #a; break; + switch(SignalReceived) + { + case SIGALRM: Signal = "Alarm"; break; + case SIGBUS: Signal = "Bus error"; break; + case SIGFPE: Signal = "Floating point exception"; break; + X(SIGHUP) + case SIGILL: Signal = "Illegal instruction"; break; + X(SIGINT) X(SIGIOT) + case SIGPIPE: Signal = "Broken pipe"; break; + X(SIGQUIT) + case SIGSEGV: Signal = "Segmentation fault"; break; + X(SIGTRAP) X(SIGTERM) X(SIGVTALRM) X(SIGXCPU) X(SIGXFSZ) +#if defined(HAVE_DECL_SIGPWR) && HAVE_DECL_SIGPWR + X(SIGPWR) +#endif + default: Signal = ssprintf("Unknown signal %i", SignalReceived); break; + } + + fprintf( CrashDump, "Crash reason: %s\n\n", Signal.c_str() ); + + output_stack_trace( CrashDump, BacktracePointers ); + fprintf(CrashDump, "\n"); + + fprintf(CrashDump, "Static log:\n"); + fprintf(CrashDump, "%s", Info); + fprintf(CrashDump, "%s", AdditionalLog); + fprintf(CrashDump, "\nPartial log:\n"); + for( int i = 0; i < cnt; ++i ) + fprintf(CrashDump, "%s\n", Recent[i] ); + fprintf(CrashDump, "\n"); + fprintf(CrashDump, "-- End of report\n"); + fclose(CrashDump); + + fprintf(stderr, + "\n" + "StepMania has crashed. Debug information has been output to\n" + "\n" + " /tmp/crashinfo.txt\n" + "\n" + "Please report a bug at:\n" + "\n" + " http://sourceforge.net/tracker/?func=add&group_id=37892&atid=421366\n" + "\n" + ); + + + /* Forcibly kill our parent. */ +// kill( getppid(), SIGKILL ); +} + + +void CrashHandlerHandleArgs() +{ + if(g_argc == 2 && !strcmp(g_argv[1], CHILD_MAGIC_PARAMETER)) + { + child_process(); + exit(0); + } +} + + diff --git a/stepmania/src/archutils/Unix/CrashHandlerInternal.h b/stepmania/src/archutils/Unix/CrashHandlerInternal.h new file mode 100644 index 0000000000..ead30a7bb9 --- /dev/null +++ b/stepmania/src/archutils/Unix/CrashHandlerInternal.h @@ -0,0 +1,7 @@ +#ifndef CRASH_HANDLER_INTERNAL_H + +#define BACKTRACE_MAX_SIZE 1024 +#define CHILD_MAGIC_PARAMETER "--private-do-crash-handler" + +#endif +