2003-07-27 07:07:45 +00:00
|
|
|
#include "global.h"
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
#include <fcntl.h>
|
2004-03-19 03:23:42 +00:00
|
|
|
#include <signal.h>
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
#include "RageLog.h" /* for RageLog::GetAdditionalLog, etc, only */
|
2004-01-13 00:01:07 +00:00
|
|
|
#include "RageThreads.h"
|
2004-03-12 05:15:32 +00:00
|
|
|
#include "Backtrace.h"
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
|
|
|
|
|
#include "CrashHandler.h"
|
|
|
|
|
#include "CrashHandlerInternal.h"
|
|
|
|
|
|
2004-03-11 23:58:49 +00:00
|
|
|
extern const char *g_pCrashHandlerArgv0;
|
|
|
|
|
|
2003-07-27 07:07:45 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2004-02-15 06:32:07 +00:00
|
|
|
static void NORETURN spawn_child_process( int from_parent )
|
2003-07-27 07:07:45 +00:00
|
|
|
{
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2003-07-31 22:35:54 +00:00
|
|
|
dup2(from_parent, 3);
|
2003-07-27 07:07:45 +00:00
|
|
|
close(from_parent);
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-11 23:58:49 +00:00
|
|
|
execl( g_pCrashHandlerArgv0, g_pCrashHandlerArgv0, CHILD_MAGIC_PARAMETER, NULL);
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
/* If we got here, the exec failed. */
|
2004-03-11 23:58:49 +00:00
|
|
|
safe_print(fileno(stderr), "Crash handler execl(", g_pCrashHandlerArgv0, ") failed: ", strerror(errno), "\n", NULL);
|
2003-07-27 07:07:45 +00:00
|
|
|
_exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2004-01-12 23:54:13 +00:00
|
|
|
/* write(), but retry a couple times on EINTR. */
|
2004-01-13 00:01:07 +00:00
|
|
|
static int retried_write( int fd, const void *buf, size_t count )
|
2004-01-12 23:54:13 +00:00
|
|
|
{
|
|
|
|
|
int tries = 3, ret;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
ret = write( fd, buf, count );
|
|
|
|
|
}
|
2004-01-13 00:01:07 +00:00
|
|
|
while( ret == -1 && errno == EINTR && tries-- );
|
2004-01-12 23:54:13 +00:00
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
static void parent_write(int to_child, const void *p, size_t size)
|
|
|
|
|
{
|
2004-01-12 23:54:13 +00:00
|
|
|
size_t ret = retried_write(to_child, p, size);
|
2003-07-27 07:07:45 +00:00
|
|
|
if(ret != size)
|
|
|
|
|
{
|
|
|
|
|
safe_print(fileno(stderr), "Unexpected write() result (", strerror(errno), ")\n", NULL);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-19 06:56:18 +00:00
|
|
|
static void parent_process( int to_child, const void **BacktracePointers, const CrashData *crash )
|
2003-07-27 07:07:45 +00:00
|
|
|
{
|
|
|
|
|
/* 1. Write the backtrace pointers. */
|
2003-07-29 21:11:21 +00:00
|
|
|
parent_write(to_child, BacktracePointers, sizeof(void *)*BACKTRACE_MAX_SIZE);
|
2003-07-27 07:07:45 +00:00
|
|
|
|
2004-03-19 06:56:18 +00:00
|
|
|
/* 2. Write the CrashData. */
|
|
|
|
|
parent_write(to_child, crash, sizeof(CrashData));
|
|
|
|
|
|
2003-07-27 07:07:45 +00:00
|
|
|
/* 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);
|
|
|
|
|
}
|
2003-08-07 19:58:15 +00:00
|
|
|
|
|
|
|
|
/* 6. Write CHECKPOINTs. */
|
2003-11-24 00:23:52 +00:00
|
|
|
p = Checkpoints::GetLogs("\n");
|
2003-08-07 19:58:15 +00:00
|
|
|
size = strlen(p)+1;
|
|
|
|
|
parent_write(to_child, &size, sizeof(size));
|
2003-10-21 09:00:15 +00:00
|
|
|
parent_write(to_child, p, size);
|
|
|
|
|
|
|
|
|
|
/* 7. Write the crashed thread's name. */
|
|
|
|
|
p = RageThread::GetCurThreadName();
|
|
|
|
|
size = strlen(p)+1;
|
|
|
|
|
parent_write(to_child, &size, sizeof(size));
|
2003-08-07 19:58:15 +00:00
|
|
|
parent_write(to_child, p, size);
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-19 08:17:44 +00:00
|
|
|
#if defined(DARWIN)
|
|
|
|
|
const char *ExceptionName( int signo )
|
2004-03-19 03:13:27 +00:00
|
|
|
{
|
|
|
|
|
#define X(code) case k##code: return #code;
|
|
|
|
|
switch( signo )
|
|
|
|
|
{
|
|
|
|
|
X(UnknownException)
|
|
|
|
|
X(IllegalInstructionException)
|
|
|
|
|
X(TrapException)
|
|
|
|
|
X(AccessException)
|
|
|
|
|
X(UnmappedMemoryException)
|
|
|
|
|
X(ExcludedMemoryException)
|
|
|
|
|
X(ReadOnlyMemoryException)
|
|
|
|
|
X(UnresolvablePageFaultException)
|
|
|
|
|
X(PrivilegeViolationException)
|
|
|
|
|
X(TraceException)
|
|
|
|
|
X(InstructionBreakpointException)
|
|
|
|
|
X(DataBreakpointException)
|
|
|
|
|
X(FloatingPointException)
|
|
|
|
|
X(StackOverflowException)
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
static char buf[128];
|
|
|
|
|
strcpy( buf, "Unknown exception " );
|
|
|
|
|
strcat( buf, itoa(signo) );
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-03-19 08:17:44 +00:00
|
|
|
#undef X
|
|
|
|
|
}
|
2004-03-19 03:13:27 +00:00
|
|
|
#endif
|
2004-03-19 08:17:44 +00:00
|
|
|
|
|
|
|
|
const char *SignalName( int signo )
|
|
|
|
|
{
|
|
|
|
|
#define X(a) case a: return #a;
|
|
|
|
|
switch( signo )
|
|
|
|
|
{
|
|
|
|
|
case SIGALRM: return "Alarm";
|
|
|
|
|
case SIGBUS: return "Bus error";
|
|
|
|
|
case SIGFPE: return "Floating point exception";
|
|
|
|
|
X(SIGHUP)
|
|
|
|
|
case SIGILL: return "Illegal instruction";
|
|
|
|
|
X(SIGINT)
|
|
|
|
|
case SIGPIPE: return "Broken pipe";
|
|
|
|
|
case SIGABRT: return "Aborted";
|
|
|
|
|
X(SIGQUIT)
|
|
|
|
|
case SIGSEGV: return "Segmentation fault";
|
|
|
|
|
X(SIGTRAP) X(SIGTERM) X(SIGVTALRM) X(SIGXCPU) X(SIGXFSZ)
|
|
|
|
|
#if defined(HAVE_DECL_SIGPWR) && HAVE_DECL_SIGPWR
|
|
|
|
|
X(SIGPWR)
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
static char buf[128];
|
|
|
|
|
strcpy( buf, "Unknown signal " );
|
|
|
|
|
strcat( buf, itoa(signo) );
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#undef X
|
2004-03-19 03:13:27 +00:00
|
|
|
}
|
|
|
|
|
|
2004-03-19 06:56:18 +00:00
|
|
|
static void RunCrashHandler( const CrashData *crash )
|
2003-07-27 07:07:45 +00:00
|
|
|
{
|
2004-03-12 03:03:09 +00:00
|
|
|
if( g_pCrashHandlerArgv0 == NULL )
|
2004-02-20 04:11:01 +00:00
|
|
|
{
|
2004-03-11 23:58:49 +00:00
|
|
|
safe_print(fileno(stderr), "Crash handler failed: CrashHandlerHandleArgs was not called\n", NULL);
|
2004-02-20 04:11:01 +00:00
|
|
|
_exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-19 03:23:42 +00:00
|
|
|
/* Block SIGPIPE, so we get EPIPE. */
|
|
|
|
|
struct sigaction sa;
|
2004-03-19 05:03:19 +00:00
|
|
|
memset( &sa, 0, sizeof(sa) );
|
2004-03-19 03:23:42 +00:00
|
|
|
sa.sa_handler = SIG_IGN;
|
|
|
|
|
if( sigaction( SIGPIPE, &sa, NULL ) != 0 )
|
|
|
|
|
{
|
|
|
|
|
safe_print(fileno(stderr), "sigaction() failed: %s", strerror(errno), NULL);
|
|
|
|
|
/* non-fatal */
|
|
|
|
|
}
|
|
|
|
|
|
2003-10-21 09:00:15 +00:00
|
|
|
static int received = 0;
|
|
|
|
|
static pid_t childpid = 0;
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
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. */
|
2004-03-19 08:41:46 +00:00
|
|
|
switch( crash->type )
|
|
|
|
|
{
|
|
|
|
|
#if !defined(DARWIN)
|
|
|
|
|
case CrashData::SIGNAL:
|
|
|
|
|
safe_print( fileno(stderr), "Fatal signal (", SignalName(crash->signal), ")", NULL );
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(DARWIN)
|
|
|
|
|
case CrashData::OSX_EXCEPTION:
|
|
|
|
|
safe_print( fileno(stderr), "Fatal exception (", ExceptionName( crash->kind ), ")", NULL );
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
safe_print( fileno(stderr), "Unexpected RunCrashHandler call (", itoa(crash->type), ")", NULL );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2003-10-21 09:00:15 +00:00
|
|
|
if( received == getpid() )
|
2004-03-19 08:41:46 +00:00
|
|
|
safe_print( fileno(stderr), " while still in the crash handler\n", NULL);
|
2003-10-21 09:00:15 +00:00
|
|
|
else if( childpid == getpid() )
|
2004-03-19 08:41:46 +00:00
|
|
|
safe_print( fileno(stderr), " while in the crash handler child\n", NULL);
|
2003-10-21 09:00:15 +00:00
|
|
|
else
|
2004-03-19 08:41:46 +00:00
|
|
|
safe_print( fileno(stderr), " (to unknown PID)\n", NULL);
|
|
|
|
|
|
2003-07-27 07:07:45 +00:00
|
|
|
_exit(1);
|
|
|
|
|
}
|
2003-10-21 09:00:15 +00:00
|
|
|
received = getpid();
|
2003-07-27 07:07:45 +00:00
|
|
|
|
2003-10-21 09:00:15 +00:00
|
|
|
/* We want to stop other threads when crashing. However, sending SIGSTOPs is messy and
|
|
|
|
|
* tends to do more harm than good. Let's just try to get the crashdump written quickly. */
|
|
|
|
|
// RageThread::HaltAllThreads();
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
/* Do this early, so functions called below don't end up on the backtrace. */
|
2004-01-29 21:16:57 +00:00
|
|
|
const void *BacktracePointers[BACKTRACE_MAX_SIZE];
|
2004-03-19 09:25:45 +00:00
|
|
|
if( crash->type == CrashData::FORCE_CRASH_THIS_THREAD )
|
|
|
|
|
GetBacktrace( BacktracePointers, BACKTRACE_MAX_SIZE, NULL );
|
|
|
|
|
else
|
|
|
|
|
GetBacktrace( BacktracePointers, BACKTRACE_MAX_SIZE, &crash->ctx );
|
2003-07-27 07:07:45 +00:00
|
|
|
|
|
|
|
|
/* 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];
|
|
|
|
|
if(pipe(fds) != 0)
|
|
|
|
|
{
|
|
|
|
|
safe_print(fileno(stderr), "Crash handler pipe() failed: ", strerror(errno), "\n", NULL);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2003-10-21 09:00:15 +00:00
|
|
|
childpid = fork();
|
|
|
|
|
if( childpid == -1 )
|
2003-07-27 07:07:45 +00:00
|
|
|
{
|
|
|
|
|
safe_print(fileno(stderr), "Crash handler fork() failed: ", strerror(errno), "\n", NULL);
|
2004-02-15 06:33:36 +00:00
|
|
|
_exit(1);
|
2003-07-27 07:07:45 +00:00
|
|
|
}
|
|
|
|
|
|
2003-10-21 09:00:15 +00:00
|
|
|
if( childpid == 0 )
|
2003-07-27 07:07:45 +00:00
|
|
|
{
|
|
|
|
|
close(fds[1]);
|
|
|
|
|
spawn_child_process(fds[0]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
close(fds[0]);
|
2004-03-19 06:56:18 +00:00
|
|
|
parent_process( fds[1], BacktracePointers, crash );
|
2003-10-21 09:00:15 +00:00
|
|
|
int status = 0;
|
|
|
|
|
waitpid( childpid, &status, 0 );
|
|
|
|
|
if( WIFSIGNALED(status) )
|
|
|
|
|
safe_print( fileno(stderr), "Crash handler child exited with signal ", itoa(WTERMSIG(status)), "\n", NULL);
|
2003-07-27 07:07:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-19 06:56:18 +00:00
|
|
|
void ForceCrashHandler( const char *reason )
|
|
|
|
|
{
|
|
|
|
|
CrashData crash;
|
|
|
|
|
crash.type = CrashData::FORCE_CRASH_THIS_THREAD;
|
|
|
|
|
strncpy( crash.reason, reason, sizeof(crash.reason) );
|
|
|
|
|
crash.reason[ sizeof(crash.reason)-1 ] = 0;
|
|
|
|
|
|
|
|
|
|
RunCrashHandler( &crash );
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-19 08:48:47 +00:00
|
|
|
#if !defined(DARWIN)
|
2004-03-19 06:56:18 +00:00
|
|
|
void CrashSignalHandler( int signal, siginfo_t *si, const ucontext_t *uc )
|
|
|
|
|
{
|
|
|
|
|
CrashData crash;
|
|
|
|
|
crash.type = CrashData::SIGNAL;
|
|
|
|
|
crash.signal = signal;
|
|
|
|
|
crash.si = *si;
|
|
|
|
|
|
|
|
|
|
GetSignalBacktraceContext( &crash.ctx, uc );
|
|
|
|
|
RunCrashHandler( &crash );
|
|
|
|
|
}
|
2004-03-19 08:48:47 +00:00
|
|
|
#endif
|
2004-03-19 06:56:18 +00:00
|
|
|
|
2004-03-19 08:17:44 +00:00
|
|
|
#if defined(DARWIN)
|
|
|
|
|
OSStatus CrashExceptionHandler( ExceptionInformation *e )
|
|
|
|
|
{
|
|
|
|
|
CrashData crash;
|
|
|
|
|
crash.type = CrashData::OSX_EXCEPTION;
|
2004-03-19 08:30:58 +00:00
|
|
|
crash.kind = e->theKind;
|
2004-03-19 08:17:44 +00:00
|
|
|
|
|
|
|
|
GetExceptionBacktraceContext( &crash.ctx, e );
|
|
|
|
|
RunCrashHandler( &crash );
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2004-03-19 06:56:18 +00:00
|
|
|
|
2003-11-11 22:21:33 +00:00
|
|
|
void InitializeCrashHandler()
|
|
|
|
|
{
|
2004-03-12 05:15:32 +00:00
|
|
|
InitializeBacktrace();
|
2003-11-11 22:21:33 +00:00
|
|
|
}
|
2003-07-27 07:07:45 +00:00
|
|
|
|