diff --git a/stepmania/src/archutils/Unix/Backtrace.cpp b/stepmania/src/archutils/Unix/Backtrace.cpp new file mode 100644 index 0000000000..23d00af129 --- /dev/null +++ b/stepmania/src/archutils/Unix/Backtrace.cpp @@ -0,0 +1,349 @@ +#include "global.h" +#include "Backtrace.h" +#include "RageUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BACKTRACE_METHOD_X86_LINUX) +#include "LinuxThreadHelpers.h" + +void GetCurrentBacktraceContext( BacktraceContext *ctx ) +{ + register void *esp __asm__ ("esp"); + ctx->esp = (long) esp; + ctx->eip = (long) GetThreadBacktraceContext; + ctx->ebp = (long) __builtin_frame_address(0); +} + +int GetThreadBacktraceContext( int ThreadID, BacktraceContext *ctx ) +{ + ctx->pid = ThreadID; + + if( ThreadID != GetCurrentThreadId() ) + return GetThreadContext( ThreadID, ctx ); + + GetCurrentBacktraceContext( ctx ); + + return 0; +} + +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; +} + +enum { READABLE_ONLY=1, EXECUTABLE_ONLY=2 }; +static int get_readable_ranges( const void **starts, const void **ends, int size, int type=READABLE_ONLY ) +{ + 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; + + char line[1024]; + strcpy( line, file ); + memmove(file, p, file_used); + file_used -= p-file; + + /* Search for the hyphen. */ + char *hyphen = strchr( line, '-' ); + if( hyphen == NULL ) + continue; /* Parse error. */ + + + /* Search for the space. */ + char *space = strchr( hyphen, ' ' ); + if( space == NULL ) + continue; /* Parse error. */ + + /* " rwxp". If space[1] isn't 'r', then the block isn't readable. */ + if( type & READABLE_ONLY ) + if( strlen(space) < 2 || space[1] != 'r' ) + continue; + /* " rwxp". If space[3] isn't 'x', then the block isn't readable. */ + if( type & EXECUTABLE_ONLY ) + if( strlen(space) < 4 || space[3] != 'x' ) + continue; + + *starts++ = (const void *) xtoi( line ); + *ends++ = (const void *) xtoi( hyphen+1 ); + ++got; + } + + if( file_used == sizeof(file) ) + { + /* Line longer than the buffer. Weird; bail. */ + break; + } + } + + close(fd); + + *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( const 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; +} + +static void *SavedStackPointer = NULL; + +void InitializeBacktrace() +{ + static bool bInitialized = false; + if( bInitialized ) + return; + bInitialized = true; + + /* We might have a different stack in the signal handler. Record a pointer + * that lies in the real stack, so we can look it up later. */ + register void *esp __asm__ ("esp"); + SavedStackPointer = esp; +} + +/* backtrace() for x86 Linux, tested with kernel 2.4.18, glibc 2.3.1. */ +static void do_backtrace( const void **buf, size_t size, BacktraceContext *ctx ) +{ + /* 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 blocks. */ + const int stack_block1 = find_address( (void *) ctx->esp, readable_begin, readable_end ); + const int stack_block2 = find_address( SavedStackPointer, 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; + }; + + StackFrame *frame = (StackFrame *) ctx->ebp; + + unsigned i=0; + if( i < size-1 ) // -1 for NULL + buf[i++] = (void *) ctx->eip; + + while( i < size-1 ) // -1 for NULL + { + /* Make sure that this frame address is readable, and is on the stack. */ + int val = find_address(frame, readable_begin, readable_end); + if( val == -1 ) + break; + if( val != stack_block1 && val != stack_block2 ) + break; + + /* XXX */ + //if( frame->return_address == (void*) CrashSignalHandler ) + // continue; + + /* + * The stack return stub is: + * + * 0x401139d8 : pop %eax 0x58 + * 0x401139d9 : mov $0x77,%eax 0xb8 0x77 0x00 0x00 0x00 + * 0x401139de : int $0x80 0xcd 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 }; + bool is_signal_return = true; + + /* Ugh. Linux 2.6 is putting the return address in a place that isn't listed + * as readable in /proc/pid/maps. This is probably brittle. */ + if( frame->return_address != (void*)0xffffe420 && + 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 ) + { + /* Mark the signal trampoline. */ + if( i < size-1 ) + buf[i++] = BACKTRACE_SIGNAL_TRAMPOLINE; + + to_add = (void *) frame->sig.eip; + } + else + to_add = frame->return_address; + + if( i < size-1 && 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; +} + +void GetBacktrace( const void **buf, size_t size, BacktraceContext *ctx ) +{ + InitializeBacktrace(); + + BacktraceContext CurrentCtx; + if( ctx == NULL ) + { + ctx = &CurrentCtx; + GetCurrentBacktraceContext( &CurrentCtx ); + } + + + do_backtrace( buf, size, ctx ); +} +#elif defined(BACKTRACE_METHOD_BACKTRACE) +#include + +void InitializeBacktrace() { } + +void GetBacktrace( const void **buf, size_t size, BacktraceContext *ctx ) +{ + InitializeBacktrace(); + + 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; +} +#elif defined(BACKTRACE_METHOD_POWERPC_DARWIN) + +#include "archutils/Darwin/Crash.h" +typedef struct Frame { + Frame *stackPointer; + long conditionReg; + void *linkReg; +} *FramePtr; + +void InitializeBacktrace() { } + +void GetBacktrace( const void **buf, size_t size, BacktraceContext *ctx ) +{ + FramePtr frame = FramePtr(GetCrashedFramePtr()); + unsigned i; + for (i=0; frame && frame->linkReg && istackPointer) + buf[i] = frame->linkReg; + i = (i == size ? size - 1 : i); + buf[i] = NULL; +} + +#else + +#warning Undefined BACKTRACE_METHOD_* +void InitializeBacktrace() { } + +void GetBacktrace( const void **buf, size_t size, BacktraceContext *ctx ) +{ + buf[0] = BACKTRACE_METHOD_NOT_AVAILABLE; + buf[1] = NULL; +} + +#endif + diff --git a/stepmania/src/archutils/Unix/Backtrace.h b/stepmania/src/archutils/Unix/Backtrace.h new file mode 100644 index 0000000000..b57692b71d --- /dev/null +++ b/stepmania/src/archutils/Unix/Backtrace.h @@ -0,0 +1,35 @@ +#ifndef BACKTRACE_H +#define BACKTRACE_H + +/* This API works like backtrace_pointers(), to retrieve a stack trace. */ + +/* This contains the information necessary to backtrace a thread. */ +struct BacktraceContext +{ +#if defined(LINUX) + long eip, esp, ebp; + pid_t pid; +#endif +}; + +/* Initialize. This is optional. If not called explicitly, it will be + * called as necessary. This may do things that are not safe to do in + * crash conditions. */ +void InitializeBacktrace(); + +/* Retrieve up to size-1 backtrace pointers in buf. The array will be + * null-terminated. If ctx is NULL, retrieve the current backtrace; otherwise + * retrieve a backtrace for the given context. (Not all backtracers may + * support contexts.) */ +void GetBacktrace( const void **buf, size_t size, BacktraceContext *ctx = NULL ); + +/* Set up a BacktraceContext to get a backtrace for a thread. ThreadID may + * be the current thread. */ +int GetThreadBacktraceContext( int ThreadID, BacktraceContext *ctx ); +void GetCurrentBacktraceContext( BacktraceContext *ctx ); + +#define BACKTRACE_METHOD_NOT_AVAILABLE ((void*) -1) +#define BACKTRACE_SIGNAL_TRAMPOLINE ((void*) -2) + +#endif + diff --git a/stepmania/src/archutils/Unix/BacktraceNames.cpp b/stepmania/src/archutils/Unix/BacktraceNames.cpp new file mode 100644 index 0000000000..350ad47d69 --- /dev/null +++ b/stepmania/src/archutils/Unix/BacktraceNames.cpp @@ -0,0 +1,284 @@ +/* for dladdr: */ +#define __USE_GNU +#include "global.h" +#include "BacktraceNames.h" + +#include +#include +#include +#include +#include +#include + +#include "RageUtil.h" + +#if defined(DARWIN) +#include "archutils/Darwin/Crash.h" +#endif + +#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); +} +#elif defined(HAVE_CXA_DEMANGLE) +#include + +void BacktraceNames::Demangle() +{ + /* demangle the name using __cxa_demangle() if needed */ + if( Symbol.substr(0, 2) != "_Z" ) + return; + + int status = 0; + char *name = abi::__cxa_demangle( Symbol, 0, 0, &status ); + if( name ) + { + Symbol = name; + free( name ); + return; + } + + switch( status ) + { + case -1: + fprintf( stderr, "Out of memory\n" ); + break; + case -2: + fprintf( stderr, "Invalid mangled name: %s.\n", Symbol.c_str() ); + break; + case -3: + fprintf( stderr, "Invalid arguments.\n" ); + break; + default: + fprintf( stderr, "Unexpected __cxa_demangle status: %i", status ); + break; + } +} +#else +void BacktraceNames::Demangle() { } +#endif + + +CString BacktraceNames::Format() const +{ + CString ShortenedPath = File; + if( ShortenedPath != "" ) + { + /* Abbreviate the module name. */ + 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 defined(BACKTRACE_LOOKUP_METHOD_DLADDR) +/* This version simply asks libdl, which is more robust. */ +#include +void BacktraceNames::FromAddr( const void *p ) +{ + Address = (int) p; + + Dl_info di; + if( !dladdr(p, &di) ) + return; + + Symbol = di.dli_sname; + File = di.dli_fname; + Offset = (char*)(p)-(char*)di.dli_saddr; +} + +#elif defined(BACKTRACE_LOOKUP_METHOD_BACKTRACE_SYMBOLS) +/* This version parses backtrace_symbols(), an doesn't need libdl. */ +#include +void BacktraceNames::FromAddr( const void *p ) +{ + Address = (int) p; + + char **foo = backtrace_symbols(&p, 1); + if( foo == NULL ) + return; + FromString( foo[0] ); + 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; + } + } +} +#elif defined(BACKTRACE_LOOKUP_METHOD_ATOS) +void BacktraceNames::FromAddr( const void *p ) +{ + int fds[2]; + int out = fileno(stdout); + pid_t pid; + pid_t ppid = getppid(); /* Do this before fork()ing! */ + + Offset = 0; + Address = long(p); + + if (pipe(fds) != 0) + { + fprintf(stderr, "FromAddr pipe() failed: %s\n", strerror(errno)); + return; + } + + pid = fork(); + if (pid == -1) + { + fprintf(stderr, "FromAddr fork() failed: %s\n", strerror(errno)); + return; + } + + if (pid == 0) + { + close(fds[0]); + for (int fd = 3; fd < 1024; ++fd) + if (fd != fds[1]) + close(fd); + dup2(fds[1], out); + close(fds[1]); + + char *addy; + asprintf(&addy, "0x%x", long(p)); + char *p; + asprintf(&p, "%d", ppid); + + execl("/usr/bin/atos", "/usr/bin/atos", "-p", p, addy, NULL); + + fprintf(stderr, "execl(atos) failed: %s\n", strerror(errno)); + free(addy); + free(p); + close(out); + _exit(1); + } + + close(fds[1]); + char f[1024]; + bzero(f, 1024); + int len = read(fds[0], f, 1024); + + Symbol = ""; + File = ""; + + if (len == -1) + { + fprintf(stderr, "FromAddr read() failed: %s\n", strerror(errno)); + return; + } + CStringArray mangledAndFile; + + split(f, " ", mangledAndFile, true); + if (mangledAndFile.size() == 0) + return; + Symbol = mangledAndFile[0]; + /* eg + * -[NSApplication run] + * +[SomeClass initialize] + */ + if (Symbol[0] == '-' || Symbol[0] == '+') + { + Symbol = mangledAndFile[0] + " " + mangledAndFile[1]; + /* eg + * (crt.c:300) + * (AppKit) + */ + if (mangledAndFile.size() == 3) + { + File = mangledAndFile[2]; + unsigned pos = File.find('('); + unsigned start = (pos == File.npos ? 0 : pos+1); + pos = File.rfind(')') - 1; + File = File.substr(start, pos); + } + return; + } + /* eg + * __start -> _start + * _SDL_main -> SDL_main + */ + if (Symbol[0] == '_') + Symbol = Symbol.substr(1); + + /* eg, the full line: + * __Z1Ci (in a.out) (asmtest.cc:33) + * _main (in a.out) (asmtest.cc:52) + */ + if (mangledAndFile.size() > 3) + { + File = mangledAndFile[3]; + unsigned pos = File.find('('); + unsigned start = (pos == File.npos ? 0 : pos+1); + pos = File.rfind(')') - 1; + File = File.substr(start, pos); + } + /* eg, the full line: + * _main (SDLMain.m:308) + * __Z8GameLoopv (crt.c:300) + */ + else if (mangledAndFile.size() == 3) + File = mangledAndFile[2].substr(0, mangledAndFile[2].rfind(')')); +} +#else +#warning Undefined BACKTRACE_LOOKUP_METHOD_* +void BacktraceNames::FromAddr( const void *p ) +{ + Address = long(p); + Offset = 0; + Symbol = ""; + File = ""; +} +#endif + diff --git a/stepmania/src/archutils/Unix/BacktraceNames.h b/stepmania/src/archutils/Unix/BacktraceNames.h new file mode 100644 index 0000000000..8695af9475 --- /dev/null +++ b/stepmania/src/archutils/Unix/BacktraceNames.h @@ -0,0 +1,17 @@ +#ifndef BACKTRACE_NAMES_H +#define BACKTRACE_NAMES_H + +struct BacktraceNames +{ + CString Symbol, File; + int Address; + int Offset; + void FromAddr( const void *p ); + void FromString( CString str ); + void Demangle(); + CString Format() const; + BacktraceNames(): Address(0), Offset(0) { } +}; + +#endif + diff --git a/stepmania/src/archutils/Unix/CrashHandler.cpp b/stepmania/src/archutils/Unix/CrashHandler.cpp index cf8be860cb..2a762cebc9 100644 --- a/stepmania/src/archutils/Unix/CrashHandler.cpp +++ b/stepmania/src/archutils/Unix/CrashHandler.cpp @@ -11,6 +11,7 @@ #include "RageLog.h" /* for RageLog::GetAdditionalLog, etc, only */ #include "RageThreads.h" +#include "Backtrace.h" #include #include @@ -167,283 +168,6 @@ static const char *itoa(unsigned n) return p; } -#if defined(BACKTRACE_METHOD_X86_LINUX) -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; -} - -enum { READABLE_ONLY=1, EXECUTABLE_ONLY=2 }; -static int get_readable_ranges( const void **starts, const void **ends, int size, int type=READABLE_ONLY ) -{ - 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; - - char line[1024]; - strcpy( line, file ); - memmove(file, p, file_used); - file_used -= p-file; - - /* Search for the hyphen. */ - char *hyphen = strchr( line, '-' ); - if( hyphen == NULL ) - continue; /* Parse error. */ - - - /* Search for the space. */ - char *space = strchr( hyphen, ' ' ); - if( space == NULL ) - continue; /* Parse error. */ - - /* " rwxp". If space[1] isn't 'r', then the block isn't readable. */ - if( type & READABLE_ONLY ) - if( strlen(space) < 2 || space[1] != 'r' ) - continue; - /* " rwxp". If space[3] isn't 'x', then the block isn't readable. */ - if( type & EXECUTABLE_ONLY ) - if( strlen(space) < 4 || space[3] != 'x' ) - continue; - - *starts++ = (const void *) xtoi( line ); - *ends++ = (const void *) xtoi( hyphen+1 ); - ++got; - } - - if( file_used == sizeof(file) ) - { - /* Line longer than the buffer. Weird; bail. */ - break; - } - } - - close(fd); - - *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( const 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; -} - -static void *SavedStackPointer = NULL; - -static void initialize_do_backtrace() -{ - /* We might have a different stack in the signal handler. Record a pointer - * that lies in the real stack, so we can look it up later. */ - register void *esp __asm__ ("esp"); - SavedStackPointer = esp; -} - -/* backtrace() for x86 Linux, tested with kernel 2.4.18, glibc 2.3.1. */ -static void do_backtrace( const void **buf, size_t size, bool ignore_before_sig = true ) -{ - /* 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 blocks. */ - register void *esp __asm__ ("esp"); - const int stack_block1 = find_address( esp, readable_begin, readable_end ); - const int stack_block2 = find_address( SavedStackPointer, 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; - }; - - StackFrame *frame = (StackFrame *) __builtin_frame_address(0); - - 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-1 ) // -1 for NULL - { - /* Make sure that this frame address is readable, and is on the stack. */ - int val = find_address(frame, readable_begin, readable_end); - if( val == -1 ) - break; - if( val != stack_block1 && val != stack_block2 ) - break; - - if( frame->return_address == (void*) CrashSignalHandler ) - continue; - - /* - * The stack return stub is: - * - * 0x401139d8 : pop %eax 0x58 - * 0x401139d9 : mov $0x77,%eax 0xb8 0x77 0x00 0x00 0x00 - * 0x401139de : int $0x80 0xcd 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 }; - bool is_signal_return = true; - - /* Ugh. Linux 2.6 is putting the return address in a place that isn't listed - * as readable in /proc/pid/maps. This is probably brittle. */ - if( frame->return_address != (void*)0xffffe420 && - 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; - - /* If we didn't get any frames, our trampoline handling probably failed. Turn - * ignore_before_sig off. We'll probably lose the top frame, which is bad. - * You can tell that this happened if this function is visible on the returned - * call stack. */ - if( i == 0 && ignore_before_sig ) - do_backtrace( buf, size, false ); -} -#elif defined(BACKTRACE_METHOD_BACKTRACE) -#include - -static void initialize_do_backtrace() { } - -static void do_backtrace( const 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; -} -#elif defined(BACKTRACE_METHOD_POWERPC_DARWIN) -#include "archutils/Darwin/Crash.h" -typedef struct Frame { - Frame *stackPointer; - long conditionReg; - void *linkReg; -} *FramePtr; - -static void initialize_do_backtrace() { } - -static void do_backtrace( const void **buf, size_t size ) -{ - FramePtr frame = FramePtr(GetCrashedFramePtr()); - unsigned i; - for (i=0; frame && frame->linkReg && istackPointer) - buf[i] = frame->linkReg; - i = (i == size ? size - 1 : i); - buf[i] = NULL; -} -#else -#warning Undefined BACKTRACE_METHOD_* -static void initialize_do_backtrace() { } - -static void do_backtrace( const void **buf, size_t size ) -{ - buf[0] = BACKTRACE_METHOD_NOT_AVAILABLE; - buf[1] = NULL; -} -#endif - - void CrashSignalHandler( int signal ) { if( g_pCrashHandlerArgv0 == NULL ) @@ -478,8 +202,25 @@ void CrashSignalHandler( int signal ) /* Do this early, so functions called below don't end up on the backtrace. */ const void *BacktracePointers[BACKTRACE_MAX_SIZE]; - do_backtrace(BacktracePointers, BACKTRACE_MAX_SIZE); + GetBacktrace( BacktracePointers, BACKTRACE_MAX_SIZE ); + /* If we have BACKTRACE_SIGNAL_TRAMPOLINE, remove it and everything before it. */ + for( int i = 0; BacktracePointers[i]; ++i ) + { + if( BacktracePointers[i] != BACKTRACE_SIGNAL_TRAMPOLINE ) + continue; + + ++i; + + /* Find the terminator. */ + int end = i; + while( BacktracePointers[end] ) + ++end; + + memmove( &BacktracePointers[0], &BacktracePointers[i], sizeof(void*) * end-i-1 ); + break; + } + /* 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. */ int fds[2]; @@ -514,6 +255,6 @@ void CrashSignalHandler( int signal ) void InitializeCrashHandler() { - initialize_do_backtrace(); + InitializeBacktrace(); } diff --git a/stepmania/src/archutils/Unix/CrashHandlerChild.cpp b/stepmania/src/archutils/Unix/CrashHandlerChild.cpp index 7c4c948379..d60608fe60 100644 --- a/stepmania/src/archutils/Unix/CrashHandlerChild.cpp +++ b/stepmania/src/archutils/Unix/CrashHandlerChild.cpp @@ -1,17 +1,15 @@ -/* for dladdr: */ #define __USE_GNU #include "global.h" #include -#include -#include -#include #include #include - #include #include +#include "Backtrace.h" +#include "BacktraceNames.h" + #include "RageUtil.h" #include "CrashHandler.h" #include "CrashHandlerInternal.h" @@ -28,289 +26,6 @@ extern const unsigned version_num; const char *g_pCrashHandlerArgv0 = NULL; -struct BacktraceNames -{ - CString Symbol, File; - int Address; - int Offset; - void FromAddr( void *p ); - void FromString( CString str ); - void Demangle(); - CString Format() const; - BacktraceNames(): Address(0), Offset(0) { } -}; - -#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); -} -#elif defined(HAVE_CXA_DEMANGLE) -#include - -void BacktraceNames::Demangle() -{ - /* demangle the name using __cxa_demangle() if needed */ - if( Symbol.substr(0, 2) != "_Z" ) - return; - - int status = 0; - char *name = abi::__cxa_demangle( Symbol, 0, 0, &status ); - if( name ) - { - Symbol = name; - free( name ); - return; - } - - switch( status ) - { - case -1: - fprintf( stderr, "Out of memory\n" ); - break; - case -2: - fprintf( stderr, "Invalid mangled name: %s.\n", Symbol.c_str() ); - break; - case -3: - fprintf( stderr, "Invalid arguments.\n" ); - break; - default: - fprintf( stderr, "Unexpected __cxa_demangle status: %i", status ); - break; - } -} -#else -void BacktraceNames::Demangle() { } -#endif - - -CString BacktraceNames::Format() const -{ - CString ShortenedPath = File; - if( ShortenedPath != "" ) - { - /* We have some sort of symbol name, so abbreviate or elide the module name. */ - if( ShortenedPath == g_pCrashHandlerArgv0 ) - 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 defined(BACKTRACE_LOOKUP_METHOD_DLADDR) -/* This version simply asks libdl, which is more robust. */ -#include -void BacktraceNames::FromAddr( void *p ) -{ - Address = (int) p; - - Dl_info di; - if( !dladdr(p, &di) ) - return; - - Symbol = di.dli_sname; - File = di.dli_fname; - Offset = (char*)(p)-(char*)di.dli_saddr; -} - -#elif defined(BACKTRACE_LOOKUP_METHOD_BACKTRACE_SYMBOLS) -/* This version parses backtrace_symbols(), an doesn't need libdl. */ -#include -void BacktraceNames::FromAddr( void *p ) -{ - Address = (int) p; - - char **foo = backtrace_symbols(&p, 1); - if( foo == NULL ) - return; - FromString( foo[0] ); - 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; - } - } -} -#elif defined(BACKTRACE_LOOKUP_METHOD_ATOS) -void BacktraceNames::FromAddr( void *p ) -{ - int fds[2]; - int out = fileno(stdout); - pid_t pid; - pid_t ppid = getppid(); /* Do this before fork()ing! */ - - Offset = 0; - Address = long(p); - - if (pipe(fds) != 0) - { - fprintf(stderr, "FromAddr pipe() failed: %s\n", strerror(errno)); - return; - } - - pid = fork(); - if (pid == -1) - { - fprintf(stderr, "FromAddr fork() failed: %s\n", strerror(errno)); - return; - } - - if (pid == 0) - { - close(fds[0]); - for (int fd = 3; fd < 1024; ++fd) - if (fd != fds[1]) - close(fd); - dup2(fds[1], out); - close(fds[1]); - - char *addy; - asprintf(&addy, "0x%x", long(p)); - char *p; - asprintf(&p, "%d", ppid); - - execl("/usr/bin/atos", "/usr/bin/atos", "-p", p, addy, NULL); - - fprintf(stderr, "execl(atos) failed: %s\n", strerror(errno)); - free(addy); - free(p); - close(out); - _exit(1); - } - - close(fds[1]); - char f[1024]; - bzero(f, 1024); - int len = read(fds[0], f, 1024); - - Symbol = ""; - File = ""; - - if (len == -1) - { - fprintf(stderr, "FromAddr read() failed: %s\n", strerror(errno)); - return; - } - CStringArray mangledAndFile; - - split(f, " ", mangledAndFile, true); - if (mangledAndFile.size() == 0) - return; - Symbol = mangledAndFile[0]; - /* eg - * -[NSApplication run] - * +[SomeClass initialize] - */ - if (Symbol[0] == '-' || Symbol[0] == '+') - { - Symbol = mangledAndFile[0] + " " + mangledAndFile[1]; - /* eg - * (crt.c:300) - * (AppKit) - */ - if (mangledAndFile.size() == 3) - { - File = mangledAndFile[2]; - unsigned pos = File.find('('); - unsigned start = (pos == File.npos ? 0 : pos+1); - pos = File.rfind(')') - 1; - File = File.substr(start, pos); - } - return; - } - /* eg - * __start -> _start - * _SDL_main -> SDL_main - */ - if (Symbol[0] == '_') - Symbol = Symbol.substr(1); - - /* eg, the full line: - * __Z1Ci (in a.out) (asmtest.cc:33) - * _main (in a.out) (asmtest.cc:52) - */ - if (mangledAndFile.size() > 3) - { - File = mangledAndFile[3]; - unsigned pos = File.find('('); - unsigned start = (pos == File.npos ? 0 : pos+1); - pos = File.rfind(')') - 1; - File = File.substr(start, pos); - } - /* eg, the full line: - * _main (SDLMain.m:308) - * __Z8GameLoopv (crt.c:300) - */ - else if (mangledAndFile.size() == 3) - File = mangledAndFile[2].substr(0, mangledAndFile[2].rfind(')')); -} -#else -#warning Undefined BACKTRACE_LOOKUP_METHOD_* -void BacktraceNames::FromAddr( void *p ) -{ - Address = long(p); - Offset = 0; - Symbol = ""; - File = ""; -} -#endif - static void output_stack_trace( FILE *out, void **BacktracePointers ) { @@ -332,6 +47,10 @@ static void output_stack_trace( FILE *out, void **BacktracePointers ) bn.FromAddr( BacktracePointers[i] ); bn.Demangle(); + /* Don't show the main module name. */ + if( bn.File == g_pCrashHandlerArgv0 ) + bn.File = ""; + if( bn.Symbol == "__libc_start_main" ) break; diff --git a/stepmania/src/archutils/Unix/CrashHandlerInternal.h b/stepmania/src/archutils/Unix/CrashHandlerInternal.h index 509a1eaaeb..303d246d74 100644 --- a/stepmania/src/archutils/Unix/CrashHandlerInternal.h +++ b/stepmania/src/archutils/Unix/CrashHandlerInternal.h @@ -4,7 +4,5 @@ #define BACKTRACE_MAX_SIZE 1024 #define CHILD_MAGIC_PARAMETER "--private-do-crash-handler" -#define BACKTRACE_METHOD_NOT_AVAILABLE ((void*) -1) - #endif diff --git a/stepmania/src/archutils/Unix/LinuxThreadHelpers.cpp b/stepmania/src/archutils/Unix/LinuxThreadHelpers.cpp new file mode 100644 index 0000000000..e31873710e --- /dev/null +++ b/stepmania/src/archutils/Unix/LinuxThreadHelpers.cpp @@ -0,0 +1,194 @@ +/* RageThreads helpers for threads in Linux, which are based on PIDs and TIDs. */ + +#include "global.h" +#include "LinuxThreadHelpers.h" +#include "RageUtil.h" + +#include "Backtrace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define _LINUX_PTRACE_H // hack to prevent broken linux/ptrace.h from conflicting with sys/ptrace.h +#include + +/* In Linux, we might be using PID-based or TID-based threads. With PID-based + * threads, getpid() returns a unique value for each thread; each thread is a + * separate process. Newer kernels using NPTL return the same PID for all + * threads; these systems support a gettid() call to get a unique TID for each + * thread. */ + + + +static bool g_bUsingNPTL = false; + +static _syscall0(pid_t,gettid) + +static bool UsingNPTL() +{ + return ThreadsVersion().Left(4) == "NPTL"; +} + +void InitializePidThreadHelpers() +{ + bool bInitialized = false; + if( bInitialized ) + return; + bInitialized = true; + + g_bUsingNPTL = UsingNPTL(); +} + +/* waitpid(); ThreadID can be a PID or (in NPTL) a TID; doesn't care if the ID + * is a clone() or not. */ +static int waittid( int ThreadID, int *status, int options ) +{ + static bool bSupportsWall = true; + + if( bSupportsWall ) + { + int ret = waitpid( ThreadID, status, options | __WALL ); + if( ret != -1 || errno != EINVAL ) + return ret; + bSupportsWall = false; + } + + /* XXX: on 2.2, we need to use __WCLONE only if ThreadID isn't the main thread; + * perhaps wait and retry without it if errno == ECHILD? */ + int ret; + ret = waitpid( ThreadID, status, options ); + + return ret; +} + +/* Attempt to PTRACE_ATTACH to a thread, and wait for the SIGSTOP. */ +static int PtraceAttach( int ThreadID ) +{ + int ret; + ret = ptrace( PTRACE_ATTACH, ThreadID, NULL, NULL ); + if( ret == -1 ) + { + printf("ptrace failed: %s\n", strerror(errno) ); + return -1; + } + + /* Wait for the SIGSTOP from the ptrace call. */ + int status; + ret = waittid( ThreadID, &status, 0 ); + if( ret == -1 ) + return -1; + +// printf( "ret %i, exited %i, signalled %i, sig %i, stopped %i, stopsig %i\n", ret, WIFEXITED(status), +// WIFSIGNALED(status), WTERMSIG(status), WIFSTOPPED(status), WSTOPSIG(status)); + return 0; +} + +static int PtraceDetach( int ThreadID ) +{ + return ptrace( PTRACE_DETACH, ThreadID, NULL, NULL ); +} + + +CString ThreadsVersion() +{ +#if defined(LINUX) + char buf[1024]; + int ret = confstr( _CS_GNU_LIBPTHREAD_VERSION, buf, sizeof(buf) ); + RAGE_ASSERT_M( ret != -1, ssprintf( "%i", ret) ); + return buf; +#else + return "(unknown)"; +#endif +} + +/* Get this thread's ID (this may be a TID or a PID). */ +int GetCurrentThreadId() +{ +#if defined(LINUX) + InitializePidThreadHelpers(); // for g_bUsingNPTL + + pid_t ret = gettid(); + + /* If this fails with ENOSYS, we're on a kernel before gettid. If we + * don't have NPTL, then just use getpid(). If we're on NPTL and don't + * have gettid(), something's wrong. */ + if( ret != -1 ) + return ret; + + ASSERT( !g_bUsingNPTL ); +#endif + + return getpid(); +} + +int SuspendThread( int ThreadID ) +{ + /* + * Linux: We can't simply kill(SIGSTOP) (or tkill), since that will stop all processes + * (grr). We can ptrace(PTRACE_ATTACH) the process to stop it, and PTRACE_DETACH + * to restart it. + */ + return PtraceAttach( ThreadID ); + // kill( ThreadID, SIGSTOP ); +} + +int ResumeThread( int ThreadID ) +{ + return PtraceDetach( ThreadID ); + // kill( ThreadID, SIGSTOP ); +} + + +/* Get user_regs_struct for a thread. tid must not be the current thread. + * + * tid() is a PID (from getpid) or a TID (from gettid). Note that this may have kernel compatibility + * problems, because NPTL is new and its interactions with ptrace() aren't well-defined. + * If we're on a non-NPTL system, tid is a regular PID. */ +int GetThreadContext( int ThreadID, BacktraceContext *ctx ) +{ + /* Can't GetThreadContext the current thread. */ + ASSERT( ThreadID != GetCurrentThreadId() ); + + /* Attach to the thread. */ + int ret = PtraceAttach( ThreadID ); + + /* This may fail with EPERM. This can happen for at least two common + * reasons: the process might be in a debugger already, or *we* might + * already have attached to it via SuspendThread. + * + * If it's in a debugger, we won't be able to ptrace(PTRACE_GETREGS). If + * it's us that attached, we will. Be careful: if SuspendThread was + * called to stop the thread (causing this attach to fail), we must not + * restart the thread when we're finished. */ + bool bAttachFailed = (ret == -1); + if( bAttachFailed ) + { + RAGE_ASSERT_M( errno == EPERM, ssprintf( "%s", strerror(errno) ) ); + } + + user_regs_struct regs; + ret = ptrace( PTRACE_GETREGS, ThreadID, NULL, ®s ); + if( ret != 0 ) + ret = -1; + ASSERT( ret == 0 ); + + if( !bAttachFailed ) + { + /* We attached to it, so we need to detach. */ + ret = ptrace( PTRACE_DETACH, ThreadID, NULL, NULL ); + ASSERT( ret == 0 ); + } + + + ctx->eip = regs.eip; + ctx->esp = regs.esp; + ctx->ebp = regs.ebp; + + return ret; +} diff --git a/stepmania/src/archutils/Unix/LinuxThreadHelpers.h b/stepmania/src/archutils/Unix/LinuxThreadHelpers.h new file mode 100644 index 0000000000..dbeb9d7f10 --- /dev/null +++ b/stepmania/src/archutils/Unix/LinuxThreadHelpers.h @@ -0,0 +1,15 @@ +#ifndef PID_THREAD_HELPERS_H +#define PID_THREAD_HELPERS_H + +CString ThreadsVersion(); + +/* Get the current thread's ThreadID. */ +int GetCurrentThreadId(); + +int SuspendThread( int ThreadID ); +int ResumeThread( int ThreadID ); + +struct BacktraceContext; +int GetThreadContext( int ThreadID, BacktraceContext *ctx ); + +#endif