From 8b50ecd93066f2bae532097c539e05c0144d41d0 Mon Sep 17 00:00:00 2001 From: Steve Biedermann Date: Fri, 6 Jun 2025 22:19:50 +0200 Subject: [PATCH] add option to write crash dump on unhandled exception on windows --- CMakeLists.txt | 2 + src/engine/client/client.cpp | 7 ++ src/engine/client/crash_handler.cpp | 99 ++++++++++++++++++++ src/engine/client/crash_handler.h | 6 ++ src/engine/shared/config_variables_tclient.h | 4 + 5 files changed, 118 insertions(+) create mode 100644 src/engine/client/crash_handler.cpp create mode 100644 src/engine/client/crash_handler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1adef225593..97e26b3060a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2338,6 +2338,8 @@ if(CLIENT) checksum.h client.cpp client.h + crash_handler.cpp + crash_handler.h demoedit.cpp demoedit.h discord.cpp diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 733f74c6d7b..b1ccd627ccb 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -57,6 +57,7 @@ #include "friends.h" #include "notifications.h" #include "serverbrowser.h" +#include "crash_handler.h" #if defined(CONF_VIDEORECORDER) #include "video.h" @@ -4932,6 +4933,12 @@ int main(int argc, const char **argv) } g_Config.m_ClConfigVersion = 1; +#if defined(CONF_FAMILY_WINDOWS) + if (g_Config.m_ClWriteCrashDump > 0) { + InitCrashHandler(g_Config.m_ClWriteCrashDump == 2); + } +#endif + // parse the command line arguments pConsole->SetUnknownCommandCallback(UnknownArgumentCallback, pClient); pConsole->ParseArguments(argc - 1, &argv[1]); diff --git a/src/engine/client/crash_handler.cpp b/src/engine/client/crash_handler.cpp new file mode 100644 index 00000000000..b265ced9fe4 --- /dev/null +++ b/src/engine/client/crash_handler.cpp @@ -0,0 +1,99 @@ +#include "crash_handler.h" + +#include + +#if defined(CONF_FAMILY_WINDOWS) +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "dbghelp.lib") +#endif + +namespace +{ +bool g_useFullMemoryDump = false; +} + +#if defined(CONF_FAMILY_WINDOWS) +static LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; + +std::wstring getExecutableName() +{ + wchar_t path[MAX_PATH]; + GetModuleFileNameW(NULL, path, MAX_PATH); + std::wstring fullPath(path); + size_t pos = fullPath.find_last_of(L"\\/"); + return (pos != std::wstring::npos) ? fullPath.substr(pos + 1) : fullPath; +} + +std::wstring GenerateDumpFilename() +{ + SYSTEMTIME st; + GetLocalTime(&st); + + std::wostringstream oss; + oss << getExecutableName() + << L"_" + << st.wYear << L"-" + << std::setw(2) << std::setfill(L'0') << st.wMonth << L"-" + << std::setw(2) << std::setfill(L'0') << st.wDay << L"_" + << std::setw(2) << std::setfill(L'0') << st.wHour << L"-" + << std::setw(2) << std::setfill(L'0') << st.wMinute << L"-" + << std::setw(2) << std::setfill(L'0') << st.wSecond; + + if (g_useFullMemoryDump) + oss << L"_full"; + + oss << L".dmp"; + + return oss.str(); +} + +LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* pExceptionPointers) +{ + std::wstring dumpFilename = GenerateDumpFilename(); + + HANDLE hFile = CreateFileW(dumpFilename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (hFile != INVALID_HANDLE_VALUE) { + MINIDUMP_EXCEPTION_INFORMATION mdei; + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = pExceptionPointers; + mdei.ClientPointers = FALSE; + + MINIDUMP_TYPE dumpType = g_useFullMemoryDump + ? MiniDumpWithFullMemory + : MiniDumpNormal; + + BOOL result = MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + dumpType, + &mdei, + nullptr, + nullptr + ); + + CloseHandle(hFile); + } + + if (g_prevExceptionFilter) + return g_prevExceptionFilter(pExceptionPointers); + + return EXCEPTION_EXECUTE_HANDLER; +} + +void InitCrashHandler(bool full) +{ + g_useFullMemoryDump = full; + g_prevExceptionFilter = SetUnhandledExceptionFilter(UnhandledExceptionHandler); +} +#else +void InitCrashHandler(bool full) {} +#endif \ No newline at end of file diff --git a/src/engine/client/crash_handler.h b/src/engine/client/crash_handler.h new file mode 100644 index 00000000000..2f883c7bdac --- /dev/null +++ b/src/engine/client/crash_handler.h @@ -0,0 +1,6 @@ +#ifndef ENGINE_CLIENT_CRASH_HANDLER_H +#define ENGINE_CLIENT_CRASH_HANDLER_H + +void InitCrashHandler(bool full); + +#endif \ No newline at end of file diff --git a/src/engine/shared/config_variables_tclient.h b/src/engine/shared/config_variables_tclient.h index 042ee791660..c8159a9d105 100644 --- a/src/engine/shared/config_variables_tclient.h +++ b/src/engine/shared/config_variables_tclient.h @@ -238,3 +238,7 @@ MACRO_CONFIG_INT(ClTClientSettingsTabs, tc_tclient_settings_tabs, 0, 0, 65536, C // Mod MACRO_CONFIG_INT(ClShowPlayerHitBoxes, tc_show_player_hit_boxes, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show player hit boxes (1 = predicted, 2 = predicted and unpredicted)") MACRO_CONFIG_INT(ClHideChatBubbles, tc_hide_chat_bubbles, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Hide your own chat bubbles, only works when authed in remote console") + +#if defined(CONF_FAMILY_WINDOWS) +MACRO_CONFIG_INT(ClWriteCrashDump, tc_write_crash_dump, 0, 0, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Whether to write a crash dump on crash (0 = off, 1 = on/normal, 2 = full") +#endif \ No newline at end of file