/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ExceptionWatcher.NT.cpp Date: 2022-1-26 Author: Reece ***/ #include #include "ExceptionWatcher.NT.hpp" #include #if defined(AURORA_PLATFORM_WIN32) #include "ExceptionWatcher.Win32.hpp" #include "Stack.Win32.hpp" #endif #include "Debug.hpp" #include #include #include #include "ErrorStack.hpp" namespace Aurora::Debug { static std::string kStringRawName = typeid(std::string).raw_name(); 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; } AuString ReportSEH(HMODULE handle, void *exception, const void *throwInfo, const AuFunction &resolveFallback, const StackTrace &trace, const AuFunction &prereport) { AuString message; if (throwInfo) { if (handle != INVALID_HANDLE_VALUE) { try { const ThrowInfo *pthrowInfo = reinterpret_cast(throwInfo); auto attribs = pthrowInfo->attributes; const auto catchableTypeArray = reinterpret_cast(reinterpret_cast(handle) + (AuUInt)(pthrowInfo->pCatchableTypeArray)); AuString suffix; for (int i = 0; i < catchableTypeArray->nCatchableTypes; i++) { const auto type = reinterpret_cast (reinterpret_cast(handle) + (AuUInt)(catchableTypeArray->arrayOfCatchableTypes[i])); const auto descriptor = reinterpret_cast (reinterpret_cast(handle) + (AuUInt)(type->pType)); message += (i == 0 ? "" : AuString(", ")) + descriptor->name(); // __std_type_info_name if (_strnicmp(descriptor->raw_name(), ".?AVException@", 15 - 1) == 0) { if (IsReadable(exception) && IsReadable(*((AuUInt32 **)exception)) && (*(AuUInt32 **)exception)[2 - AuBuild::IsPlatformX32()]) { auto exception2 = reinterpret_cast(exception); auto wptr = exception2->what(); if (wptr) { suffix = wptr; } } } else if (_strnicmp(descriptor->raw_name(), ".PEAD", 5) == 0) { auto possibleStringPointer = reinterpret_cast(exception); if (IsReadable(possibleStringPointer)) { auto string = *possibleStringPointer; if (IsReadable(string) && (strnlen(string, 4096) < 1024)) { suffix = string; } } } else if (_strnicmp(descriptor->raw_name(), kStringRawName.data(), kStringRawName.size()) == 0) { auto possibleStdStringPointer = reinterpret_cast(exception); if (IsReadable(possibleStdStringPointer)) { suffix = *possibleStdStringPointer; } } } if (suffix.size()) { message += AuString("\r\n ") + suffix; } } catch (...) { } } } if (message.find("invalid sto") != AuString::npos) { return message; } try { if (message.empty() && resolveFallback) { message = resolveFallback(); } } catch (...) { } if (prereport) { prereport(message); } try { Telemetry::NewBlockboxEntry entry; entry.type = Telemetry::ENewBlackBoxEntry::eStackWarning; entry.stack.fenceId = ReportStackTrace(trace, message); entry.stack.backtrace = trace; Telemetry::Report(entry); } catch (...) { } try { auto pStr = AuMakeSharedThrow(message); // auto pTrace = AuMakeSharedThrow(trace); if (ShouldPushErrorStackInternal()) { auto pMessage = AuMakeSharedThrow(); pMessage->optStackTrace = trace; pMessage->pStringMessage = pStr; PushErrorStackInternal(pMessage); } } catch (...) { } return message; } static void HandleFatal(bool fatal, _EXCEPTION_POINTERS *pExceptionInfo) { static bool handlingFatal = false; #if defined(AU_ENABLE_NATIVE_MINIDUMP) if (!fatal) { if (gRuntimeConfig.debug.bRaiseDebuggerToAllExceptionsInStageAndDbg && IsDebuggerPresent()) { __debugbreak(); } else { if (gRuntimeConfig.debug.bSaveAllExceptionsAsMinidumpInBg) { BlackboxReport(pExceptionInfo, false); } } } else { SaveMinidump(pExceptionInfo, true); } #else if (fatal) { BlackboxReport(pExceptionInfo, true); } #endif } void PlatformHandleFatalEx2(bool fatal, CONTEXT &ctx, bool bNoExit); void PlatformHandleFatal(bool fatal, bool bNoExit) { CONTEXT ctx {}; ctx.ContextFlags = CONTEXT_ALL; if (!GetThreadContext(GetCurrentThread(), &ctx)) { Debug::Panic(); } PlatformHandleFatalEx2(fatal, ctx, bNoExit); } void PlatformHandleFatalEx2(bool fatal, CONTEXT & ctx, bool bNoExit) { static bool forceFail { false }; _EXCEPTION_POINTERS ptrs; StackTrace ret; EXCEPTION_RECORD ex {}; if (!bNoExit) { if (!AuExchange(forceFail, true)) { Exit::PostLevel(AuThreads::GetThread(), Exit::ETriggerLevel::eFatalException); } } ex.ExceptionCode = 0x1337; ptrs.ExceptionRecord = &ex; ptrs.ContextRecord = &ctx; HandleFatal(fatal, &ptrs); } void InitNT() { if (!gRuntimeConfig.debug.bEnableInjectedExceptionHandler) { return; } // i dont think we need a slow grug path for windows // "runs in the same thread context" (- msdn) =/= we will run out of the old threads stack // cant be any worse than unix &~ ONSTACK traps (ironically on-stack being shorthand for onstack on an alt heap) SetUnhandledExceptionFilter([](_EXCEPTION_POINTERS *pExceptionInfo) -> LONG { try { AuLogWarn("Handling fatal NT err exception..."); } catch (...) { } HandleFatal(true, pExceptionInfo); return EXCEPTION_EXECUTE_HANDLER; }); } } #include "MemoryCrunch.hpp" static thread_local AuUInt tlsThrowCounter; extern "C" AUKN_SYM void __stdcall _ReportMSVCSEH(void *exception, const void *throwInfo, void *caller) { AU_DEBUG_MEMCRUNCH; if (!throwInfo) { return; } if (!exception) { return; } HMODULE handle = 0; if (_EH_RELATIVE_TYPEINFO) { if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(caller), reinterpret_cast(&handle))) { return; } } if (handle == INVALID_HANDLE_VALUE) { return; } // we will probably overflow for a bit if an inner try catch borks up somewhere if ((tlsThrowCounter++) == 7) // TODO: this might be stupid. we should configure for panic on second { tlsThrowCounter--; return; } try { auto trace = AuDebug::GetStackTrace(); if (gRuntimeConfig.debug.bPrintExceptionStackTracesOut) { AuDebug::ReportSEH(handle, exception, throwInfo, {}, trace, [&](const AuString &str) { AuLogWarn("Local MSVC Exception: 0x{:x}, {}", exception, str.c_str()); AuLogWarn("{}", StringifyStackTrace(trace)); }); } else { AuDebug::ReportSEH(handle, exception, throwInfo, {}, trace, [&](const AuString &str) {}); } } catch (...) { } Aurora::Exit::PostLevel(AuThreads::GetThread(), Aurora::Exit::ETriggerLevel::eProblematicEvent); tlsThrowCounter--; }