diff --git a/sources.cmake b/sources.cmake index ff4d3db2d..eff433e36 100644 --- a/sources.cmake +++ b/sources.cmake @@ -233,6 +233,7 @@ src/tracks/track_object_manager.cpp src/tracks/track_object_presentation.cpp src/tracks/track_sector.cpp src/utils/constants.cpp +src/utils/crash_reporting.cpp src/utils/leak_check.cpp src/utils/log.cpp src/utils/profiler.cpp @@ -499,6 +500,7 @@ src/tracks/track_sector.hpp src/utils/aligned_array.hpp src/utils/constants.hpp src/utils/interpolation_array.hpp +src/utils/crash_reporting.hpp src/utils/leak_check.hpp src/utils/log.hpp src/utils/no_copy.hpp diff --git a/src/main.cpp b/src/main.cpp index ef8335ffa..8c42e097d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -179,6 +179,7 @@ #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/constants.hpp" +#include "utils/crash_reporting.hpp" #include "utils/leak_check.hpp" #include "utils/log.hpp" #include "utils/translation.hpp" @@ -1303,6 +1304,8 @@ int main(int argc, char *argv[] ) google_breakpad::ExceptionHandler eh(L"C:\\Temp", NULL, ShowDumpResults, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL); #endif + CrashReporting::installHandlers(); + srand(( unsigned ) time( 0 )); try { diff --git a/src/utils/crash_reporting.cpp b/src/utils/crash_reporting.cpp new file mode 100644 index 000000000..811f39d09 --- /dev/null +++ b/src/utils/crash_reporting.cpp @@ -0,0 +1,330 @@ +// SuperTuxKart - a fun racing game with go-kart +// +// Copyright (C) 2013 Lionel Fuentes +// +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "crash_reporting.hpp" +#include "log.hpp" +#include + +#ifdef WIN32 + // --------------------- Windows version ----------------- + #include + #include + #include + #include + #include + + typedef BOOL (__stdcall *tSymCleanup)( + _In_ HANDLE hProcess + ); + typedef PVOID (__stdcall *tSymFunctionTableAccess64)( + _In_ HANDLE hProcess, + _In_ DWORD64 AddrBase + ); + typedef BOOL (__stdcall *tSymGetLineFromAddr64)( + _In_ HANDLE hProcess, + _In_ DWORD64 qwAddr, + _Out_ PDWORD pdwDisplacement, + _Out_ PIMAGEHLP_LINE64 Line64 + ); + typedef DWORD64 (__stdcall *tSymGetModuleBase64)( + _In_ HANDLE hProcess, + _In_ DWORD64 qwAddr + ); + typedef BOOL (__stdcall *tSymGetSymFromAddr64)( + _In_ HANDLE hProcess, + _In_ DWORD64 qwAddr, + _Out_opt_ PDWORD64 pdwDisplacement, + _Inout_ PIMAGEHLP_SYMBOL64 Symbol + ); + typedef BOOL (__stdcall *tSymInitialize)( + _In_ HANDLE hProcess, + _In_opt_ PCSTR UserSearchPath, + _In_ BOOL fInvadeProcess + ); + typedef DWORD (__stdcall *tSymSetOptions)( + _In_ DWORD SymOptions + ); + typedef BOOL (__stdcall *tStackWalk64)( + _In_ DWORD MachineType, + _In_ HANDLE hProcess, + _In_ HANDLE hThread, + _Inout_ LPSTACKFRAME64 StackFrame, + _Inout_ PVOID ContextRecord, + _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ); + typedef DWORD (__stdcall *tUnDecorateSymbolName)( + _In_ PCSTR name, + _Out_writes_(maxStringLength) PSTR outputString, + _In_ DWORD maxStringLength, + _In_ DWORD flags + ); + + namespace CrashReporting + { + void getCallStackWithContext(std::string& callstack, PCONTEXT pContext); + + void winCrashHandler(PCONTEXT pContext=NULL) + { + std::string callstack; + if(pContext) + getCallStackWithContext(callstack, pContext); + else + getCallStack(callstack); + + std::string msg = "SuperTuxKart crashed!\n" + "Please hit Ctrl+C to copy to clipboard and signal the problem\n" + "to the developers on our forum: http://forum.freegamedev.net/viewforum.php?f=16\n" + "\n" + "Call stack:\n"; + msg += callstack; + MessageBoxA(NULL, msg.c_str(), "SuperTuxKart crashed :/", MB_OK); + } + + LONG WINAPI sehHandler(_In_ struct _EXCEPTION_POINTERS *ExceptionInfo) + { + winCrashHandler(ExceptionInfo->ContextRecord); + return EXCEPTION_EXECUTE_HANDLER; + } + + void pureCallHandler() + { + winCrashHandler(); + } + + int newHandler( size_t ) + { + winCrashHandler(); + return 0; + } + + void invalidParameterHandler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t) + { + winCrashHandler(); + } + + void signalHandler(int code) + { + winCrashHandler(); + } + + void installHandlers() + { + // ----- Per-process handlers ----- + SetUnhandledExceptionFilter(sehHandler); // Top-level SEH handler + _set_purecall_handler(pureCallHandler); // Pure virtual function calls handler + + // Catch new operator memory allocation exceptions + _set_new_mode(1); // Force malloc() to call new handler too + _set_new_handler(newHandler); + + _set_invalid_parameter_handler(invalidParameterHandler); // Catch invalid parameter exceptions. + //_set_security_error_handler(securityHandler); // Catch buffer overrun exceptions + + signal(SIGABRT, signalHandler); + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + + // ----- Per-thread handlers ----- + // TODO + } + + void getCallStackWithContext(std::string& callstack, PCONTEXT pContext) + { + HINSTANCE hImageHlpDll = LoadLibraryA("imagehlp.dll"); + if(!hImageHlpDll) + { + Log::warn("CrashReporting", "Failed to load DLL imagehlp.dll"); + callstack = "Crash reporting failed to load DLL imagehlp.dll"; + return; + } + + // Retrieve the DLL functions +#define GET_FUNC_PTR(FuncName) \ + t##FuncName _##FuncName = (t##FuncName)GetProcAddress(hImageHlpDll, #FuncName); \ + if(!_##FuncName) { \ + Log::warn("CrashReporting", "Failed to import symbol " #FuncName " from imagehlp.dll"); \ + FreeLibrary(hImageHlpDll); \ + return; \ + } + + GET_FUNC_PTR(SymCleanup ) + GET_FUNC_PTR(SymFunctionTableAccess64 ) + GET_FUNC_PTR(SymGetLineFromAddr64 ) + GET_FUNC_PTR(SymGetModuleBase64 ) + GET_FUNC_PTR(SymGetSymFromAddr64 ) + GET_FUNC_PTR(SymInitialize ) + GET_FUNC_PTR(SymSetOptions ) + GET_FUNC_PTR(StackWalk64 ) + GET_FUNC_PTR(UnDecorateSymbolName ) + +#undef GET_FUNC_PTR + + const HANDLE hProcess = GetCurrentProcess(); + const HANDLE hThread = GetCurrentThread(); + + // Initialize the symbol hander for the process + { + // Get the file path of the executable + char filepath[512]; + GetModuleFileNameA(NULL, filepath, sizeof(filepath)); + if(!filepath) + { + Log::warn("CrashReporting", "GetModuleFileNameA failed"); + FreeLibrary(hImageHlpDll); + return; + } + + // Only keep the directory + char* last_separator = strchr(filepath, '/'); + if(!last_separator) last_separator = strchr(filepath, '\\'); + if(last_separator) + last_separator[0] = '\0'; + + // Finally initialize the symbol handler. + BOOL bOk = _SymInitialize(hProcess, filepath ? filepath : NULL, TRUE); + if(!bOk) + { + Log::warn("CrashReporting", "SymInitialize() failed"); + FreeLibrary(hImageHlpDll); + return; + } + + _SymSetOptions(SYMOPT_LOAD_LINES); + } + + // Get the stack trace + { + // Initialize the IMAGEHLP_SYMBOL64 structure + const size_t MaxNameLength = 256; + IMAGEHLP_SYMBOL64* sym = (IMAGEHLP_SYMBOL64*)alloca(sizeof(IMAGEHLP_SYMBOL64) + MaxNameLength); + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = MaxNameLength; + + // Initialize the STACKFRAME structure so that it + // corresponds to the current function call + STACKFRAME64 stackframe; + memset(&stackframe, 0, sizeof(stackframe)); + stackframe.AddrPC.Offset = pContext->Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = pContext->Esp; + stackframe.AddrStack.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = pContext->Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + + const DWORD machine_type = IMAGE_FILE_MACHINE_I386; + + // Walk the stack + const int max_nb_calls = 32; + for(int i=0 ; i < max_nb_calls ; i++) + { + const BOOL stackframe_ok = _StackWalk64( machine_type, + hProcess, + hThread, + &stackframe, + pContext, + NULL, + _SymFunctionTableAccess64, + _SymGetModuleBase64, + NULL); + if(stackframe_ok) + { + // Decode the symbol and add it to the call stack + DWORD64 sym_displacement; + if(_SymGetSymFromAddr64( hProcess, + stackframe.AddrPC.Offset, + &sym_displacement, + sym)) + { + IMAGEHLP_LINE64 line64; + DWORD dwDisplacement = (DWORD)sym_displacement; + if(_SymGetLineFromAddr64(hProcess, stackframe.AddrPC.Offset, &dwDisplacement, &line64)) + { + callstack += "\n "; + + // Directory + filename -> filename only + const char* filename = line64.FileName; + const char* ptr = line64.FileName; + while(*ptr) + { + if(*ptr == '\\' || *ptr == '/') + filename = ptr+1; + ptr++; + } + callstack += filename; + callstack += ":"; + callstack += sym->Name; + + char str[128]; + _itoa(line64.LineNumber, str, 10); + callstack += ":"; + callstack += str; + } + else + { + callstack += "\n "; + callstack += sym->Name; + } + } + else + callstack += "\n "; + } + else + break; // done + } + } + + FreeLibrary(hImageHlpDll); + } + + + void getCallStack(std::string& callstack) + { + // Get the current CONTEXT + // NB: this code is ONLY VALID FOR X86 (32 bit)! + CONTEXT ctx; + memset(&ctx, '\0', sizeof(ctx)); + ctx.ContextFlags = CONTEXT_FULL; + __asm call x + __asm x: pop eax // get eip (can't directly use mov) + __asm mov ctx.Eip, eax + __asm mov ctx.Ebp, ebp + __asm mov ctx.Esp, esp + + getCallStackWithContext(callstack, &ctx); + } + } // end namespace CrashReporting + +#else + // --------------------- Unix version ----------------------- + namespace CrashReporting + { + void installHandlers() + { + // TODO! + } + + void getCallStack(std::string& callstack) + { + // TODO! + } + } // end namespace CrashReporting + +#endif diff --git a/src/utils/crash_reporting.hpp b/src/utils/crash_reporting.hpp new file mode 100644 index 000000000..9302861b8 --- /dev/null +++ b/src/utils/crash_reporting.hpp @@ -0,0 +1,31 @@ +// SuperTuxKart - a fun racing game with go-kart +// +// Copyright (C) 2013 Lionel Fuentes +// +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_CRASH_REPORTING_HPP +#define HEADER_CRASH_REPORTING_HPP + +#include + +namespace CrashReporting +{ + void installHandlers(); + void getCallStack(std::string& callstack); +} + + +#endif // HEADER_CRASH_REPORTING_HPP