aboutsummaryrefslogtreecommitdiff
path: root/primedev/logging/crashhandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'primedev/logging/crashhandler.cpp')
-rw-r--r--primedev/logging/crashhandler.cpp590
1 files changed, 590 insertions, 0 deletions
diff --git a/primedev/logging/crashhandler.cpp b/primedev/logging/crashhandler.cpp
new file mode 100644
index 00000000..a01de5a1
--- /dev/null
+++ b/primedev/logging/crashhandler.cpp
@@ -0,0 +1,590 @@
+#include "crashhandler.h"
+#include "config/profile.h"
+#include "dedicated/dedicated.h"
+#include "util/version.h"
+#include "mods/modmanager.h"
+#include "plugins/plugins.h"
+
+#include <minidumpapiset.h>
+
+#define CRASHHANDLER_MAX_FRAMES 32
+#define CRASHHANDLER_GETMODULEHANDLE_FAIL "GetModuleHandleExA failed!"
+
+//-----------------------------------------------------------------------------
+// Purpose: Vectored exception callback
+//-----------------------------------------------------------------------------
+LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo)
+{
+ g_pCrashHandler->Lock();
+
+ g_pCrashHandler->SetExceptionInfos(pExceptionInfo);
+
+ // Check if we should handle this
+ // NOTE [Fifty]: This gets called before even a try{} catch() {} can handle an exception
+ // we don't handle these unless "-crash_handle_all" is passed as a launch arg
+ if (!g_pCrashHandler->IsExceptionFatal() && !g_pCrashHandler->GetAllFatal())
+ {
+ g_pCrashHandler->Unlock();
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Don't run if a debbuger is attached
+ if (IsDebuggerPresent())
+ {
+ g_pCrashHandler->Unlock();
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Prevent recursive calls
+ if (g_pCrashHandler->GetState())
+ {
+ g_pCrashHandler->Unlock();
+ ExitProcess(1);
+ }
+
+ g_pCrashHandler->SetState(true);
+
+ // Needs to be called first as we use the members this sets later on
+ g_pCrashHandler->SetCrashedModule();
+
+ // Format
+ g_pCrashHandler->FormatException();
+ g_pCrashHandler->FormatCallstack();
+ g_pCrashHandler->FormatRegisters();
+ g_pCrashHandler->FormatLoadedMods();
+ g_pCrashHandler->FormatLoadedPlugins();
+ g_pCrashHandler->FormatModules();
+
+ // Flush
+ NS::log::FlushLoggers();
+
+ // Write minidump
+ g_pCrashHandler->WriteMinidump();
+
+ // Show message box
+ g_pCrashHandler->ShowPopUpMessage();
+
+ g_pCrashHandler->Unlock();
+
+ // We showed the "Northstar has crashed" message box
+ // make sure we terminate
+ if (!g_pCrashHandler->IsExceptionFatal())
+ ExitProcess(1);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: console control signal handler
+//-----------------------------------------------------------------------------
+BOOL WINAPI ConsoleCtrlRoutine(DWORD dwCtrlType)
+{
+ // NOTE [Fifty]: When closing the process by closing the console we don't want
+ // to trigger the crash handler so we remove it
+ switch (dwCtrlType)
+ {
+ case CTRL_CLOSE_EVENT:
+ spdlog::info("Exiting due to console close...");
+ delete g_pCrashHandler;
+ g_pCrashHandler = nullptr;
+ std::exit(EXIT_SUCCESS);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CCrashHandler::CCrashHandler()
+ : m_hExceptionFilter(nullptr)
+ , m_pExceptionInfos(nullptr)
+ , m_bHasSetConsolehandler(false)
+ , m_bAllExceptionsFatal(false)
+ , m_bHasShownCrashMsg(false)
+ , m_bState(false)
+{
+ Init();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CCrashHandler::~CCrashHandler()
+{
+ Shutdown();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initilazes crash handler
+//-----------------------------------------------------------------------------
+void CCrashHandler::Init()
+{
+ m_hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter);
+ m_bHasSetConsolehandler = SetConsoleCtrlHandler(ConsoleCtrlRoutine, TRUE);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shutdowns crash handler
+//-----------------------------------------------------------------------------
+void CCrashHandler::Shutdown()
+{
+ if (m_hExceptionFilter)
+ {
+ RemoveVectoredExceptionHandler(m_hExceptionFilter);
+ m_hExceptionFilter = nullptr;
+ }
+
+ if (m_bHasSetConsolehandler)
+ {
+ SetConsoleCtrlHandler(ConsoleCtrlRoutine, FALSE);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the exception info
+//-----------------------------------------------------------------------------
+void CCrashHandler::SetExceptionInfos(EXCEPTION_POINTERS* pExceptionPointers)
+{
+ m_pExceptionInfos = pExceptionPointers;
+}
+//-----------------------------------------------------------------------------
+// Purpose: Sets the exception stirngs for message box
+//-----------------------------------------------------------------------------
+void CCrashHandler::SetCrashedModule()
+{
+ LPCSTR pCrashAddress = static_cast<LPCSTR>(m_pExceptionInfos->ExceptionRecord->ExceptionAddress);
+ HMODULE hCrashedModule;
+ if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pCrashAddress, &hCrashedModule))
+ {
+ m_svCrashedModule = CRASHHANDLER_GETMODULEHANDLE_FAIL;
+ m_svCrashedOffset = "";
+
+ DWORD dwErrorID = GetLastError();
+ if (dwErrorID != 0)
+ {
+ LPSTR pszBuffer;
+ DWORD dwSize = FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ dwErrorID,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&pszBuffer,
+ 0,
+ NULL);
+
+ if (dwSize > 0)
+ {
+ m_svError = pszBuffer;
+ LocalFree(pszBuffer);
+ }
+ }
+
+ return;
+ }
+
+ // Get module filename
+ CHAR szCrashedModulePath[MAX_PATH];
+ GetModuleFileNameExA(GetCurrentProcess(), hCrashedModule, szCrashedModulePath, sizeof(szCrashedModulePath));
+
+ const CHAR* pszCrashedModuleFileName = strrchr(szCrashedModulePath, '\\') + 1;
+
+ // Get relative address
+ LPCSTR pModuleBase = reinterpret_cast<LPCSTR>(pCrashAddress - reinterpret_cast<LPCSTR>(hCrashedModule));
+
+ m_svCrashedModule = pszCrashedModuleFileName;
+ m_svCrashedOffset = fmt::format("{:#x}", reinterpret_cast<DWORD64>(pModuleBase));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the exception null terminated stirng
+//-----------------------------------------------------------------------------
+
+const CHAR* CCrashHandler::GetExceptionString() const
+{
+ return GetExceptionString(m_pExceptionInfos->ExceptionRecord->ExceptionCode);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the exception null terminated stirng
+//-----------------------------------------------------------------------------
+const CHAR* CCrashHandler::GetExceptionString(DWORD dwExceptionCode) const
+{
+ // clang-format off
+ switch (dwExceptionCode)
+ {
+ case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION";
+ case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT";
+ case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT";
+ case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP";
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
+ case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND";
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
+ case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT";
+ case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION";
+ case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW";
+ case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK";
+ case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW";
+ case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO";
+ case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW";
+ case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION";
+ case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR";
+ case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION";
+ case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
+ case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW";
+ case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION";
+ case EXCEPTION_GUARD_PAGE: return "EXCEPTION_GUARD_PAGE";
+ case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE";
+ case 3765269347: return "RUNTIME_EXCEPTION";
+ }
+ // clang-format on
+ return "UNKNOWN_EXCEPTION";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if exception is known
+//-----------------------------------------------------------------------------
+bool CCrashHandler::IsExceptionFatal() const
+{
+ return IsExceptionFatal(m_pExceptionInfos->ExceptionRecord->ExceptionCode);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if exception is known
+//-----------------------------------------------------------------------------
+bool CCrashHandler::IsExceptionFatal(DWORD dwExceptionCode) const
+{
+ // clang-format off
+ switch (dwExceptionCode)
+ {
+ case EXCEPTION_ACCESS_VIOLATION:
+ case EXCEPTION_DATATYPE_MISALIGNMENT:
+ case EXCEPTION_BREAKPOINT:
+ case EXCEPTION_SINGLE_STEP:
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
+ case EXCEPTION_FLT_DENORMAL_OPERAND:
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+ case EXCEPTION_FLT_INEXACT_RESULT:
+ case EXCEPTION_FLT_INVALID_OPERATION:
+ case EXCEPTION_FLT_OVERFLOW:
+ case EXCEPTION_FLT_STACK_CHECK:
+ case EXCEPTION_FLT_UNDERFLOW:
+ case EXCEPTION_INT_DIVIDE_BY_ZERO:
+ case EXCEPTION_INT_OVERFLOW:
+ case EXCEPTION_PRIV_INSTRUCTION:
+ case EXCEPTION_IN_PAGE_ERROR:
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ case EXCEPTION_NONCONTINUABLE_EXCEPTION:
+ case EXCEPTION_STACK_OVERFLOW:
+ case EXCEPTION_INVALID_DISPOSITION:
+ case EXCEPTION_GUARD_PAGE:
+ case EXCEPTION_INVALID_HANDLE:
+ return true;
+ }
+ // clang-format on
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows a message box
+//-----------------------------------------------------------------------------
+void CCrashHandler::ShowPopUpMessage()
+{
+ if (m_bHasShownCrashMsg)
+ return;
+
+ m_bHasShownCrashMsg = true;
+
+ if (!IsDedicatedServer())
+ {
+ std::string svMessage = fmt::format(
+ "Northstar has crashed! Crash info can be found at {}/logs!\n\n{}\n{} + {}",
+ GetNorthstarPrefix(),
+ GetExceptionString(),
+ m_svCrashedModule,
+ m_svCrashedOffset);
+
+ MessageBoxA(GetForegroundWindow(), svMessage.c_str(), "Northstar has crashed!", MB_ICONERROR | MB_OK);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatException()
+{
+ spdlog::error("-------------------------------------------");
+ spdlog::error("Northstar has crashed!");
+ spdlog::error("\tVersion: {}", version);
+ if (!m_svError.empty())
+ {
+ spdlog::info("\tEncountered an error when gathering crash information!");
+ spdlog::info("\tWinApi Error: {}", m_svError.c_str());
+ }
+ spdlog::error("\t{}", GetExceptionString());
+
+ DWORD dwExceptionCode = m_pExceptionInfos->ExceptionRecord->ExceptionCode;
+ if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION || dwExceptionCode == EXCEPTION_IN_PAGE_ERROR)
+ {
+ ULONG_PTR uExceptionInfo0 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[0];
+ ULONG_PTR uExceptionInfo1 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[1];
+
+ if (!uExceptionInfo0)
+ spdlog::error("\tAttempted to read from: {:#x}", uExceptionInfo1);
+ else if (uExceptionInfo0 == 1)
+ spdlog::error("\tAttempted to write to: {:#x}", uExceptionInfo1);
+ else if (uExceptionInfo0 == 8)
+ spdlog::error("\tData Execution Prevention (DEP) at: {:#x}", uExceptionInfo1);
+ else
+ spdlog::error("\tUnknown access violation at: {:#x}", uExceptionInfo1);
+ }
+
+ spdlog::error("\tAt: {} + {}", m_svCrashedModule, m_svCrashedOffset);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatCallstack()
+{
+ spdlog::error("Callstack:");
+
+ PVOID pFrames[CRASHHANDLER_MAX_FRAMES];
+
+ int iFrames = RtlCaptureStackBackTrace(0, CRASHHANDLER_MAX_FRAMES, pFrames, NULL);
+
+ // Above call gives us frames after the crash occured, we only want to print the ones starting from where
+ // the exception was called
+ bool bSkipExceptionHandlingFrames = true;
+
+ // We ran into an error when getting the offset, just print all frames
+ if (m_svCrashedOffset.empty())
+ bSkipExceptionHandlingFrames = false;
+
+ for (int i = 0; i < iFrames; i++)
+ {
+ const CHAR* pszModuleFileName;
+
+ LPCSTR pAddress = static_cast<LPCSTR>(pFrames[i]);
+ HMODULE hModule;
+ if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pAddress, &hModule))
+ {
+ pszModuleFileName = CRASHHANDLER_GETMODULEHANDLE_FAIL;
+ // If we fail here it's too late to do any damage control
+ }
+ else
+ {
+ CHAR szModulePath[MAX_PATH];
+ GetModuleFileNameExA(GetCurrentProcess(), hModule, szModulePath, sizeof(szModulePath));
+ pszModuleFileName = strrchr(szModulePath, '\\') + 1;
+ }
+
+ // Get relative address
+ LPCSTR pCrashOffset = reinterpret_cast<LPCSTR>(pAddress - reinterpret_cast<LPCSTR>(hModule));
+ std::string svCrashOffset = fmt::format("{:#x}", reinterpret_cast<DWORD64>(pCrashOffset));
+
+ // Should we log this frame
+ if (bSkipExceptionHandlingFrames)
+ {
+ if (m_svCrashedModule == pszModuleFileName && m_svCrashedOffset == svCrashOffset)
+ {
+ bSkipExceptionHandlingFrames = false;
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ // Log module + offset
+ spdlog::error("\t{} + {:#x}", pszModuleFileName, reinterpret_cast<DWORD64>(pCrashOffset));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatFlags(const CHAR* pszRegister, DWORD nValue)
+{
+ spdlog::error("\t{}: {:#b}", pszRegister, nValue);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatIntReg(const CHAR* pszRegister, DWORD64 nValue)
+{
+ spdlog::error("\t{}: {:#x}", pszRegister, nValue);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatFloatReg(const CHAR* pszRegister, M128A nValue)
+{
+ DWORD nVec[4] = {
+ static_cast<DWORD>(nValue.Low & UINT_MAX),
+ static_cast<DWORD>(nValue.Low >> 32),
+ static_cast<DWORD>(nValue.High & UINT_MAX),
+ static_cast<DWORD>(nValue.High >> 32)};
+
+ spdlog::error(
+ "\t{}: [ {:G}, {:G}, {:G}, {:G} ]; [ {:#x}, {:#x}, {:#x}, {:#x} ]",
+ pszRegister,
+ static_cast<float>(nVec[0]),
+ static_cast<float>(nVec[1]),
+ static_cast<float>(nVec[2]),
+ static_cast<float>(nVec[3]),
+ nVec[0],
+ nVec[1],
+ nVec[2],
+ nVec[3]);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatRegisters()
+{
+ spdlog::error("Registers:");
+
+ PCONTEXT pContext = m_pExceptionInfos->ContextRecord;
+
+ FormatFlags("Flags:", pContext->ContextFlags);
+
+ FormatIntReg("Rax", pContext->Rax);
+ FormatIntReg("Rcx", pContext->Rcx);
+ FormatIntReg("Rdx", pContext->Rdx);
+ FormatIntReg("Rbx", pContext->Rbx);
+ FormatIntReg("Rsp", pContext->Rsp);
+ FormatIntReg("Rbp", pContext->Rbp);
+ FormatIntReg("Rsi", pContext->Rsi);
+ FormatIntReg("Rdi", pContext->Rdi);
+ FormatIntReg("R8 ", pContext->R8);
+ FormatIntReg("R9 ", pContext->R9);
+ FormatIntReg("R10", pContext->R10);
+ FormatIntReg("R11", pContext->R11);
+ FormatIntReg("R12", pContext->R12);
+ FormatIntReg("R13", pContext->R13);
+ FormatIntReg("R14", pContext->R14);
+ FormatIntReg("R15", pContext->R15);
+ FormatIntReg("Rip", pContext->Rip);
+
+ FormatFloatReg("Xmm0 ", pContext->Xmm0);
+ FormatFloatReg("Xmm1 ", pContext->Xmm1);
+ FormatFloatReg("Xmm2 ", pContext->Xmm2);
+ FormatFloatReg("Xmm3 ", pContext->Xmm3);
+ FormatFloatReg("Xmm4 ", pContext->Xmm4);
+ FormatFloatReg("Xmm5 ", pContext->Xmm5);
+ FormatFloatReg("Xmm6 ", pContext->Xmm6);
+ FormatFloatReg("Xmm7 ", pContext->Xmm7);
+ FormatFloatReg("Xmm8 ", pContext->Xmm8);
+ FormatFloatReg("Xmm9 ", pContext->Xmm9);
+ FormatFloatReg("Xmm10", pContext->Xmm10);
+ FormatFloatReg("Xmm11", pContext->Xmm11);
+ FormatFloatReg("Xmm12", pContext->Xmm12);
+ FormatFloatReg("Xmm13", pContext->Xmm13);
+ FormatFloatReg("Xmm14", pContext->Xmm14);
+ FormatFloatReg("Xmm15", pContext->Xmm15);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatLoadedMods()
+{
+ if (g_pModManager)
+ {
+ spdlog::error("Enabled mods:");
+ for (const Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ if (!mod.m_bEnabled)
+ continue;
+
+ spdlog::error("\t{}", mod.Name);
+ }
+
+ spdlog::error("Disabled mods:");
+ for (const Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ if (mod.m_bEnabled)
+ continue;
+
+ spdlog::error("\t{}", mod.Name);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatLoadedPlugins()
+{
+ if (g_pPluginManager)
+ {
+ spdlog::error("Loaded Plugins:");
+ for (const Plugin& plugin : g_pPluginManager->m_vLoadedPlugins)
+ {
+ spdlog::error("\t{}", plugin.name);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrashHandler::FormatModules()
+{
+ spdlog::error("Loaded modules:");
+ HMODULE hModules[1024];
+ DWORD cbNeeded;
+
+ if (EnumProcessModules(GetCurrentProcess(), hModules, sizeof(hModules), &cbNeeded))
+ {
+ for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
+ {
+ CHAR szModulePath[MAX_PATH];
+ if (GetModuleFileNameExA(GetCurrentProcess(), hModules[i], szModulePath, sizeof(szModulePath)))
+ {
+ const CHAR* pszModuleFileName = strrchr(szModulePath, '\\') + 1;
+ spdlog::error("\t{}", pszModuleFileName);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Writes minidump to disk
+//-----------------------------------------------------------------------------
+void CCrashHandler::WriteMinidump()
+{
+ time_t time = std::time(nullptr);
+ tm currentTime = *std::localtime(&time);
+ std::stringstream stream;
+ stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str());
+
+ HANDLE hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (hMinidumpFile)
+ {
+ MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo;
+ dumpExceptionInfo.ThreadId = GetCurrentThreadId();
+ dumpExceptionInfo.ExceptionPointers = m_pExceptionInfos;
+ dumpExceptionInfo.ClientPointers = false;
+
+ MiniDumpWriteDump(
+ GetCurrentProcess(),
+ GetCurrentProcessId(),
+ hMinidumpFile,
+ MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
+ &dumpExceptionInfo,
+ nullptr,
+ nullptr);
+ CloseHandle(hMinidumpFile);
+ }
+ else
+ spdlog::error("Failed to write minidump file {}!", stream.str());
+}
+
+//-----------------------------------------------------------------------------
+CCrashHandler* g_pCrashHandler = nullptr;