Skip to content

Commit 37a280d

Browse files
committed
Add Windows crash dump and fatal-handler instrumentation
1 parent 0d65d10 commit 37a280d

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ if(ULTRA_BUILD_GUI)
244244
target_link_libraries(ultra_gui PRIVATE
245245
opengl32
246246
imm32
247+
dbghelp
247248
)
248249
elseif(UNIX)
249250
target_link_libraries(ultra_gui PRIVATE dl)

src/gui/main_gui.cpp

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@
2121
#include <ctime>
2222
#include <exception>
2323
#include <vector>
24+
#include <atomic>
2425
#include <ultra/logging.hpp>
2526

2627
#ifdef _WIN32
2728
#ifndef NOMINMAX
2829
#define NOMINMAX
2930
#endif
3031
#include <windows.h>
32+
#include <DbgHelp.h>
33+
#include <eh.h>
34+
#include <signal.h>
3135
#ifdef ERROR
3236
#undef ERROR
3337
#endif
@@ -37,6 +41,7 @@ namespace {
3741

3842
#ifdef _WIN32
3943
HANDLE g_startup_log_handle = INVALID_HANDLE_VALUE;
44+
PVOID g_vectored_handler = nullptr;
4045
#else
4146
FILE* g_startup_log_file = nullptr;
4247
#endif
@@ -182,6 +187,89 @@ void showFatalStartupMessage(const std::string& msg) {
182187
MessageBoxA(nullptr, msg.c_str(), "ProjectUltra Startup Error", MB_ICONERROR | MB_OK);
183188
}
184189

190+
std::string buildCrashDumpPath(const char* tag) {
191+
std::filesystem::path base_dir = "logs";
192+
if (!g_startup_log_path.empty()) {
193+
std::filesystem::path startup_path(g_startup_log_path);
194+
if (!startup_path.parent_path().empty()) {
195+
base_dir = startup_path.parent_path();
196+
}
197+
}
198+
199+
std::error_code ec;
200+
std::filesystem::create_directories(base_dir, ec);
201+
202+
SYSTEMTIME st{};
203+
GetLocalTime(&st);
204+
char name[160];
205+
std::snprintf(name, sizeof(name),
206+
"crash_%04u%02u%02u_%02u%02u%02u_%s_pid%lu.dmp",
207+
static_cast<unsigned>(st.wYear),
208+
static_cast<unsigned>(st.wMonth),
209+
static_cast<unsigned>(st.wDay),
210+
static_cast<unsigned>(st.wHour),
211+
static_cast<unsigned>(st.wMinute),
212+
static_cast<unsigned>(st.wSecond),
213+
tag ? tag : "unknown",
214+
static_cast<unsigned long>(GetCurrentProcessId()));
215+
return (base_dir / name).string();
216+
}
217+
218+
void writeCrashDump(EXCEPTION_POINTERS* ex, const char* tag) {
219+
static std::atomic<bool> dump_written{false};
220+
bool expected = false;
221+
if (!dump_written.compare_exchange_strong(expected, true)) {
222+
return;
223+
}
224+
225+
const std::string dump_path = buildCrashDumpPath(tag);
226+
HANDLE file = CreateFileA(dump_path.c_str(),
227+
GENERIC_WRITE,
228+
FILE_SHARE_READ,
229+
nullptr,
230+
CREATE_ALWAYS,
231+
FILE_ATTRIBUTE_NORMAL,
232+
nullptr);
233+
if (file == INVALID_HANDLE_VALUE) {
234+
writeStartupLog("Crash dump create failed: path=%s err=%lu",
235+
dump_path.c_str(), static_cast<unsigned long>(GetLastError()));
236+
return;
237+
}
238+
239+
MINIDUMP_EXCEPTION_INFORMATION exception_info{};
240+
MINIDUMP_EXCEPTION_INFORMATION* exception_info_ptr = nullptr;
241+
if (ex) {
242+
exception_info.ThreadId = GetCurrentThreadId();
243+
exception_info.ExceptionPointers = ex;
244+
exception_info.ClientPointers = FALSE;
245+
exception_info_ptr = &exception_info;
246+
}
247+
248+
MINIDUMP_TYPE dump_type = static_cast<MINIDUMP_TYPE>(
249+
MiniDumpWithThreadInfo |
250+
MiniDumpWithDataSegs |
251+
MiniDumpWithHandleData |
252+
MiniDumpWithIndirectlyReferencedMemory
253+
);
254+
255+
BOOL ok = MiniDumpWriteDump(GetCurrentProcess(),
256+
GetCurrentProcessId(),
257+
file,
258+
dump_type,
259+
exception_info_ptr,
260+
nullptr,
261+
nullptr);
262+
DWORD err = ok ? 0 : GetLastError();
263+
CloseHandle(file);
264+
265+
if (ok) {
266+
writeStartupLog("Crash dump written: %s", dump_path.c_str());
267+
} else {
268+
writeStartupLog("Crash dump write failed: path=%s err=%lu",
269+
dump_path.c_str(), static_cast<unsigned long>(err));
270+
}
271+
}
272+
185273
std::string modulePathForAddress(void* addr) {
186274
if (!addr) {
187275
return "<unknown module>";
@@ -202,6 +290,59 @@ std::string modulePathForAddress(void* addr) {
202290
return std::string(path, len);
203291
}
204292

293+
LONG CALLBACK startupVectoredExceptionHandler(EXCEPTION_POINTERS* ex) {
294+
if (!ex || !ex->ExceptionRecord) {
295+
return EXCEPTION_CONTINUE_SEARCH;
296+
}
297+
298+
unsigned long code = ex->ExceptionRecord->ExceptionCode;
299+
void* addr = ex->ExceptionRecord->ExceptionAddress;
300+
301+
// Log only likely-fatal runtime faults to avoid noisy first-chance spam.
302+
switch (code) {
303+
case EXCEPTION_ACCESS_VIOLATION:
304+
case EXCEPTION_STACK_OVERFLOW:
305+
case EXCEPTION_ILLEGAL_INSTRUCTION:
306+
case EXCEPTION_IN_PAGE_ERROR:
307+
case EXCEPTION_INT_DIVIDE_BY_ZERO:
308+
case 0xC0000409: // STATUS_STACK_BUFFER_OVERRUN / fast-fail
309+
case 0xC0000374: // STATUS_HEAP_CORRUPTION
310+
writeStartupLog("Vectored exception: code=0x%08lX addr=%p",
311+
code, addr);
312+
writeCrashDump(ex, "veh");
313+
break;
314+
default:
315+
break;
316+
}
317+
318+
return EXCEPTION_CONTINUE_SEARCH;
319+
}
320+
321+
void startupInvalidParameterHandler(const wchar_t* expression,
322+
const wchar_t* function,
323+
const wchar_t* file,
324+
unsigned int line,
325+
uintptr_t) {
326+
writeStartupLog("CRT invalid parameter: line=%u expr=%p func=%p file=%p",
327+
line,
328+
static_cast<const void*>(expression),
329+
static_cast<const void*>(function),
330+
static_cast<const void*>(file));
331+
writeCrashDump(nullptr, "invalid_param");
332+
}
333+
334+
int __cdecl startupPurecallHandler() {
335+
writeStartupLog("CRT pure virtual call detected");
336+
writeCrashDump(nullptr, "purecall");
337+
return 0;
338+
}
339+
340+
void startupSignalHandler(int sig) {
341+
writeStartupLog("Signal raised: %d", sig);
342+
writeCrashDump(nullptr, "signal");
343+
std::_Exit(3);
344+
}
345+
205346
LONG WINAPI startupUnhandledExceptionFilter(EXCEPTION_POINTERS* ex) {
206347
unsigned long code = 0;
207348
void* addr = nullptr;
@@ -211,6 +352,7 @@ LONG WINAPI startupUnhandledExceptionFilter(EXCEPTION_POINTERS* ex) {
211352
}
212353
std::string module = modulePathForAddress(addr);
213354
writeStartupLog("Unhandled exception: code=0x%08lX address=%p module=%s", code, addr, module.c_str());
355+
writeCrashDump(ex, "unhandled");
214356

215357
std::string msg = "Unhandled exception in startup path.\n";
216358
char details[512];
@@ -238,7 +380,20 @@ int main(int argc, char* argv[]) {
238380
_putenv_s("ULTRA_STARTUP_LOG", g_startup_log_path.c_str());
239381
writeStartupLog("Startup trace path fallback: %s", g_startup_log_path.c_str());
240382
}
383+
SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
241384
SetUnhandledExceptionFilter(startupUnhandledExceptionFilter);
385+
if (!g_vectored_handler) {
386+
g_vectored_handler = AddVectoredExceptionHandler(1, startupVectoredExceptionHandler);
387+
}
388+
writeStartupLog("Vectored exception handler: %s", g_vectored_handler ? "installed" : "not installed");
389+
_set_invalid_parameter_handler(startupInvalidParameterHandler);
390+
_set_purecall_handler(startupPurecallHandler);
391+
signal(SIGABRT, startupSignalHandler);
392+
signal(SIGSEGV, startupSignalHandler);
393+
signal(SIGILL, startupSignalHandler);
394+
signal(SIGFPE, startupSignalHandler);
395+
signal(SIGTERM, startupSignalHandler);
396+
writeStartupLog("CRT crash handlers installed");
242397
#endif
243398
std::set_terminate([]() {
244399
writeStartupLog("std::terminate invoked");
@@ -579,6 +734,12 @@ int main(int argc, char* argv[]) {
579734
}
580735
SDL_DestroyWindow(window);
581736
SDL_Quit();
737+
#ifdef _WIN32
738+
if (g_vectored_handler) {
739+
RemoveVectoredExceptionHandler(g_vectored_handler);
740+
g_vectored_handler = nullptr;
741+
}
742+
#endif
582743
closeStartupLog();
583744

584745
return 0;

0 commit comments

Comments
 (0)