/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ExceptionWatcher.Win32.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "Debug.hpp" #include "ExceptionWatcher.NT.hpp" #include "ExceptionWatcher.Win32.hpp" #include "Stack.Win32.hpp" #include #include #include #include #include #include #include #include #include static thread_local int gDebugLocked = 0; namespace Aurora::Debug { #define EXCEPTION_ENTRY(n) {n, #n} static const AuHashMap kExceptionTable { EXCEPTION_ENTRY(STILL_ACTIVE), EXCEPTION_ENTRY(EXCEPTION_ACCESS_VIOLATION), EXCEPTION_ENTRY(EXCEPTION_DATATYPE_MISALIGNMENT), EXCEPTION_ENTRY(EXCEPTION_BREAKPOINT), EXCEPTION_ENTRY(EXCEPTION_SINGLE_STEP), EXCEPTION_ENTRY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), EXCEPTION_ENTRY(EXCEPTION_FLT_DENORMAL_OPERAND), EXCEPTION_ENTRY(EXCEPTION_FLT_DIVIDE_BY_ZERO), EXCEPTION_ENTRY(EXCEPTION_FLT_INEXACT_RESULT), EXCEPTION_ENTRY(EXCEPTION_FLT_INVALID_OPERATION), EXCEPTION_ENTRY(EXCEPTION_FLT_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_FLT_STACK_CHECK), EXCEPTION_ENTRY(EXCEPTION_FLT_UNDERFLOW), EXCEPTION_ENTRY(EXCEPTION_INT_DIVIDE_BY_ZERO), EXCEPTION_ENTRY(EXCEPTION_INT_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_PRIV_INSTRUCTION), EXCEPTION_ENTRY(EXCEPTION_IN_PAGE_ERROR), EXCEPTION_ENTRY(EXCEPTION_ILLEGAL_INSTRUCTION), EXCEPTION_ENTRY(EXCEPTION_NONCONTINUABLE_EXCEPTION), EXCEPTION_ENTRY(EXCEPTION_STACK_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_INVALID_DISPOSITION), EXCEPTION_ENTRY(EXCEPTION_GUARD_PAGE), EXCEPTION_ENTRY(EXCEPTION_INVALID_HANDLE), //EXCEPTION_ENTRY(EXCEPTION_POSSIBLE_DEADLOCK), EXCEPTION_ENTRY(CONTROL_C_EXIT) }; #undef EXCEPTION_ENTRY #define EXCEPTION_ENTRY(n) {n, true} static const AuHashMap kExceptionFatalTable { EXCEPTION_ENTRY(EXCEPTION_ACCESS_VIOLATION), EXCEPTION_ENTRY(EXCEPTION_DATATYPE_MISALIGNMENT), EXCEPTION_ENTRY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), EXCEPTION_ENTRY(EXCEPTION_FLT_DENORMAL_OPERAND), EXCEPTION_ENTRY(EXCEPTION_FLT_DIVIDE_BY_ZERO), EXCEPTION_ENTRY(EXCEPTION_FLT_INEXACT_RESULT), EXCEPTION_ENTRY(EXCEPTION_FLT_INVALID_OPERATION), EXCEPTION_ENTRY(EXCEPTION_FLT_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_FLT_STACK_CHECK), EXCEPTION_ENTRY(EXCEPTION_FLT_UNDERFLOW), EXCEPTION_ENTRY(EXCEPTION_INT_DIVIDE_BY_ZERO), EXCEPTION_ENTRY(EXCEPTION_INT_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_PRIV_INSTRUCTION), EXCEPTION_ENTRY(EXCEPTION_IN_PAGE_ERROR), EXCEPTION_ENTRY(EXCEPTION_ILLEGAL_INSTRUCTION), EXCEPTION_ENTRY(EXCEPTION_NONCONTINUABLE_EXCEPTION), EXCEPTION_ENTRY(EXCEPTION_STACK_OVERFLOW), EXCEPTION_ENTRY(EXCEPTION_INVALID_DISPOSITION), EXCEPTION_ENTRY(EXCEPTION_GUARD_PAGE) }; #undef EXCEPTION_ENTRY static bool IsReadable(const void *address) { MEMORY_BASIC_INFORMATION info; if (!VirtualQuery(address, &info, sizeof(info))) { return false; } if (!info.BaseAddress) { return false; } return (info.Protect & (PAGE_READONLY | PAGE_READWRITE)) != 0; } static LONG HandleVectorException(_EXCEPTION_POINTERS *ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode < STATUS_GUARD_PAGE_VIOLATION) { return EXCEPTION_CONTINUE_SEARCH; } if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) { return EXCEPTION_CONTINUE_SEARCH; } auto minimal = gDebugLocked++; if (minimal >= 5) { SysPanic("Nested Exception"); } bool cxxThrow = ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == EH_MAGIC_NUMBER1; bool cxxThrowPure = ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == EH_PURE_MAGIC_NUMBER1; void *exception {}; HMODULE handle {}; ThrowInfo *pThrowInfo {}; if ((ExceptionInfo->ExceptionRecord->ExceptionCode == EH_EXCEPTION_NUMBER) && (ExceptionInfo->ExceptionRecord->NumberParameters >= 4) && ((cxxThrow) || (cxxThrowPure)) ) { pThrowInfo = reinterpret_cast(ExceptionInfo->ExceptionRecord->ExceptionInformation[2]); if (pThrowInfo) { auto attribs = pThrowInfo->attributes; if (_EH_RELATIVE_TYPEINFO) { handle = reinterpret_cast(ExceptionInfo->ExceptionRecord->ExceptionInformation[3]); } exception = reinterpret_cast(ExceptionInfo->ExceptionRecord->ExceptionInformation[1]); } } bool isCritical = AuTryFind(kExceptionFatalTable, ExceptionInfo->ExceptionRecord->ExceptionCode); auto handleNoCppObject = [&]() -> AuString { const AuString *msg; if (AuTryFind(kExceptionTable, ExceptionInfo->ExceptionRecord->ExceptionCode, msg)) { return *msg; } else { return AuToString(ExceptionInfo->ExceptionRecord->ExceptionCode); } }; StackTrace backtrace; ParseStack(ExceptionInfo->ContextRecord, backtrace); #if defined(STAGING) || defined(DEBUG) bool isInternal = true; #else bool isInternal = false; #endif ReportSEH(handle, exception, pThrowInfo, handleNoCppObject, backtrace, [&](const AuString &str) { // Pre-submit callback -> its showtime if ((isCritical || isInternal) && (minimal == 0)) { AuLogWarn("NT Exception: 0x{:x}, {}", ExceptionInfo->ExceptionRecord->ExceptionCode, str); AuLogWarn("{}", StringifyStackTrace(backtrace)); } }); try { if (isCritical) { Telemetry::Mayday(); } if (isCritical) { PlatformHandleFatal(true); } } catch (...) { } gDebugLocked = 0; return EXCEPTION_CONTINUE_SEARCH; } static AuString GetDumpName() { AuString exeName; try { Process::GetProcName(exeName); } catch (...) { } try { auto tm = Time::ToCivilTime(Time::CurrentClockMS()); return fmt::format("{}-{}-{}T{}-{}-{}Z_{}.dmp", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, exeName); } catch (...) { return "errordate.dmp"; } } #if defined(DEBUG) || defined(INTERNAL) void SaveMinidump(_EXCEPTION_POINTERS *ExceptionInfo) { bool ok {}; MINIDUMP_EXCEPTION_INFORMATION info; info.ThreadId = GetCurrentThreadId(); info.ClientPointers = ExceptionInfo != nullptr; info.ExceptionPointers = ExceptionInfo; static const DWORD flags = MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo; std::wstring path; while (path.empty()) { try { path = Locale::ConvertFromUTF8(AuIOFS::NormalizePathRet("./Logs/Crashes/" + GetDumpName())); } catch (...) { } } auto hFile = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { AuLogWarn("Couldn't open minidump file. Has a debugger locked the .dmp file?"); goto miniDumpOut; } ok = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)flags, &info, nullptr, nullptr); if (!ok) { AuLogWarn("Couldn't write minidump"); goto miniDumpOut; } CloseHandle(hFile); miniDumpOut: try { AuDebug::PrintError(); } catch (...) { } __debugbreak(); __fastfail('fokd'); } #endif void BlackboxReport(_EXCEPTION_POINTERS *ExceptionInfo) { AuString path; HANDLE hFile; std::wstring wpath; MINIDUMP_EXCEPTION_INFORMATION info; auto ok = AuIOFS::GetProfileDomain(path); if (!ok) { AuLogWarn("Couldn't find minidump directory"); goto miniMiniDumpOut; } info.ClientPointers = true; info.ThreadId = GetCurrentThreadId(); info.ExceptionPointers = ExceptionInfo; static const DWORD flags = MiniDumpWithDataSegs | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo; while (wpath.empty()) { try { wpath = Locale::ConvertFromUTF8(path + "Crashes/" + GetDumpName()); } catch (...) { } } hFile = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { AuLogWarn("[1] Couldn't open minidump file. Has a debugger locked the .dmp file?"); goto miniMiniDumpOut; } ok = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)flags, &info, nullptr, nullptr); if (!ok) { AuLogWarn("Couldn't write minidump"); goto miniMiniDumpOut; } CloseHandle(hFile); miniMiniDumpOut: Telemetry::NewBlackBoxEntryMinidump report {}; report.includesRx = false; report.resource.path = path; report.resource.type = Telemetry::ENewBlackBoxResourceType::eLocal; Telemetry::ReportDyingBreath(report); __fastfail('fokd'); } void InitWin32() { static AuString kStringRawName = typeid(AuString).raw_name(); #if defined(DEBUG) || defined(STAGING) SymInitialize(GetCurrentProcess(), NULL, TRUE); #endif // This is really gross :( AddVectoredExceptionHandler(1, HandleVectorException); } }