From 8582e2770d5f6a7fa122dbb69a660581de2c235d Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 6 Feb 2021 13:21:22 +1000 Subject: [PATCH] CrashHandler: Save minidump on Windows --- src/common/crash_handler.cpp | 101 +++++++++++++++--- src/common/thirdparty/StackWalker.cpp | 144 ++++++++++++++------------ src/common/thirdparty/StackWalker.h | 2 + 3 files changed, 165 insertions(+), 82 deletions(-) diff --git a/src/common/crash_handler.cpp b/src/common/crash_handler.cpp index dadc5d91c..72e922323 100644 --- a/src/common/crash_handler.cpp +++ b/src/common/crash_handler.cpp @@ -5,9 +5,11 @@ #include #ifdef _WIN32 -#include "thirdparty/StackWalker.h" #include "windows_headers.h" +#include "thirdparty/StackWalker.h" +#include + namespace CrashHandler { class CrashHandlerStackWalker : public StackWalker @@ -45,33 +47,68 @@ void CrashHandlerStackWalker::OnOutput(LPCSTR szText) OutputDebugStringA(szText); } +static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD process_id, DWORD thread_id, + PEXCEPTION_POINTERS exception, MINIDUMP_TYPE type) +{ + using PFNMINIDUMPWRITEDUMP = + BOOL(WINAPI*)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + + PFNMINIDUMPWRITEDUMP minidump_write_dump = + reinterpret_cast(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")); + if (!minidump_write_dump) + return false; + + MINIDUMP_EXCEPTION_INFORMATION mei; + PMINIDUMP_EXCEPTION_INFORMATION mei_ptr = nullptr; + if (exception) + { + mei.ThreadId = thread_id; + mei.ExceptionPointers = exception; + mei.ClientPointers = FALSE; + mei_ptr = &mei; + } + + return minidump_write_dump(hProcess, process_id, hFile, type, mei_ptr, nullptr, nullptr); +} + static std::wstring s_write_directory; -static PVOID s_veh_handle; +static PVOID s_veh_handle = nullptr; +static bool s_in_crash_handler = false; static LONG ExceptionHandler(PEXCEPTION_POINTERS exi) { + if (s_in_crash_handler) + return EXCEPTION_CONTINUE_SEARCH; + switch (exi->ExceptionRecord->ExceptionCode) { - case EXCEPTION_ACCESS_VIOLATION: - case EXCEPTION_BREAKPOINT: - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - case EXCEPTION_INT_DIVIDE_BY_ZERO: - case EXCEPTION_INT_OVERFLOW: - case EXCEPTION_PRIV_INSTRUCTION: - case EXCEPTION_ILLEGAL_INSTRUCTION: - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - case EXCEPTION_STACK_OVERFLOW: - case EXCEPTION_GUARD_PAGE: - break; + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_BREAKPOINT: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + case EXCEPTION_PRIV_INSTRUCTION: + case EXCEPTION_ILLEGAL_INSTRUCTION: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_GUARD_PAGE: + break; - default: - return EXCEPTION_CONTINUE_SEARCH; + default: + return EXCEPTION_CONTINUE_SEARCH; } // if the debugger is attached, let it take care of it. if (IsDebuggerPresent()) return EXCEPTION_CONTINUE_SEARCH; + s_in_crash_handler = true; + + // we definitely need dbg helper - maintain an extra reference here + HMODULE hDbgHelp = StackWalker::LoadDbgHelpLibrary(); + wchar_t filename[1024] = {}; if (!s_write_directory.empty()) { @@ -94,8 +131,42 @@ static LONG ExceptionHandler(PEXCEPTION_POINTERS exi) WriteFile(hFile, line, static_cast(std::strlen(line)), &written, nullptr); } + if (!s_write_directory.empty()) + { + wcsncpy_s(filename, countof(filename), s_write_directory.c_str(), _TRUNCATE); + wcsncat_s(filename, countof(filename), L"\\crash.dmp", _TRUNCATE); + } + else + { + wcsncat_s(filename, countof(filename), L"crash.dmp", _TRUNCATE); + } + + const MINIDUMP_TYPE minidump_type = + static_cast(MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithProcessThreadData | + MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory); + HANDLE hMinidumpFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr); + if (!hMinidumpFile || !WriteMinidump(hDbgHelp, hMinidumpFile, GetCurrentProcess(), GetCurrentProcessId(), + GetCurrentThreadId(), exi, minidump_type)) + { + static const char error_message[] = "Failed to write minidump file.\n"; + if (hFile) + { + DWORD written; + WriteFile(hFile, error_message, sizeof(error_message) - 1, &written, nullptr); + } + } + if (hMinidumpFile) + CloseHandle(hMinidumpFile); + CrashHandlerStackWalker sw(hFile); sw.ShowCallstack(GetCurrentThread(), exi->ContextRecord); + + if (hFile) + CloseHandle(hFile); + + if (hDbgHelp) + FreeLibrary(hDbgHelp); + return EXCEPTION_CONTINUE_SEARCH; } diff --git a/src/common/thirdparty/StackWalker.cpp b/src/common/thirdparty/StackWalker.cpp index 296dc26b8..5a75efd9e 100644 --- a/src/common/thirdparty/StackWalker.cpp +++ b/src/common/thirdparty/StackWalker.cpp @@ -246,6 +246,81 @@ static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc) // Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL') #define USED_CONTEXT_FLAGS CONTEXT_FULL +HMODULE StackWalker::LoadDbgHelpLibrary() +{ + HMODULE hModule = NULL; + + // Dynamically load the Entry-Points for dbghelp.dll: + // First try to load the newest one from + TCHAR szTemp[4096]; + // But before we do this, we first check if the ".local" file exists + if (GetModuleFileName(NULL, szTemp, 4096) > 0) + { + _tcscat_s(szTemp, _T(".local")); + if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) + { + // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" + // Ok, first try the new path according to the architecture: +#ifdef _M_IX86 + if ((hModule == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) + { + hModule = LoadLibrary(szTemp); + } + } +#elif _M_X64 + if ((hModule == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) + { + hModule = LoadLibrary(szTemp); + } + } +#elif _M_IA64 + if ((hModule == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) + { + hModule = LoadLibrary(szTemp); + } + } +#endif + // If still not found, try the old directories... + if ((hModule == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) + { + hModule = LoadLibrary(szTemp); + } + } +#if defined _M_X64 || defined _M_IA64 + // Still not found? Then try to load the (old) 64-Bit version: + if ((hModule == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll")); + if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) + { + hModule = LoadLibrary(szTemp); + } + } +#endif + } + } + if (hModule == NULL) // if not already loaded, try to load a default-one + hModule = LoadLibrary(_T("dbghelp.dll")); + + return hModule; +} + class StackWalkerInternal { public: @@ -285,73 +360,8 @@ public: { if (m_parent == NULL) return FALSE; - // Dynamically load the Entry-Points for dbghelp.dll: - // First try to load the newest one from - TCHAR szTemp[4096]; - // But before we do this, we first check if the ".local" file exists - if (GetModuleFileName(NULL, szTemp, 4096) > 0) - { - _tcscat_s(szTemp, _T(".local")); - if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) - { - // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" - // Ok, first try the new path according to the architecture: -#ifdef _M_IX86 - if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) - { - _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll")); - // now check if the file exists: - if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) - { - m_hDbhHelp = LoadLibrary(szTemp); - } - } -#elif _M_X64 - if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) - { - _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll")); - // now check if the file exists: - if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) - { - m_hDbhHelp = LoadLibrary(szTemp); - } - } -#elif _M_IA64 - if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) - { - _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll")); - // now check if the file exists: - if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) - { - m_hDbhHelp = LoadLibrary(szTemp); - } - } -#endif - // If still not found, try the old directories... - if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) - { - _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll")); - // now check if the file exists: - if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) - { - m_hDbhHelp = LoadLibrary(szTemp); - } - } -#if defined _M_X64 || defined _M_IA64 - // Still not found? Then try to load the (old) 64-Bit version: - if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) - { - _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll")); - if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) - { - m_hDbhHelp = LoadLibrary(szTemp); - } - } -#endif - } - } - if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one - m_hDbhHelp = LoadLibrary(_T("dbghelp.dll")); + + m_hDbhHelp = StackWalker::LoadDbgHelpLibrary(); if (m_hDbhHelp == NULL) return FALSE; pSI = (tSI)GetProcAddress(m_hDbhHelp, "SymInitialize"); diff --git a/src/common/thirdparty/StackWalker.h b/src/common/thirdparty/StackWalker.h index 0a004d96f..fe7c43c1f 100644 --- a/src/common/thirdparty/StackWalker.h +++ b/src/common/thirdparty/StackWalker.h @@ -102,6 +102,8 @@ public: StackWalker(DWORD dwProcessId, HANDLE hProcess); virtual ~StackWalker(); + static HMODULE LoadDbgHelpLibrary(); + typedef BOOL(__stdcall* PReadProcessMemoryRoutine)( HANDLE hProcess, DWORD64 qwBaseAddress,