diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c deleted file mode 100644 index 00703b61..00000000 --- a/engine/common/crashhandler.c +++ /dev/null @@ -1,558 +0,0 @@ -/* -crashhandler.c - advanced crashhandler -Copyright (C) 2016 Mittorn - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include "common.h" - -/* -================ -Sys_Crash - -Crash handler, called from system -================ -*/ -#if XASH_WIN32 - -#if DBGHELP - -#pragma comment( lib, "dbghelp" ) - -#include -#include -#include -#include - -#ifndef XASH_SDL -typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; -#endif - -static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) -{ - DWORD_PTR baseAddress = 0; - static HMODULE *moduleArray; - static unsigned int moduleCount; - LPBYTE moduleArrayBytes; - DWORD bytesRequired; - int i; - - if( len < 3 ) - return 0; - - if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) - { - if ( bytesRequired ) - { - moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); - - if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) - { - moduleCount = bytesRequired / sizeof( HMODULE ); - moduleArray = (HMODULE *)moduleArrayBytes; - } - } - } - - for( i = 0; i < moduleCount; i++ ) - { - MODULEINFO info; - GetModuleInformation( process, moduleArray[i], &info, sizeof(MODULEINFO) ); - - if( ( address > info.lpBaseOfDll ) && - ( (DWORD64)address < (DWORD64)info.lpBaseOfDll + (DWORD64)info.SizeOfImage ) ) - return GetModuleBaseName( process, moduleArray[i], name, len ); - } - return Q_snprintf( name, len, "???" ); -} - -static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) -{ - char message[8192]; // match *nix Sys_Crash - int len = 0; - size_t i; - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - IMAGEHLP_LINE64 line; - DWORD dline = 0; - DWORD options; - CONTEXT context; - STACKFRAME64 stackframe; - DWORD image; - - context = *pInfo->ContextRecord; - - options = SymGetOptions(); - options |= SYMOPT_DEBUG; - options |= SYMOPT_LOAD_LINES; - SymSetOptions( options ); - - SymInitialize( process, NULL, TRUE ); - - ZeroMemory( &stackframe, sizeof( STACKFRAME64 )); - -#ifdef _M_IX86 - image = IMAGE_FILE_MACHINE_I386; - stackframe.AddrPC.Offset = context.Eip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Ebp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Esp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_X64 - image = IMAGE_FILE_MACHINE_AMD64; - stackframe.AddrPC.Offset = context.Rip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Rsp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Rsp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_IA64 - image = IMAGE_FILE_MACHINE_IA64; - stackframe.AddrPC.Offset = context.StIIP; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.IntSp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrBStore.Offset = context.RsBSP; - stackframe.AddrBStore.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.IntSp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_ARM - image = IMAGE_FILE_MACHINE_ARMNT; - stackframe.AddrPC.Offset = context.Pc; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.R11; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Sp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_ARM64 - image = IMAGE_FILE_MACHINE_ARM64; - stackframe.AddrPC.Offset = context.Pc; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Fp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Sp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif -#error -#endif - - len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", - Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); - - len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", - pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); - if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len, "Exception: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - for( i = 0; i < 25; i++ ) - { - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - BOOL result = StackWalk64( - image, process, thread, - &stackframe, &context, NULL, - SymFunctionTableAccess64, SymGetModuleBase64, NULL); - DWORD64 displacement = 0; - - if( !result ) - break; - - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - len += Q_snprintf( message + len, 1024 - len, "% 2d %p", - i, (void*)stackframe.AddrPC.Offset ); - if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ) ) - { - len += Q_snprintf( message + len, 1024 - len, " %s ", symbol->Name ); - } - if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - len += Q_snprintf( message + len, 1024 - len, "("); - len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); - len += Q_snprintf( message + len, 1024 - len, ")\n"); - } - -#if XASH_SDL == 2 - if( host.type != HOST_DEDICATED ) // let system to restart server automaticly - SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); -#endif - - Sys_PrintLog( message ); - - SymCleanup( process ); -} - -static void Sys_GetProcessName( char *processName, size_t bufferSize ) -{ - char fullpath[MAX_PATH]; - - GetModuleBaseName( GetCurrentProcess(), NULL, fullpath, sizeof( fullpath ) - 1 ); - COM_FileBase( fullpath, processName, bufferSize ); -} - -static void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName, size_t bufferSize ) -{ - time_t currentUtcTime = time( NULL ); - struct tm *currentLocalTime = localtime( ¤tUtcTime ); - - Q_snprintf( mdmpFileName, bufferSize, "%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp", - processName, - Q_buildcommit(), - currentLocalTime->tm_year + 1900, - currentLocalTime->tm_mon + 1, - currentLocalTime->tm_mday, - currentLocalTime->tm_hour, - currentLocalTime->tm_min, - currentLocalTime->tm_sec); -} - -static qboolean Sys_WriteMinidump(PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType) -{ - HRESULT errorCode; - string processName; - string mdmpFileName; - MINIDUMP_EXCEPTION_INFORMATION minidumpInfo; - - Sys_GetProcessName( processName, sizeof( processName )); - Sys_GetMinidumpFileName( processName, mdmpFileName, sizeof( mdmpFileName )); - - SetLastError( NOERROR ); - HANDLE fileHandle = CreateFile( - mdmpFileName, GENERIC_WRITE, FILE_SHARE_WRITE, - NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - errorCode = HRESULT_FROM_WIN32( GetLastError( )); - if( !SUCCEEDED( errorCode )) { - CloseHandle( fileHandle ); - return false; - } - - minidumpInfo.ThreadId = GetCurrentThreadId(); - minidumpInfo.ExceptionPointers = exceptionInfo; - minidumpInfo.ClientPointers = FALSE; - - qboolean status = MiniDumpWriteDump( - GetCurrentProcess(), GetCurrentProcessId(), fileHandle, - minidumpType, &minidumpInfo, NULL, NULL); - - CloseHandle( fileHandle ); - return status; -} - -#endif /* DBGHELP */ - -LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; -static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) -{ - // save config - if( host.status != HOST_CRASHED ) - { - // check to avoid recursive call - host.crashed = true; - -#ifdef XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif // XASH_SDL - -#if DBGHELP - Sys_StackTrace( pInfo ); -#else - Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); -#endif - - if( host.type == HOST_NORMAL ) - CL_Crashed(); // tell client about crash - else host.status = HOST_CRASHED; - -#if DBGHELP - if( Sys_CheckParm( "-minidumps" )) - { - int minidumpFlags = ( - MiniDumpWithDataSegs | - MiniDumpWithCodeSegs | - MiniDumpWithHandleData | - MiniDumpWithFullMemory | - MiniDumpWithFullMemoryInfo | - MiniDumpWithIndirectlyReferencedMemory | - MiniDumpWithThreadInfo | - MiniDumpWithModuleHeaders); - - if( !Sys_WriteMinidump( pInfo, (MINIDUMP_TYPE)minidumpFlags )) { - // fallback method, create minidump with minimal info in it - Sys_WriteMinidump( pInfo, MiniDumpWithDataSegs ); - } - } -#endif - - if( host_developer.value <= 0 ) - { - // no reason to call debugger in release build - just exit - Sys_Quit(); - return EXCEPTION_CONTINUE_EXECUTION; - } - - // all other states keep unchanged to let debugger find bug - Sys_DestroyConsole(); - } - - if( oldFilter ) - return oldFilter( pInfo ); - return EXCEPTION_CONTINUE_EXECUTION; -} - -void Sys_SetupCrashHandler( void ) -{ - SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors - oldFilter = SetUnhandledExceptionFilter( Sys_Crash ); -} - -void Sys_RestoreCrashHandler( void ) -{ - // restore filter - if( oldFilter ) SetUnhandledExceptionFilter( oldFilter ); -} - -#elif XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX -// Posix signal handler -#ifndef XASH_OPENBSD -#include -#endif -#include -#include -#include "library.h" - -#define STACK_BACKTRACE_STR "Stack backtrace:\n" -#define STACK_DUMP_STR "Stack dump:\n" - -#define STACK_BACKTRACE_STR_LEN ( sizeof( STACK_BACKTRACE_STR ) - 1 ) -#define STACK_DUMP_STR_LEN ( sizeof( STACK_DUMP_STR ) - 1 ) -#define ALIGN( x, y ) (((uintptr_t) ( x ) + (( y ) - 1 )) & ~(( y ) - 1 )) - -static struct sigaction oldFilter; - -static int Sys_PrintFrame( char *buf, int len, int i, void *addr ) -{ - Dl_info dlinfo; - if( len <= 0 ) - return 0; // overflow - - if( dladdr( addr, &dlinfo )) - { - if( dlinfo.dli_sname ) - return Q_snprintf( buf, len, "%2d: %p <%s+%lu> (%s)\n", i, addr, dlinfo.dli_sname, - (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address - - return Q_snprintf( buf, len, "%2d: %p (%s)\n", i, addr, dlinfo.dli_fname ); // print module and address - } - - return Q_snprintf( buf, len, "%2d: %p\n", i, addr ); // print only address -} - -static void Sys_Crash( int signal, siginfo_t *si, void *context) -{ - void *pc = NULL, **bp = NULL, **sp = NULL; // this must be set for every OS! - char message[8192]; - int len, logfd, i = 0; - -#if XASH_OPENBSD - struct sigcontext *ucontext = (struct sigcontext*)context; -#else - ucontext_t *ucontext = (ucontext_t*)context; -#endif - -#if XASH_AMD64 - #if XASH_FREEBSD - pc = (void*)ucontext->uc_mcontext.mc_rip; - bp = (void**)ucontext->uc_mcontext.mc_rbp; - sp = (void**)ucontext->uc_mcontext.mc_rsp; - #elif XASH_NETBSD - pc = (void*)ucontext->uc_mcontext.__gregs[_REG_RIP]; - bp = (void**)ucontext->uc_mcontext.__gregs[_REG_RBP]; - sp = (void**)ucontext->uc_mcontext.__gregs[_REG_RSP]; - #elif XASH_OPENBSD - pc = (void*)ucontext->sc_rip; - bp = (void**)ucontext->sc_rbp; - sp = (void**)ucontext->sc_rsp; - #else - pc = (void*)ucontext->uc_mcontext.gregs[REG_RIP]; - bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP]; - sp = (void**)ucontext->uc_mcontext.gregs[REG_RSP]; - #endif -#elif XASH_X86 - #if XASH_FREEBSD - pc = (void*)ucontext->uc_mcontext.mc_eip; - bp = (void**)ucontext->uc_mcontext.mc_ebp; - sp = (void**)ucontext->uc_mcontext.mc_esp; - #elif XASH_NETBSD - pc = (void*)ucontext->uc_mcontext.__gregs[_REG_EIP]; - bp = (void**)ucontext->uc_mcontext.__gregs[_REG_EBP]; - sp = (void**)ucontext->uc_mcontext.__gregs[_REG_ESP]; - #elif XASH_OPENBSD - pc = (void*)ucontext->sc_eip; - bp = (void**)ucontext->sc_ebp; - sp = (void**)ucontext->sc_esp; - #else - pc = (void*)ucontext->uc_mcontext.gregs[REG_EIP]; - bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP]; - sp = (void**)ucontext->uc_mcontext.gregs[REG_ESP]; - #endif -#elif XASH_ARM && XASH_64BIT - pc = (void*)ucontext->uc_mcontext.pc; - bp = (void*)ucontext->uc_mcontext.regs[29]; - sp = (void*)ucontext->uc_mcontext.sp; -#elif XASH_ARM - pc = (void*)ucontext->uc_mcontext.arm_pc; - bp = (void*)ucontext->uc_mcontext.arm_fp; - sp = (void*)ucontext->uc_mcontext.arm_sp; -#endif - - // safe actions first, stack and memory may be corrupted - len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", - Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); - -#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD - len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr ); -#else - len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr ); -#endif - - write( STDERR_FILENO, message, len ); - - // flush buffers before writing directly to descriptors - fflush( stdout ); - fflush( stderr ); - - // now get log fd and write trace directly to log - logfd = Sys_LogFileNo(); - write( logfd, message, len ); - - if( pc && bp && sp ) - { - size_t pagesize = sysconf( _SC_PAGESIZE ); - - // try to print backtrace - write( STDERR_FILENO, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - write( logfd, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - Q_strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); - len += STACK_BACKTRACE_STR_LEN; - -// false on success, true on failure -#define try_allow_read(pointer, pagesize) \ - ((mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE | PROT_EXEC ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_EXEC ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ ) == -1)) - - do - { - int line = Sys_PrintFrame( message + len, sizeof( message ) - len, ++i, pc); - write( STDERR_FILENO, message + len, line ); - write( logfd, message + len, line ); - len += line; - //if( !dladdr(bp,0) ) break; // only when bp is in module - if( try_allow_read( bp, pagesize ) ) - break; - if( try_allow_read( bp[0], pagesize ) ) - break; - pc = bp[1]; - bp = (void**)bp[0]; - } - while( bp && i < 128 ); - - // try to print stack - write( STDERR_FILENO, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - write( logfd, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - Q_strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); - len += STACK_DUMP_STR_LEN; - - if( !try_allow_read( sp, pagesize ) ) - { - for( i = 0; i < 32; i++ ) - { - int line = Sys_PrintFrame( message + len, sizeof( message ) - len, i, sp[i] ); - write( STDERR_FILENO, message + len, line ); - write( logfd, message + len, line ); - len += line; - } - } - -#undef try_allow_read - } - - // put MessageBox as Sys_Error - Msg( "%s\n", message ); -#ifdef XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif - host.crashed = true; - Platform_MessageBox( "Xash Error", message, false ); - - // log saved, now we can try to save configs and close log correctly, it may crash - if( host.type == HOST_NORMAL ) - CL_Crashed(); - host.status = HOST_CRASHED; - - - Sys_Quit(); -} - -void Sys_SetupCrashHandler( void ) -{ - struct sigaction act = { 0 }; - act.sa_sigaction = Sys_Crash; - act.sa_flags = SA_SIGINFO | SA_ONSTACK; - sigaction( SIGSEGV, &act, &oldFilter ); - sigaction( SIGABRT, &act, &oldFilter ); - sigaction( SIGBUS, &act, &oldFilter ); - sigaction( SIGILL, &act, &oldFilter ); -} - -void Sys_RestoreCrashHandler( void ) -{ - sigaction( SIGSEGV, &oldFilter, NULL ); - sigaction( SIGABRT, &oldFilter, NULL ); - sigaction( SIGBUS, &oldFilter, NULL ); - sigaction( SIGILL, &oldFilter, NULL ); -} - -#else - -void Sys_SetupCrashHandler( void ) -{ - // stub -} - -void Sys_RestoreCrashHandler( void ) -{ - // stub -} -#endif diff --git a/engine/common/system.h b/engine/common/system.h index 630a347f..30eca2e6 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -51,8 +51,6 @@ void Sys_Error( const char *error, ... ) FORMAT_CHECK( 1 ); qboolean Sys_LoadLibrary( dll_info_t *dll ); qboolean Sys_FreeLibrary( dll_info_t *dll ); void Sys_ParseCommandLine( int argc, char **argv ); -void Sys_SetupCrashHandler( void ); -void Sys_RestoreCrashHandler( void ); void Sys_DebugBreak( void ); #define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size ); diff --git a/engine/platform/platform.h b/engine/platform/platform.h index dc1dc92c..0ec16b2a 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -177,6 +177,20 @@ static inline void Platform_Sleep( int msec ) #endif } +#if XASH_WIN32 || XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX +void Sys_SetupCrashHandler( void ); +void Sys_RestoreCrashHandler( void ); +#else +static inline void Sys_SetupCrashHandler( void ) +{ +} + +static inline void Sys_RestoreCrashHandler( void ) +{ +} +#endif + + /* ============================================================================== diff --git a/engine/platform/posix/crash_posix.c b/engine/platform/posix/crash_posix.c new file mode 100644 index 00000000..f36a826a --- /dev/null +++ b/engine/platform/posix/crash_posix.c @@ -0,0 +1,223 @@ +/* +crashhandler.c - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" + +// while this is mostly POSIX-compatible functions +// the contents of ucontext_t is platform-dependent +// before adding new OS here, check platform.h +#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX +#ifndef XASH_OPENBSD + #include +#endif +#include +#include +#include "library.h" + +#define STACK_BACKTRACE_STR "Stack backtrace:\n" +#define STACK_DUMP_STR "Stack dump:\n" + +#define STACK_BACKTRACE_STR_LEN ( sizeof( STACK_BACKTRACE_STR ) - 1 ) +#define STACK_DUMP_STR_LEN ( sizeof( STACK_DUMP_STR ) - 1 ) +#define ALIGN( x, y ) (((uintptr_t) ( x ) + (( y ) - 1 )) & ~(( y ) - 1 )) + +static struct sigaction oldFilter; + +static int Sys_PrintFrame( char *buf, int len, int i, void *addr ) +{ + Dl_info dlinfo; + if( len <= 0 ) + return 0; // overflow + + if( dladdr( addr, &dlinfo )) + { + if( dlinfo.dli_sname ) + return Q_snprintf( buf, len, "%2d: %p <%s+%lu> (%s)\n", i, addr, dlinfo.dli_sname, + (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address + + return Q_snprintf( buf, len, "%2d: %p (%s)\n", i, addr, dlinfo.dli_fname ); // print module and address + } + + return Q_snprintf( buf, len, "%2d: %p\n", i, addr ); // print only address +} + +static void Sys_Crash( int signal, siginfo_t *si, void *context) +{ + void *pc = NULL, **bp = NULL, **sp = NULL; // this must be set for every OS! + char message[8192]; + int len, logfd, i = 0; + +#if XASH_OPENBSD + struct sigcontext *ucontext = (struct sigcontext*)context; +#else + ucontext_t *ucontext = (ucontext_t*)context; +#endif + +#if XASH_AMD64 +#if XASH_FREEBSD + pc = (void*)ucontext->uc_mcontext.mc_rip; + bp = (void**)ucontext->uc_mcontext.mc_rbp; + sp = (void**)ucontext->uc_mcontext.mc_rsp; +#elif XASH_NETBSD + pc = (void*)ucontext->uc_mcontext.__gregs[_REG_RIP]; + bp = (void**)ucontext->uc_mcontext.__gregs[_REG_RBP]; + sp = (void**)ucontext->uc_mcontext.__gregs[_REG_RSP]; +#elif XASH_OPENBSD + pc = (void*)ucontext->sc_rip; + bp = (void**)ucontext->sc_rbp; + sp = (void**)ucontext->sc_rsp; +#else + pc = (void*)ucontext->uc_mcontext.gregs[REG_RIP]; + bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP]; + sp = (void**)ucontext->uc_mcontext.gregs[REG_RSP]; +#endif +#elif XASH_X86 +#if XASH_FREEBSD + pc = (void*)ucontext->uc_mcontext.mc_eip; + bp = (void**)ucontext->uc_mcontext.mc_ebp; + sp = (void**)ucontext->uc_mcontext.mc_esp; +#elif XASH_NETBSD + pc = (void*)ucontext->uc_mcontext.__gregs[_REG_EIP]; + bp = (void**)ucontext->uc_mcontext.__gregs[_REG_EBP]; + sp = (void**)ucontext->uc_mcontext.__gregs[_REG_ESP]; +#elif XASH_OPENBSD + pc = (void*)ucontext->sc_eip; + bp = (void**)ucontext->sc_ebp; + sp = (void**)ucontext->sc_esp; +#else + pc = (void*)ucontext->uc_mcontext.gregs[REG_EIP]; + bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP]; + sp = (void**)ucontext->uc_mcontext.gregs[REG_ESP]; +#endif +#elif XASH_ARM && XASH_64BIT + pc = (void*)ucontext->uc_mcontext.pc; + bp = (void*)ucontext->uc_mcontext.regs[29]; + sp = (void*)ucontext->uc_mcontext.sp; +#elif XASH_ARM + pc = (void*)ucontext->uc_mcontext.arm_pc; + bp = (void*)ucontext->uc_mcontext.arm_fp; + sp = (void*)ucontext->uc_mcontext.arm_sp; +#endif + + // safe actions first, stack and memory may be corrupted + len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", + Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); + +#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD + len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr ); +#else + len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr ); +#endif + + write( STDERR_FILENO, message, len ); + + // flush buffers before writing directly to descriptors + fflush( stdout ); + fflush( stderr ); + + // now get log fd and write trace directly to log + logfd = Sys_LogFileNo(); + write( logfd, message, len ); + + if( pc && bp && sp ) + { + size_t pagesize = sysconf( _SC_PAGESIZE ); + + // try to print backtrace + write( STDERR_FILENO, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); + write( logfd, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); + Q_strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); + len += STACK_BACKTRACE_STR_LEN; + + // false on success, true on failure +#define try_allow_read(pointer, pagesize) \ + (( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE | PROT_EXEC ) == -1 ) && \ + ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_EXEC ) == -1 ) && \ + ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE ) == -1 ) && \ + ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ ) == -1 )) + + do + { + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, ++i, pc); + write( STDERR_FILENO, message + len, line ); + write( logfd, message + len, line ); + len += line; + //if( !dladdr(bp,0) ) break; // only when bp is in module + if( try_allow_read( bp, pagesize ) ) + break; + if( try_allow_read( bp[0], pagesize ) ) + break; + pc = bp[1]; + bp = (void**)bp[0]; + } + while( bp && i < 128 ); + + // try to print stack + write( STDERR_FILENO, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); + write( logfd, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); + Q_strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); + len += STACK_DUMP_STR_LEN; + + if( !try_allow_read( sp, pagesize ) ) + { + for( i = 0; i < 32; i++ ) + { + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, i, sp[i] ); + write( STDERR_FILENO, message + len, line ); + write( logfd, message + len, line ); + len += line; + } + } + +#undef try_allow_read + } + + // put MessageBox as Sys_Error + Msg( "%s\n", message ); +#ifdef XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif + host.crashed = true; + Platform_MessageBox( "Xash Error", message, false ); + + // log saved, now we can try to save configs and close log correctly, it may crash + if( host.type == HOST_NORMAL ) + CL_Crashed(); + host.status = HOST_CRASHED; + + + Sys_Quit(); +} + +void Sys_SetupCrashHandler( void ) +{ + struct sigaction act = { 0 }; + act.sa_sigaction = Sys_Crash; + act.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigaction( SIGSEGV, &act, &oldFilter ); + sigaction( SIGABRT, &act, &oldFilter ); + sigaction( SIGBUS, &act, &oldFilter ); + sigaction( SIGILL, &act, &oldFilter ); +} + +void Sys_RestoreCrashHandler( void ) +{ + sigaction( SIGSEGV, &oldFilter, NULL ); + sigaction( SIGABRT, &oldFilter, NULL ); + sigaction( SIGBUS, &oldFilter, NULL ); + sigaction( SIGILL, &oldFilter, NULL ); +} + +#endif // XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX diff --git a/engine/platform/win32/crash_win.c b/engine/platform/win32/crash_win.c new file mode 100644 index 00000000..563f6285 --- /dev/null +++ b/engine/platform/win32/crash_win.c @@ -0,0 +1,331 @@ +/* +crashhandler.c - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" + +#define DBGHELP 1 // we always enable dbghelp.dll on Windows targets + +#if DBGHELP + +#include +#include +#include +#include + +#ifndef XASH_SDL +typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; +#endif + +static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) +{ + DWORD_PTR baseAddress = 0; + static HMODULE *moduleArray; + static unsigned int moduleCount; + LPBYTE moduleArrayBytes; + DWORD bytesRequired; + int i; + + if( len < 3 ) + return 0; + + if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) + { + if ( bytesRequired ) + { + moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); + + if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) + { + moduleCount = bytesRequired / sizeof( HMODULE ); + moduleArray = (HMODULE *)moduleArrayBytes; + } + } + } + + for( i = 0; i < moduleCount; i++ ) + { + MODULEINFO info; + GetModuleInformation( process, moduleArray[i], &info, sizeof(MODULEINFO) ); + + if( ( address > info.lpBaseOfDll ) && + ( (DWORD64)address < (DWORD64)info.lpBaseOfDll + (DWORD64)info.SizeOfImage ) ) + return GetModuleBaseName( process, moduleArray[i], name, len ); + } + return Q_snprintf( name, len, "???" ); +} + +static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) +{ + char message[8192]; // match *nix Sys_Crash + int len = 0; + size_t i; + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + IMAGEHLP_LINE64 line; + DWORD dline = 0; + DWORD options; + CONTEXT context; + STACKFRAME64 stackframe; + DWORD image; + + context = *pInfo->ContextRecord; + + options = SymGetOptions(); + options |= SYMOPT_DEBUG; + options |= SYMOPT_LOAD_LINES; + SymSetOptions( options ); + + SymInitialize( process, NULL, TRUE ); + + ZeroMemory( &stackframe, sizeof( STACKFRAME64 )); + +#ifdef _M_IX86 + image = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context.Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Esp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + image = IMAGE_FILE_MACHINE_AMD64; + stackframe.AddrPC.Offset = context.Rip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Rsp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Rsp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + image = IMAGE_FILE_MACHINE_IA64; + stackframe.AddrPC.Offset = context.StIIP; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.IntSp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrBStore.Offset = context.RsBSP; + stackframe.AddrBStore.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.IntSp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_ARM + image = IMAGE_FILE_MACHINE_ARMNT; + stackframe.AddrPC.Offset = context.Pc; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.R11; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Sp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_ARM64 + image = IMAGE_FILE_MACHINE_ARM64; + stackframe.AddrPC.Offset = context.Pc; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Fp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Sp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif +#error +#endif + + len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", + Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); + + len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", + pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); + if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ) ) + { + len += Q_snprintf(message + len, 1024 - len, "Exception: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); + } + if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) + { + len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); + } + if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ) ) + { + len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); + } + for( i = 0; i < 25; i++ ) + { + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + BOOL result = StackWalk64( + image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL); + DWORD64 displacement = 0; + + if( !result ) + break; + + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + len += Q_snprintf( message + len, 1024 - len, "% 2d %p", + i, (void*)stackframe.AddrPC.Offset ); + if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ) ) + { + len += Q_snprintf( message + len, 1024 - len, " %s ", symbol->Name ); + } + if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) + { + len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", + (char*)line.FileName, (int)line.LineNumber, (int)dline); + } + len += Q_snprintf( message + len, 1024 - len, "("); + len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); + len += Q_snprintf( message + len, 1024 - len, ")\n"); + } + +#if XASH_SDL == 2 + if( host.type != HOST_DEDICATED ) // let system to restart server automaticly + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); +#endif + + Sys_PrintLog( message ); + + SymCleanup( process ); +} + +static void Sys_GetProcessName( char *processName, size_t bufferSize ) +{ + char fullpath[MAX_PATH]; + + GetModuleBaseName( GetCurrentProcess(), NULL, fullpath, sizeof( fullpath ) - 1 ); + COM_FileBase( fullpath, processName, bufferSize ); +} + +static void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName, size_t bufferSize ) +{ + time_t currentUtcTime = time( NULL ); + struct tm *currentLocalTime = localtime( ¤tUtcTime ); + + Q_snprintf( mdmpFileName, bufferSize, "%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp", + processName, + Q_buildcommit(), + currentLocalTime->tm_year + 1900, + currentLocalTime->tm_mon + 1, + currentLocalTime->tm_mday, + currentLocalTime->tm_hour, + currentLocalTime->tm_min, + currentLocalTime->tm_sec); +} + +static qboolean Sys_WriteMinidump(PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType) +{ + HRESULT errorCode; + string processName; + string mdmpFileName; + MINIDUMP_EXCEPTION_INFORMATION minidumpInfo; + + Sys_GetProcessName( processName, sizeof( processName )); + Sys_GetMinidumpFileName( processName, mdmpFileName, sizeof( mdmpFileName )); + + SetLastError( NOERROR ); + HANDLE fileHandle = CreateFile( + mdmpFileName, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + errorCode = HRESULT_FROM_WIN32( GetLastError( )); + if( !SUCCEEDED( errorCode )) { + CloseHandle( fileHandle ); + return false; + } + + minidumpInfo.ThreadId = GetCurrentThreadId(); + minidumpInfo.ExceptionPointers = exceptionInfo; + minidumpInfo.ClientPointers = FALSE; + + qboolean status = MiniDumpWriteDump( + GetCurrentProcess(), GetCurrentProcessId(), fileHandle, + minidumpType, &minidumpInfo, NULL, NULL); + + CloseHandle( fileHandle ); + return status; +} + +#endif /* DBGHELP */ + +static LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; + +static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) +{ + // save config + if( host.status != HOST_CRASHED ) + { + // check to avoid recursive call + host.crashed = true; + +#ifdef XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif // XASH_SDL + +#if DBGHELP + Sys_StackTrace( pInfo ); +#else + Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); +#endif + + if( host.type == HOST_NORMAL ) + CL_Crashed(); // tell client about crash + else host.status = HOST_CRASHED; + +#if DBGHELP + if( Sys_CheckParm( "-minidumps" )) + { + int minidumpFlags = ( + MiniDumpWithDataSegs | + MiniDumpWithCodeSegs | + MiniDumpWithHandleData | + MiniDumpWithFullMemory | + MiniDumpWithFullMemoryInfo | + MiniDumpWithIndirectlyReferencedMemory | + MiniDumpWithThreadInfo | + MiniDumpWithModuleHeaders); + + if( !Sys_WriteMinidump( pInfo, (MINIDUMP_TYPE)minidumpFlags )) { + // fallback method, create minidump with minimal info in it + Sys_WriteMinidump( pInfo, MiniDumpWithDataSegs ); + } + } +#endif + + if( host_developer.value <= 0 ) + { + // no reason to call debugger in release build - just exit + Sys_Quit(); + return EXCEPTION_CONTINUE_EXECUTION; + } + + // all other states keep unchanged to let debugger find bug + Sys_DestroyConsole(); + } + + if( oldFilter ) + return oldFilter( pInfo ); + return EXCEPTION_CONTINUE_EXECUTION; +} + +void Sys_SetupCrashHandler( void ) +{ + SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors + oldFilter = SetUnhandledExceptionFilter( Sys_Crash ); +} + +void Sys_RestoreCrashHandler( void ) +{ + // restore filter + if( oldFilter ) SetUnhandledExceptionFilter( oldFilter ); +} diff --git a/engine/wscript b/engine/wscript index 904e797b..cbc4b1fb 100644 --- a/engine/wscript +++ b/engine/wscript @@ -130,7 +130,6 @@ def configure(conf): conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) conf.define_cond('XASH_ENABLE_MAIN', conf.env.DISABLE_LAUNCHER) conf.define_cond('XASH_NO_ASYNC_NS_RESOLVE', conf.options.NO_ASYNC_RESOLVE) - conf.define_cond('DBGHELP', conf.env.DEST_OS == 'win32') conf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1 for refdll in conf.refdlls: