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
3943HANDLE g_startup_log_handle = INVALID_HANDLE_VALUE;
44+ PVOID g_vectored_handler = nullptr ;
4045#else
4146FILE* 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+
185273std::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+
205346LONG 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