#define __USE_GNU #include "global.h" #include #include #include #include #include #include #include "Backtrace.h" #include "BacktraceNames.h" #include "RageUtil.h" #include "CrashHandler.h" #include "CrashHandlerInternal.h" #include "RageLog.h" /* for RageLog::GetAdditionalLog, etc. only */ #include "ProductInfo.h" #include "arch/ArchHooks/ArchHooks.h" #if defined(MACOSX) #include "archutils/Darwin/Crash.h" #endif #if defined(HAVE_VERSION_INFO) extern const unsigned version_num; extern const char *version_time; #endif const char *g_pCrashHandlerArgv0 = NULL; static void output_stack_trace( FILE *out, const void **BacktracePointers ) { if( BacktracePointers[0] == BACKTRACE_METHOD_NOT_AVAILABLE ) { fprintf( out, "No backtrace method available.\n"); return; } if( !BacktracePointers[0] ) { fprintf( out, "Backtrace was empty.\n"); return; } for( int i = 0; BacktracePointers[i]; ++i) { BacktraceNames bn; 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; fprintf( out, "%s\n", bn.Format().c_str() ); } } bool child_read( int fd, void *p, int size ) { char *buf = (char *) p; int got = 0; while( got < size ) { int ret = read( fd, buf+got, size-got ); if( ret == -1 ) { if( errno == EINTR ) continue; fprintf( stderr, "Crash handler: error communicating with parent: %s\n", strerror(errno) ); return false; } if( ret == 0 ) { fprintf( stderr, "Crash handler: EOF communicating with parent.\n" ); return false; } got += ret; } return true; } /* Once we get here, we should be * safe to do whatever we want; * heavyweights like malloc and RString are OK. (Don't crash!) */ static void child_process() { /* 1. Read the CrashData. */ CrashData crash; if( !child_read(3, &crash, sizeof(CrashData)) ) return; /* 2. Read info. */ int size; if( !child_read(3, &size, sizeof(size)) ) return; char *Info = new char [size]; if( !child_read(3, Info, size) ) return; /* 3. Read AdditionalLog. */ if( !child_read(3, &size, sizeof(size)) ) return; char *AdditionalLog = new char [size]; if( !child_read(3, AdditionalLog, size) ) return; /* 4. Read RecentLogs. */ int cnt = 0; if( !child_read(3, &cnt, sizeof(cnt)) ) return; char *Recent[1024]; for( int i = 0; i < cnt; ++i ) { if( !child_read(3, &size, sizeof(size)) ) return; Recent[i] = new char [size]; if( !child_read(3, Recent[i], size) ) return; } /* 5. Read CHECKPOINTs. */ if( !child_read(3, &size, sizeof(size)) ) return; char *temp = new char [size]; if( !child_read(3, temp, size) ) return; vector Checkpoints; split(temp, "$$", Checkpoints); delete [] temp; /* 6. Read the crashed thread's name. */ if( !child_read(3, &size, sizeof(size)) ) return; temp = new char [size]; if( !child_read(3, temp, size) ) return; const RString CrashedThread(temp); delete[] temp; /* Wait for the child to either finish cleaning up or die. */ fd_set rs; struct timeval timeout = { 5, 0 }; // 5 seconds FD_ZERO( &rs ); FD_SET( 3, &rs ); int ret = select( 4, &rs, NULL, NULL, &timeout ); if( ret == 0 ) { fputs( "Timeout exceeded.\n", stderr ); } else if( (ret == -1 && errno != EPIPE) || ret != 1 ) { fprintf( stderr, "Unexpected return from select() result: %d (%s)\n", ret, strerror(errno) ); // Keep going. } else { char x; // No need to check FD_ISSET( 3, &rs ) because it was the only descriptor in the set. ret = read( 3, &x, sizeof(x) ); if( ret > 0 ) { fprintf( stderr, "Unexpected child read() result: %i\n", ret ); /* keep going */ } else 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 */ } } RString sCrashInfoPath = "/tmp"; #if defined(MACOSX) sCrashInfoPath = CrashHandler::GetLogsDirectory(); #else const char *home = getenv( "HOME" ); if( home ) sCrashInfoPath = home; #endif sCrashInfoPath += "/crashinfo.txt"; FILE *CrashDump = fopen( sCrashInfoPath, "w+" ); if(CrashDump == NULL) { fprintf( stderr, "Couldn't open " + sCrashInfoPath + ": %s\n", strerror(errno) ); exit(1); } fprintf( CrashDump, "%s crash report", PRODUCT_ID_VER ); #if defined(HAVE_VERSION_INFO) fprintf( CrashDump, " (build %u, %s)", version_num, version_time ); #endif fprintf( CrashDump, "\n" ); fprintf( CrashDump, "--------------------------------------\n" ); fprintf( CrashDump, "\n" ); RString reason; switch( crash.type ) { case CrashData::SIGNAL: { reason = ssprintf( "%s - %s", SignalName(crash.signal), SignalCodeName(crash.signal, crash.si.si_code) ); /* Linux puts the PID that sent the signal in si_addr for SI_USER. */ if( crash.si.si_code == SI_USER ) { reason += ssprintf( " from pid %li", (long) crash.si.si_addr ); } else { switch( crash.signal ) { case SIGILL: case SIGFPE: case SIGSEGV: case SIGBUS: reason += ssprintf( " at 0x%0*lx", int(sizeof(void*)*2), (unsigned long) crash.si.si_addr ); } break; } } case CrashData::FORCE_CRASH: crash.reason[sizeof(crash.reason)-1] = 0; reason = crash.reason; break; } fprintf( CrashDump, "Architecture: %s\n", HOOKS->GetArchName().c_str() ); fprintf( CrashDump, "Crash reason: %s\n", reason.c_str() ); fprintf( CrashDump, "Crashed thread: %s\n\n", CrashedThread.c_str() ); fprintf(CrashDump, "Checkpoints:\n"); for( unsigned i=0; i