#include "CrashCatcher.h" #include "../util/Dictionary.h" #include #include #include USING_NS_BF; #pragma comment(lib, "version.lib") #pragma comment(lib, "comdlg32.lib") #pragma warning(disable:4091) #pragma warning(disable:4996) #include #ifdef BF64 typedef BOOL(__stdcall * SYMINITIALIZEPROC)(HANDLE, LPSTR, BOOL); typedef DWORD(__stdcall *SYMSETOPTIONSPROC)(DWORD); typedef BOOL(__stdcall *SYMCLEANUPPROC)(HANDLE); typedef LPCSTR(__stdcall *UNDECORATESYMBOLNAMEPROC)(LPCSTR, LPSTR, DWORD, DWORD); typedef BOOL(__stdcall * STACKWALKPROC) (DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE); typedef LPVOID(__stdcall *SYMFUNCTIONTABLEACCESSPROC)(HANDLE, DWORD64); typedef DWORD64 (__stdcall *SYMGETMODULEBASEPROC)(HANDLE, DWORD64); typedef BOOL(__stdcall *SYMGETSYMFROMADDRPROC)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64); typedef BOOL(__stdcall *SYMGETLINEFROMADDR)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64); #else typedef BOOL(__stdcall * SYMINITIALIZEPROC)(HANDLE, LPSTR, BOOL); typedef DWORD(__stdcall *SYMSETOPTIONSPROC)(DWORD); typedef BOOL(__stdcall *SYMCLEANUPPROC)(HANDLE); typedef LPCSTR(__stdcall *UNDECORATESYMBOLNAMEPROC)(LPCSTR, LPSTR, DWORD, DWORD); typedef BOOL(__stdcall * STACKWALKPROC) (DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE); typedef LPVOID(__stdcall *SYMFUNCTIONTABLEACCESSPROC)(HANDLE, DWORD); typedef DWORD(__stdcall *SYMGETMODULEBASEPROC)(HANDLE, DWORD); typedef BOOL(__stdcall *SYMGETSYMFROMADDRPROC)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL); typedef BOOL(__stdcall *SYMGETLINEFROMADDR)(HANDLE hProcess, DWORD qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE Line64); #endif static HMODULE gImageHelpLib = NULL; static SYMINITIALIZEPROC gSymInitialize = NULL; static SYMSETOPTIONSPROC gSymSetOptions = NULL; static UNDECORATESYMBOLNAMEPROC gUnDecorateSymbolName = NULL; static SYMCLEANUPPROC gSymCleanup = NULL; static STACKWALKPROC gStackWalk = NULL; static SYMFUNCTIONTABLEACCESSPROC gSymFunctionTableAccess = NULL; static SYMGETMODULEBASEPROC gSymGetModuleBase = NULL; static SYMGETSYMFROMADDRPROC gSymGetSymFromAddr = NULL; static SYMGETLINEFROMADDR gSymGetLineFromAddr = NULL; static bool CreateMiniDump(EXCEPTION_POINTERS* pep, const StringImpl& filePath); static bool LoadImageHelp() { gImageHelpLib = LoadLibraryA("IMAGEHLP.DLL"); if (!gImageHelpLib) return false; gSymInitialize = (SYMINITIALIZEPROC)GetProcAddress(gImageHelpLib, "SymInitialize"); if (!gSymInitialize) return false; gSymSetOptions = (SYMSETOPTIONSPROC)GetProcAddress(gImageHelpLib, "SymSetOptions"); if (!gSymSetOptions) return false; gSymCleanup = (SYMCLEANUPPROC)GetProcAddress(gImageHelpLib, "SymCleanup"); if (!gSymCleanup) return false; gUnDecorateSymbolName = (UNDECORATESYMBOLNAMEPROC)GetProcAddress(gImageHelpLib, "UnDecorateSymbolName"); if (!gUnDecorateSymbolName) return false; gStackWalk = (STACKWALKPROC)GetProcAddress(gImageHelpLib, "StackWalk"); if (!gStackWalk) return false; gSymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC)GetProcAddress(gImageHelpLib, "SymFunctionTableAccess"); if (!gSymFunctionTableAccess) return false; gSymGetModuleBase = (SYMGETMODULEBASEPROC)GetProcAddress(gImageHelpLib, "SymGetModuleBase"); if (!gSymGetModuleBase) return false; gSymGetSymFromAddr = (SYMGETSYMFROMADDRPROC)GetProcAddress(gImageHelpLib, "SymGetSymFromAddr"); if (!gSymGetSymFromAddr) return false; gSymGetLineFromAddr = (SYMGETLINEFROMADDR)GetProcAddress(gImageHelpLib, "SymGetLineFromAddr64"); if (!gSymGetLineFromAddr) return false; gSymSetOptions(SYMOPT_DEFERRED_LOADS); // Get image filename of the main executable char filepath[MAX_PATH], *lastdir, *pPath; DWORD filepathlen = GetModuleFileNameA(NULL, filepath, sizeof(filepath)); lastdir = strrchr(filepath, '/'); if (lastdir == NULL) lastdir = strrchr(filepath, '\\'); if (lastdir != NULL) lastdir[0] = '\0'; // Initialize the symbol table routines, supplying a pointer to the path pPath = filepath; if (filepath[0] == 0) pPath = NULL; if (!gSymInitialize(GetCurrentProcess(), pPath, TRUE)) return false; return true; } static HWND gDebugButtonWindow = NULL; static HWND gYesButtonWindow = NULL; static HWND gNoButtonWindow = NULL; static bool gExiting = false; static LRESULT CALLBACK SEHWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: { HWND hwndCtl = (HWND)lParam; if (hwndCtl == gYesButtonWindow) { WCHAR fileName[MAX_PATH]; fileName[0] = 0; OPENFILENAMEW openFileName = { 0 }; openFileName.hInstance = ::GetModuleHandle(NULL); openFileName.hwndOwner = hWnd; openFileName.lStructSize = sizeof(OPENFILENAMEW); openFileName.lpstrDefExt = L".dmp"; openFileName.lpstrFilter = L"Crash Dump (*.dmp)\0*.dmp\0All files (*.*)\0*.*\0\0"; openFileName.lpstrFile = fileName; openFileName.nMaxFile = MAX_PATH; openFileName.Flags = OFN_EXPLORER | OFN_ENABLESIZING; openFileName.lpstrTitle = L"Save Crash Dump"; if (::GetSaveFileNameW(&openFileName)) { CreateMiniDump(CrashCatcher::Get()->mExceptionPointers, UTF8Encode(fileName)); } } else if (hwndCtl == gNoButtonWindow) { if (!CrashCatcher::Get()->mRelaunchCmd.IsEmpty()) { SHELLEXECUTEINFOW shellExecuteInfo = { 0 }; shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOW); shellExecuteInfo.nShow = SW_SHOWNORMAL; String cmd = CrashCatcher::Get()->mRelaunchCmd; String file; bool nameQuoted = cmd[0] == '\"'; int i; for (i = (nameQuoted ? 1 : 0); cmd[i] != 0; i++) { wchar_t c = cmd[i]; if (((nameQuoted) && (c == '"')) || ((!nameQuoted) && (c == ' '))) { i++; break; } file += cmd[i]; } const char* useParamsPtr = cmd.c_str(); useParamsPtr += i; while (*useParamsPtr == L' ') useParamsPtr++; auto fileW = UTF8Decode(file); shellExecuteInfo.lpFile = fileW.c_str(); auto paramsW = UTF8Decode(useParamsPtr); shellExecuteInfo.lpParameters = paramsW.c_str(); BOOL success = ::ShellExecuteExW(&shellExecuteInfo); } CrashCatcher::Get()->mCloseRequested = true; gExiting = true; } else if (hwndCtl == gDebugButtonWindow) { CrashCatcher::Get()->mDebugError = true; gExiting = true; } } break; case WM_CLOSE: CrashCatcher::Get()->mCloseRequested = true; gExiting = true; return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } static void ShowErrorDialog(const StringImpl& errorTitle, const StringImpl& errorText, const StringImpl& relaunchCmd) { bool gUseDefaultFonts; HINSTANCE gHInstance = ::GetModuleHandle(NULL); OSVERSIONINFO aVersionInfo; aVersionInfo.dwOSVersionInfoSize = sizeof(aVersionInfo); GetVersionEx(&aVersionInfo); // Setting fonts on 98 causes weirdo crash things in GDI upon the second crash. // That's no good. gUseDefaultFonts = aVersionInfo.dwPlatformId != VER_PLATFORM_WIN32_NT; int aHeight = -MulDiv(8, 96, 72); HFONT gDialogFont = ::CreateFontA(aHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Tahoma"); aHeight = -MulDiv(10, 96, 72); HFONT gBoldFont = ::CreateFontA(aHeight, 0, 0, 0, FW_BOLD, FALSE, FALSE, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Tahoma"); ::SetCursor(::LoadCursor(NULL, IDC_ARROW)); String gErrorTitle = errorTitle; String gErrorText = errorText; WNDCLASSW wc; wc.style = 0; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = ::GetSysColorBrush(COLOR_BTNFACE); wc.hCursor = ::LoadCursor(NULL, IDC_ARROW); wc.hIcon = ::LoadIcon(NULL, IDI_ERROR); wc.hInstance = gHInstance; wc.lpfnWndProc = SEHWindowProc; wc.lpszClassName = L"SEHWindow"; wc.lpszMenuName = NULL; RegisterClassW(&wc); RECT aRect; aRect.left = 0; aRect.top = 0; aRect.right = 500; aRect.bottom = 400; DWORD aWindowStyle = WS_CLIPCHILDREN | WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; RECT windowRect = aRect; BOOL worked = AdjustWindowRect(&windowRect, aWindowStyle, FALSE); HWND aHWnd = ::CreateWindowExW(0, L"SEHWindow", L"Fatal Error!", aWindowStyle, 64, 64, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, NULL, NULL, gHInstance, 0); int textHeight = 30; HWND aLabelWindow = ::CreateWindowW(L"EDIT", L"An unexpected error has occured!", WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_READONLY, 8, 8, aRect.right - 8 - 8, textHeight, aHWnd, NULL, gHInstance, 0); int aFontHeight = -MulDiv(9, 96, 72); HFONT aBoldArialFont = CreateFontA(aFontHeight, 0, 0, 0, FW_BOLD, 0, 0, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Arial"); if (!gUseDefaultFonts) SendMessage(aLabelWindow, WM_SETFONT, (WPARAM)aBoldArialFont, 0); HWND anEditWindow = CreateWindowA("EDIT", errorText.c_str(), WS_VISIBLE | WS_CHILD | ES_MULTILINE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_READONLY, 8, textHeight + 8, aRect.right - 8 - 8, aRect.bottom - textHeight - 24 - 8 - 8 - 8, aHWnd, NULL, gHInstance, 0); aFontHeight = -MulDiv(8, 96, 72); HFONT aCourierNewFont = CreateFontA(aFontHeight, 0, 0, 0, FW_NORMAL, 0, 0, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Courier New"); if (!gUseDefaultFonts) SendMessage(anEditWindow, WM_SETFONT, (WPARAM)aCourierNewFont, 0); aWindowStyle = WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | BS_PUSHBUTTON; //if (mApp == NULL) //aWindowStyle |= WS_DISABLED; //#ifdef _DEBUG bool doDebugButton = true; // #else // bool doDebugButton = false; // #endif //bool canSubmit = mAllowSubmit && !mSubmitHost.empty(); bool canSubmit = true; int aNumButtons = 1 + (doDebugButton ? 1 : 0) + (canSubmit ? 1 : 0); int aButtonWidth = (aRect.right - 8 - 8 - (aNumButtons - 1) * 8) / aNumButtons; int aCurX = 8; if (canSubmit) { gYesButtonWindow = CreateWindowA("BUTTON", "Save Crash Dump...", aWindowStyle, aCurX, aRect.bottom - 24 - 8, aButtonWidth, 24, aHWnd, NULL, gHInstance, 0); if (!gUseDefaultFonts) SendMessage(gYesButtonWindow, WM_SETFONT, (WPARAM)aBoldArialFont, 0); aCurX += aButtonWidth + 8; } if (doDebugButton) { gDebugButtonWindow = CreateWindowA("BUTTON", "Debug", aWindowStyle, aCurX, aRect.bottom - 24 - 8, aButtonWidth, 24, aHWnd, NULL, gHInstance, 0); if (!gUseDefaultFonts) SendMessage(gDebugButtonWindow, WM_SETFONT, (WPARAM)aBoldArialFont, 0); aCurX += aButtonWidth + 8; } gNoButtonWindow = CreateWindowA("BUTTON", relaunchCmd.IsEmpty() ? "Close Now" : "Relaunch", WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | BS_PUSHBUTTON, aCurX, aRect.bottom - 24 - 8, aButtonWidth, 24, aHWnd, NULL, gHInstance, 0); if (!gUseDefaultFonts) SendMessage(gNoButtonWindow, WM_SETFONT, (WPARAM)aBoldArialFont, 0); ShowWindow(aHWnd, SW_NORMAL); MSG msg; while ((GetMessage(&msg, NULL, 0, 0) > 0) && (!gExiting)) { TranslateMessage(&msg); DispatchMessage(&msg); } DestroyWindow(aHWnd); DeleteObject(gDialogFont); DeleteObject(gBoldFont); DeleteObject(aBoldArialFont); DeleteObject(aCourierNewFont); } static bool GetLogicalAddress(void* addr, char* szModule, DWORD len, uintptr& section, uintptr& offset) { MEMORY_BASIC_INFORMATION mbi; if (!VirtualQuery(addr, &mbi, sizeof(mbi))) return false; uintptr hMod = (uintptr)mbi.AllocationBase; if (hMod == NULL) { szModule[0] = 0; section = 0; offset = 0; return false; } if (!GetModuleFileNameA((HMODULE)hMod, szModule, len)) return false; // Point to the DOS header in memory PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; // From the DOS header, find the NT (PE) header PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)((uint8*)hMod + pDosHdr->e_lfanew); PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr); uintptr rva = (uintptr)addr - hMod; // RVA is offset from module load address // Iterate through the section table, looking for the one that encompasses // the linear address. for (unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++) { uintptr sectionStart = pSection->VirtualAddress; uintptr sectionEnd = sectionStart + BF_MAX(pSection->SizeOfRawData, pSection->Misc.VirtualSize); // Is the address in this section??? if ((rva >= sectionStart) && (rva <= sectionEnd)) { // Yes, address is in the section. Calculate section and offset, // and store in the "section" & "offset" params, which were // passed by reference. section = i + 1; offset = rva - sectionStart; return true; } } return false; // Should never get here! } static BOOL CALLBACK MyMiniDumpCallback( PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput ) { BOOL bRet = FALSE; // Check parameters if (pInput == 0) return FALSE; if (pOutput == 0) return FALSE; // Process the callbacks switch (pInput->CallbackType) { case IncludeModuleCallback: { // Include the module into the dump bRet = TRUE; } break; case IncludeThreadCallback: { // Include the thread into the dump bRet = TRUE; } break; case ModuleCallback: { // Does the module have ModuleReferencedByMemory flag set ? if (!(pOutput->ModuleWriteFlags & ModuleReferencedByMemory)) { // No, it does not - exclude it //wprintf(L"Excluding module: %s \n", pInput->Module.FullPath); pOutput->ModuleWriteFlags &= (~ModuleWriteModule); } bRet = TRUE; } break; case ThreadCallback: { // Include all thread information into the minidump bRet = TRUE; } break; case ThreadExCallback: { // Include this information bRet = TRUE; } break; case MemoryCallback: { // We do not include any information here -> return FALSE bRet = FALSE; } break; case CancelCallback: break; } return bRet; } static bool CreateMiniDump(EXCEPTION_POINTERS* pep, const StringImpl& filePath) { // Open the file typedef BOOL(*PDUMPFN)( HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); HANDLE hFile = CreateFileW(UTF8Decode(filePath).c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); HMODULE h = ::LoadLibrary(L"DbgHelp.dll"); PDUMPFN pFn = (PDUMPFN)GetProcAddress(h, "MiniDumpWriteDump"); if (pFn == NULL) return false; if ((hFile == NULL) || (hFile == INVALID_HANDLE_VALUE)) return false; // Create the minidump MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId = GetCurrentThreadId(); mdei.ExceptionPointers = pep; mdei.ClientPointers = TRUE; MINIDUMP_CALLBACK_INFORMATION mci; mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MyMiniDumpCallback; mci.CallbackParam = 0; MINIDUMP_TYPE mdt = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); CrashCatcher* crashCatcher = CrashCatcher::Get(); MINIDUMP_USER_STREAM user_info_stream = { 0xBEEF00, (ULONG)crashCatcher->mCrashInfo.length(), (void*)crashCatcher->mCrashInfo.c_str() }; MINIDUMP_USER_STREAM_INFORMATION user_stream_info = { 1, &user_info_stream }; BOOL rv = (*pFn)(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, &user_stream_info, &mci); // Close the file CloseHandle(hFile); return true; } static String ImageHelpWalk(PCONTEXT theContext, int theSkipCount) { //char aBuffer[2048]; String aDebugDump; STACKFRAME sf; memset(&sf, 0, sizeof(sf)); // Initialize the STACKFRAME structure for the first call. This is only // necessary for Intel CPUs, and isn't mentioned in the documentation. #ifdef BF64 sf.AddrPC.Offset = theContext->Rip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = theContext->Rsp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = theContext->Rbp; sf.AddrFrame.Mode = AddrModeFlat; #else sf.AddrPC.Offset = theContext->Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = theContext->Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = theContext->Ebp; sf.AddrFrame.Mode = AddrModeFlat; #endif int aLevelCount = 0; CONTEXT ctx = *theContext; struct ModuleInfo { }; Dictionary moduleInfoMap; for (;;) { #ifdef BF64 DWORD machineType = IMAGE_FILE_MACHINE_AMD64; PCONTEXT ctxPtr = &ctx; #else DWORD machineType = IMAGE_FILE_MACHINE_I386; PCONTEXT ctxPtr = NULL; #endif if (!gStackWalk(machineType, GetCurrentProcess(), GetCurrentThread(), &sf, ctxPtr, NULL, gSymFunctionTableAccess, gSymGetModuleBase, 0)) { //DWORD lastErr = GetLastError(); //sprintf(aBuffer, "StackWalk failed (error %d)\r\n", lastErr); //aDebugDump += aBuffer; break; } if ((aLevelCount > 0) && ((sf.AddrFrame.Offset == 0) || (sf.AddrPC.Offset == 0))) break; if (theSkipCount > 0) { theSkipCount--; continue; } BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 512]; PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer; pSymbol->SizeOfStruct = sizeof(symbolBuffer); pSymbol->MaxNameLength = 512; // Displacement of the input address, relative to the start of the symbol #ifdef BF64 DWORD64 symDisplacement = 0; #else DWORD symDisplacement = 0; #endif HANDLE hProcess = GetCurrentProcess(); char szModule[MAX_PATH]; szModule[0] = 0; uintptr section = 0, offset = 0; GetLogicalAddress((PVOID)sf.AddrPC.Offset, szModule, sizeof(szModule), section, offset); bool forceFail = false; if ((gSymGetSymFromAddr(hProcess, sf.AddrPC.Offset, &symDisplacement, pSymbol)) && (!forceFail)) { char aUDName[256]; gUnDecorateSymbolName(pSymbol->Name, aUDName, 256, UNDNAME_NO_ALLOCATION_MODEL | UNDNAME_NO_ALLOCATION_LANGUAGE | UNDNAME_NO_MS_THISTYPE | UNDNAME_NO_ACCESS_SPECIFIERS | UNDNAME_NO_THISTYPE | UNDNAME_NO_MEMBER_TYPE | UNDNAME_NO_RETURN_UDT_MODEL | UNDNAME_NO_THROW_SIGNATURES | UNDNAME_NO_SPECIAL_SYMS); String dispName = aUDName; if (dispName.StartsWith("_bf::")) { dispName.Remove(0, 5); dispName.Replace("::", "."); } aDebugDump += StrFormat("%@ %@ %s %hs+%X\r\n", sf.AddrFrame.Offset, sf.AddrPC.Offset, GetFileName(szModule).c_str(), dispName.c_str(), symDisplacement); DWORD displacement = 0; #ifdef BF64 IMAGEHLP_LINE64 lineInfo = { 0 }; lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); #else IMAGEHLP_LINE lineInfo = { 0 }; lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE); #endif if (gSymGetLineFromAddr(hProcess, sf.AddrPC.Offset, &displacement, &lineInfo)) { aDebugDump += StrFormat(" at %s:%d\r\n", lineInfo.FileName, lineInfo.LineNumber); } } else // No symbol found. Print out the logical address instead. { // ModuleInfo* moduleInfo = NULL; // if (moduleInfoMap.TryAdd(szModule, NULL, &moduleInfo)) // { // // } aDebugDump += StrFormat("%@ %@ %04X:%@ %s\r\n", sf.AddrFrame.Offset, sf.AddrPC.Offset, section, offset, GetFileName(szModule).c_str()); } aDebugDump += StrFormat(" Params: %@ %@ %@ %@\r\n", sf.Params[0], sf.Params[1], sf.Params[2], sf.Params[3]); aDebugDump += "\r\n"; aLevelCount++; } return aDebugDump; } static String GetSysInfo() { return ""; } static String GetVersion(const StringImpl& fileName) { String verStr = ""; bool bReturn = false; DWORD dwReserved = 0; DWORD dwBufferSize = GetFileVersionInfoSizeA(fileName.c_str(), &dwReserved); struct TRANSARRAY { WORD wLanguageID; WORD wCharacterSet; }; if (dwBufferSize > 0) { LPVOID pBuffer = (void*)malloc(dwBufferSize); if (pBuffer != (void*)NULL) { UINT nInfoSize = 0, nFixedLength = 0; LPSTR lpVersion = NULL; void* lpFixedPointer; TRANSARRAY* lpTransArray; GetFileVersionInfoA(fileName.c_str(), dwReserved, dwBufferSize, pBuffer); VerQueryValueA(pBuffer, "\\VarFileInfo\\Translation", &lpFixedPointer, &nFixedLength); lpTransArray = (TRANSARRAY*)lpFixedPointer; String langStr = StrFormat( "\\StringFileInfo\\%04x%04x\\FileVersion", lpTransArray[0].wLanguageID, lpTransArray[0].wCharacterSet); VerQueryValueA(pBuffer, langStr.c_str(), (void**)&lpVersion, &nInfoSize); if (nInfoSize != 0) { verStr += "File Version: "; verStr += lpVersion; verStr += "\r\n"; } langStr = StrFormat( "\\StringFileInfo\\%04x%04x\\ProductVersion", lpTransArray[0].wLanguageID, lpTransArray[0].wCharacterSet); VerQueryValueA(pBuffer, langStr.c_str(), (void**)&lpVersion, &nInfoSize); if (nInfoSize != 0) { verStr += "Product Version: "; verStr += lpVersion; verStr += "\r\n"; } free(pBuffer); } } return verStr; } static void DoHandleDebugEvent(LPEXCEPTION_POINTERS lpEP) { auto crashCatcher = CrashCatcher::Get(); if (crashCatcher->mCrashed) return; crashCatcher->mCrashed = true; HMODULE hMod = GetModuleHandleA(NULL); PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)((uint8*)hMod + pDosHdr->e_lfanew); bool isCLI = pNtHdr->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI; if (CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_GUI) isCLI = false; else if ((CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_Console) || (CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_PrintOnly)) isCLI = true; bool hasImageHelp = LoadImageHelp(); String anErrorTitle; String aDebugDump; String& crashInfo = CrashCatcher::Get()->mCrashInfo; char aBuffer[2048]; if (isCLI) aDebugDump += "**** FATAL APPLICATION ERROR ****\n"; for (auto func : CrashCatcher::Get()->mCrashInfoFuncs) func(); CHAR path[MAX_PATH]; GetModuleFileNameA(NULL, path, MAX_PATH); crashInfo += "\nExecutable: "; crashInfo += path; crashInfo += "\r\n"; crashInfo += GetVersion(path); aDebugDump.Append(crashInfo); for (int i = 0; i < (int)aDebugDump.length(); i++) { char c = aDebugDump[i]; if (c == '\n') { aDebugDump.Insert(i, '\r'); i++; } else if (c == '\t') { aDebugDump[i] = ' '; aDebugDump.Insert(i, ' '); } } // aDebugDump.Replace("\n", "\r\n"); // aDebugDump.Replace("\t", " "); if (!aDebugDump.IsEmpty()) { if (!aDebugDump.EndsWith("\n")) aDebugDump += "\r\n"; aDebugDump += "\r\n"; } WCHAR exeFilePathW[MAX_PATH]; exeFilePathW[0] = 0; ::GetModuleFileNameW(hMod, exeFilePathW, MAX_PATH); String exeFilePath = UTF8Encode(exeFilePathW); String exeDir = GetFileDir(exeFilePath); String crashPath = exeDir + "\\CrashDumps"; if (BfpDirectory_Exists(crashPath.c_str())) { crashPath += "\\" + GetFileName(exeFilePath); crashPath.RemoveToEnd((int)crashPath.length() - 4); crashPath += "_"; time_t curTime = time(NULL); auto time_info = localtime(&curTime); crashPath += StrFormat("%4d%02d%02d_%02d%02d%02d", time_info->tm_year + 1900, time_info->tm_mon + 1, time_info->tm_mday, time_info->tm_hour, time_info->tm_min, time_info->tm_sec); crashPath += ".dmp"; if (CreateMiniDump(lpEP, crashPath)) { aDebugDump += StrFormat("Crash minidump saved as %s\n", crashPath.c_str()); } } /////////////////////////// // first name the exception struct { DWORD dwExceptionCode; char* szMessage; } gMsgTable[] = { { STATUS_SEGMENT_NOTIFICATION, "Segment Notification" }, { STATUS_BREAKPOINT, "Breakpoint" }, { STATUS_SINGLE_STEP, "Single step" }, { STATUS_WAIT_0, "Wait 0" }, { STATUS_ABANDONED_WAIT_0, "Abandoned Wait 0" }, { STATUS_USER_APC, "User APC" }, { STATUS_TIMEOUT, "Timeout" }, { STATUS_PENDING, "Pending" }, { STATUS_GUARD_PAGE_VIOLATION, "Guard Page Violation" }, { STATUS_DATATYPE_MISALIGNMENT, "Data Type Misalignment" }, { STATUS_ACCESS_VIOLATION, "Access Violation" }, { STATUS_IN_PAGE_ERROR, "In Page Error" }, { STATUS_NO_MEMORY, "No Memory" }, { STATUS_ILLEGAL_INSTRUCTION, "Illegal Instruction" }, { STATUS_NONCONTINUABLE_EXCEPTION, "Noncontinuable Exception" }, { STATUS_INVALID_DISPOSITION, "Invalid Disposition" }, { STATUS_ARRAY_BOUNDS_EXCEEDED, "Array Bounds Exceeded" }, { STATUS_FLOAT_DENORMAL_OPERAND, "Float Denormal Operand" }, { STATUS_FLOAT_DIVIDE_BY_ZERO, "Divide by Zero" }, { STATUS_FLOAT_INEXACT_RESULT, "Float Inexact Result" }, { STATUS_FLOAT_INVALID_OPERATION, "Float Invalid Operation" }, { STATUS_FLOAT_OVERFLOW, "Float Overflow" }, { STATUS_FLOAT_STACK_CHECK, "Float Stack Check" }, { STATUS_FLOAT_UNDERFLOW, "Float Underflow" }, { STATUS_INTEGER_DIVIDE_BY_ZERO, "Integer Divide by Zero" }, { STATUS_INTEGER_OVERFLOW, "Integer Overflow" }, { STATUS_PRIVILEGED_INSTRUCTION, "Privileged Instruction" }, { STATUS_STACK_OVERFLOW, "Stack Overflow" }, { STATUS_CONTROL_C_EXIT, "Ctrl+C Exit" }, { 0xFFFFFFFF, "" } }; char *szName = NULL; for (int i = 0; gMsgTable[i].dwExceptionCode != 0xFFFFFFFF; i++) { if (gMsgTable[i].dwExceptionCode == lpEP->ExceptionRecord->ExceptionCode) { szName = gMsgTable[i].szMessage; break; } } if (szName != NULL) { aDebugDump += StrFormat("Exception: %s (code 0x%x) at address %@ in thread %X\r\n", szName, lpEP->ExceptionRecord->ExceptionCode, lpEP->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); } else { aDebugDump += StrFormat("Unknown exception: (code 0x%x) at address %@ in thread %X\r\n", lpEP->ExceptionRecord->ExceptionCode, lpEP->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); } /////////////////////////////////////////////////////////// // Get logical address of the module where exception occurs uintptr section, offset; GetLogicalAddress(lpEP->ExceptionRecord->ExceptionAddress, aBuffer, sizeof(aBuffer), section, offset); aDebugDump += StrFormat("Logical Address: %04X:%@\r\n", section, offset); aDebugDump += "\r\n"; anErrorTitle = StrFormat("Exception at %04X:%08X", section, offset); String aWalkString; if (hasImageHelp) aWalkString = ImageHelpWalk(lpEP->ContextRecord, 0); /*if (aWalkString.length() == 0) aWalkString = IntelWalk(lpEP->ContextRecord, 0);*/ aDebugDump += aWalkString; aDebugDump += "\r\n"; #ifdef BF64 aDebugDump += StrFormat("RAX:%@ RBX:%@ RCX:%@ RDX:%@ RSI:%@ RDI:%@\r\n", lpEP->ContextRecord->Rax, lpEP->ContextRecord->Rbx, lpEP->ContextRecord->Rcx, lpEP->ContextRecord->Rdx, lpEP->ContextRecord->Rsi, lpEP->ContextRecord->Rdi); aDebugDump += StrFormat("R8:%@ R9:%@ R10:%@ R11:%@\r\nR12:%@ R13:%@ R14:%@ R15:%@\r\n", lpEP->ContextRecord->R8, lpEP->ContextRecord->R9, lpEP->ContextRecord->R10, lpEP->ContextRecord->R11, lpEP->ContextRecord->R12, lpEP->ContextRecord->R13, lpEP->ContextRecord->R14, lpEP->ContextRecord->R15); aDebugDump += StrFormat("EIP:%@ ESP:%@ EBP:%@\r\n", lpEP->ContextRecord->Rip, lpEP->ContextRecord->Rsp, lpEP->ContextRecord->Rbp); aDebugDump += StrFormat("CS:%04X SS:%04X DS:%04X ES:%04X FS:%04X GS:%04X\r\n", lpEP->ContextRecord->SegCs, lpEP->ContextRecord->SegSs, lpEP->ContextRecord->SegDs, lpEP->ContextRecord->SegEs, lpEP->ContextRecord->SegFs, lpEP->ContextRecord->SegGs); aDebugDump += StrFormat("Flags:%@\r\n", lpEP->ContextRecord->EFlags); #else aDebugDump += StrFormat("EAX:%08X EBX:%08X ECX:%08X EDX:%08X ESI:%08X EDI:%08X\r\n", lpEP->ContextRecord->Eax, lpEP->ContextRecord->Ebx, lpEP->ContextRecord->Ecx, lpEP->ContextRecord->Edx, lpEP->ContextRecord->Esi, lpEP->ContextRecord->Edi); aDebugDump += StrFormat("EIP:%08X ESP:%08X EBP:%08X\r\n", lpEP->ContextRecord->Eip, lpEP->ContextRecord->Esp, lpEP->ContextRecord->Ebp); aDebugDump += StrFormat("CS:%04X SS:%04X DS:%04X ES:%04X FS:%04X GS:%04X\r\n", lpEP->ContextRecord->SegCs, lpEP->ContextRecord->SegSs, lpEP->ContextRecord->SegDs, lpEP->ContextRecord->SegEs, lpEP->ContextRecord->SegFs, lpEP->ContextRecord->SegGs); aDebugDump += StrFormat("Flags:%08X\r\n", lpEP->ContextRecord->EFlags); #endif aDebugDump += "\r\n"; aDebugDump += GetSysInfo(); /*if (mApp != NULL) { String aGameSEHInfo = mApp->GetGameSEHInfo(); if (aGameSEHInfo.length() > 0) { aDebugDump += "\r\n"; aDebugDump += aGameSEHInfo; } mApp->CopyToClipboard(aDebugDump); } if (hasImageHelp) GetSymbolsFromMapFile(aDebugDump);*/ if (isCLI) { aDebugDump += "\n"; //fwrite(aDebugDump.c_str(), 1, aDebugDump.length(), stderr); //fflush(stderr); DWORD bytesWritten; ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), aDebugDump.c_str(), (DWORD)aDebugDump.length(), &bytesWritten, NULL); } else ShowErrorDialog(anErrorTitle, aDebugDump, crashCatcher->mRelaunchCmd); } CrashCatcher::CrashCatcher() { mCrashed = false; mInitialized = false; mExceptionPointers = NULL; mPreviousFilter = NULL; mDebugError = false; mCrashReportKind = BfpCrashReportKind_Default; mCloseRequested = false; } static long __stdcall SEHFilter(LPEXCEPTION_POINTERS lpExceptPtr) { OutputDebugStrF("SEH Filter! CraskReportKind:%d\n", CrashCatcher::Get()->mCrashReportKind); if (CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_System) return EXCEPTION_CONTINUE_SEARCH; if (CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_None) { OutputDebugStrF("Silent Exiting\n"); ::TerminateProcess(GetCurrentProcess(), lpExceptPtr->ExceptionRecord->ExceptionCode); } AutoCrit autoCrit(CrashCatcher::Get()->mBfpCritSect); //::ExitProcess(); //quick_exit(1); if (!CrashCatcher::Get()->mCrashed) { CrashCatcher::Get()->mExceptionPointers = lpExceptPtr; //CreateMiniDump(lpExceptPtr); DoHandleDebugEvent(lpExceptPtr); } //if (!gDebugError) //SetErrorMode(SEM_NOGPFAULTERRORBOX); if (CrashCatcher::Get()->mCrashReportKind == BfpCrashReportKind_PrintOnly) { ::TerminateProcess(GetCurrentProcess(), lpExceptPtr->ExceptionRecord->ExceptionCode); } //return EXCEPTION_CONTINUE_SEARCH; return (CrashCatcher::Get()->mCloseRequested) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; } //PVECTORED_EXCEPTION_HANDLER( static long __stdcall VectorExceptionHandler(LPEXCEPTION_POINTERS lpExceptPtr) { OutputDebugStrF("VectorExceptionHandler\n"); return EXCEPTION_CONTINUE_SEARCH; } void CrashCatcher::Init() { if (mInitialized) return; mPreviousFilter = SetUnhandledExceptionFilter(SEHFilter); OutputDebugStrF("Setting SEH filter %p\n", mPreviousFilter); mInitialized = true; // OutputDebugStrF("AddVectoredExceptionHandler 2\n"); // AddVectoredExceptionHandler(0, VectorExceptionHandler); } void CrashCatcher::Test() { __try { // all of code normally inside of main or WinMain here... int a = 123; int b = 0; a /= b; } __except (SEHFilter(GetExceptionInformation())) { } } void CrashCatcher::AddCrashInfoFunc(CrashInfoFunc crashInfoFunc) { AutoCrit autoCrit(mBfpCritSect); mCrashInfoFuncs.Add(crashInfoFunc); } void CrashCatcher::AddInfo(const StringImpl& str) { AutoCrit autoCrit(mBfpCritSect); mCrashInfo.Append(str); if (!str.EndsWith('\n')) mCrashInfo.Append('\n'); } void CrashCatcher::Crash(const StringImpl& str) { OutputDebugStrF("CrashCatcher::Crash\n"); mBfpCritSect.Lock(); mCrashInfo.Append(str); mCrashInfo.Append("\n"); if (mPreviousFilter == NULL) { // A little late, but install handler now so we can catch this crash Init(); } OutputDebugStr(str); mBfpCritSect.Unlock(); __debugbreak(); // When we catch the exception information like this, it displays the dump correctly but // the minidump doesn't contain a valid callstack, so we need to rely on SetUnhandledExceptionFilter /*__try { ::MessageBoxA(NULL, "A", "B", MB_ICONERROR); __debugbreak(); } __except (SEHFilter(GetExceptionInformation())) { }*/ for (auto func : CrashCatcher::Get()->mCrashInfoFuncs) func(); exit(1); } void CrashCatcher::SetCrashReportKind(BfpCrashReportKind crashReportKind) { mCrashReportKind = crashReportKind; } void CrashCatcher::SetRelaunchCmd(const StringImpl& relaunchCmd) { mRelaunchCmd = relaunchCmd; } struct CrashCatchMemory { public: CrashCatcher* mBpManager; int mABIVersion; int mCounter; }; #define CRASHCATCH_ABI_VERSION 1 static CrashCatcher* sCrashCatcher = NULL; CrashCatcher* CrashCatcher::Get() { if (sCrashCatcher != NULL) return sCrashCatcher; char mutexName[128]; sprintf(mutexName, "BfCrashCatch_mutex_%d", GetCurrentProcessId()); char memName[128]; sprintf(memName, "BfCrashCatch_mem_%d", GetCurrentProcessId()); auto mutex = ::CreateMutexA(NULL, TRUE, mutexName); if (mutex != NULL) { HANDLE fileMapping = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, memName); if (fileMapping != NULL) { CrashCatchMemory* sharedMem = (CrashCatchMemory*)MapViewOfFile(fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashCatchMemory)); if (sharedMem != NULL) { if (sharedMem->mABIVersion == 0 && sharedMem->mBpManager == NULL && sharedMem->mCounter == 0) { sCrashCatcher = new CrashCatcher(); sharedMem->mBpManager = sCrashCatcher; sharedMem->mABIVersion = CRASHCATCH_ABI_VERSION; sharedMem->mCounter = 1; } else if (sharedMem->mABIVersion == CRASHCATCH_ABI_VERSION) { sharedMem->mCounter++; sCrashCatcher = sharedMem->mBpManager; } ::UnmapViewOfFile(sharedMem); } ::CloseHandle(fileMapping); } else { fileMapping = ::CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CrashCatchMemory), memName); if (fileMapping != NULL) { CrashCatchMemory* sharedMem = (CrashCatchMemory*)MapViewOfFile(fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashCatchMemory)); if (sharedMem != NULL) { sCrashCatcher = new CrashCatcher(); sharedMem->mBpManager = sCrashCatcher; sharedMem->mABIVersion = CRASHCATCH_ABI_VERSION; sharedMem->mCounter = 1; ::UnmapViewOfFile(sharedMem); ::ReleaseMutex(mutex); } else { ::CloseHandle(fileMapping); ::CloseHandle(mutex); } } } } if (sCrashCatcher == NULL) sCrashCatcher = new CrashCatcher(); return sCrashCatcher; } int CrashCatcher::Shutdown() { if (sCrashCatcher == NULL) return 0; char mutexName[128]; sprintf(mutexName, "BfCrashCatch_mutex_%d", GetCurrentProcessId()); char memName[128]; sprintf(memName, "BfCrashCatch_mem_%d", GetCurrentProcessId()); auto mutex = ::CreateMutexA(NULL, TRUE, mutexName); if (mutex != NULL) { HANDLE fileMapping = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, memName); if (fileMapping != NULL) { CrashCatchMemory* sharedMem = (CrashCatchMemory*)MapViewOfFile(fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashCatchMemory)); if (sharedMem != NULL) { if (sharedMem->mABIVersion == CRASHCATCH_ABI_VERSION && sharedMem->mBpManager != NULL && sharedMem->mCounter > 0) { sharedMem->mCounter--; if (sharedMem->mCounter <= 0) { delete sharedMem->mBpManager; sharedMem->mBpManager = NULL; sharedMem->mCounter = 0; sharedMem->mABIVersion = 0; } } ::UnmapViewOfFile(sharedMem); } ::CloseHandle(fileMapping); } ::CloseHandle(mutex); } sCrashCatcher = NULL; return 1; }