split out backtracing and symbol lookup

add support for backtracing other threads
This commit is contained in:
Glenn Maynard
2004-03-12 05:15:32 +00:00
parent 35688b1a21
commit 5d6063681c
9 changed files with 921 additions and 569 deletions
+349
View File
@@ -0,0 +1,349 @@
#include "global.h"
#include "Backtrace.h"
#include "RageUtil.h"
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#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 <sigaction+280>: pop %eax 0x58
* 0x401139d9 <sigaction+281>: mov $0x77,%eax 0xb8 0x77 0x00 0x00 0x00
* 0x401139de <sigaction+286>: 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 <execinfo.h>
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 && i<size; ++i, frame=frame->stackPointer)
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
+35
View File
@@ -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
@@ -0,0 +1,284 @@
/* for dladdr: */
#define __USE_GNU
#include "global.h"
#include "BacktraceNames.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#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 <cxxabi.h>
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 <dlfcn.h>
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 <execinfo.h>
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
@@ -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
+20 -279
View File
@@ -11,6 +11,7 @@
#include "RageLog.h" /* for RageLog::GetAdditionalLog, etc, only */
#include "RageThreads.h"
#include "Backtrace.h"
#include <sys/types.h>
#include <sys/wait.h>
@@ -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 <sigaction+280>: pop %eax 0x58
* 0x401139d9 <sigaction+281>: mov $0x77,%eax 0xb8 0x77 0x00 0x00 0x00
* 0x401139de <sigaction+286>: 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 <execinfo.h>
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 && i<size; ++i, frame=frame->stackPointer)
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();
}
@@ -1,17 +1,15 @@
/* for dladdr: */
#define __USE_GNU
#include "global.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#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 <cxxabi.h>
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 <dlfcn.h>
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 <execinfo.h>
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;
@@ -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
@@ -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 <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <linux/unistd.h>
#define _LINUX_PTRACE_H // hack to prevent broken linux/ptrace.h from conflicting with sys/ptrace.h
#include <linux/user.h>
/* 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, &regs );
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;
}
@@ -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