AuroraRuntime/Source/Debug/ExceptionWatcher.NT.cpp
2024-02-16 13:07:28 +00:00

334 lines
9.7 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: ExceptionWatcher.NT.cpp
Date: 2022-1-26
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "ExceptionWatcher.NT.hpp"
#include <ehdata.h>
#if defined(AURORA_PLATFORM_WIN32)
#include "ExceptionWatcher.Win32.hpp"
#include "Stack.Win32.hpp"
#endif
#include "Debug.hpp"
#include <Source/Telemetry/Telemetry.hpp>
#include <Source/Grug/AuGrug.hpp>
#include <Source/Exit/AuExit.hpp>
#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<AuString()> &resolveFallback, const StackTrace &trace, const AuFunction<void(const AuString &)> &prereport)
{
AuString message;
if (throwInfo)
{
if (handle != INVALID_HANDLE_VALUE)
{
try
{
const ThrowInfo *pthrowInfo = reinterpret_cast<const ThrowInfo *>(throwInfo);
auto attribs = pthrowInfo->attributes;
const auto catchableTypeArray = reinterpret_cast<const CatchableTypeArray *>(reinterpret_cast<AuUInt>(handle) + (AuUInt)(pthrowInfo->pCatchableTypeArray));
AuString suffix;
for (int i = 0; i < catchableTypeArray->nCatchableTypes; i++)
{
const auto type = reinterpret_cast<CatchableType *> (reinterpret_cast<AuUInt>(handle) + (AuUInt)(catchableTypeArray->arrayOfCatchableTypes[i]));
const auto descriptor = reinterpret_cast<std::type_info *> (reinterpret_cast<AuUInt>(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<std::exception *>(exception);
auto wptr = exception2->what();
if (wptr)
{
suffix = wptr;
}
}
}
else if (_strnicmp(descriptor->raw_name(), ".PEAD", 5) == 0)
{
auto possibleStringPointer = reinterpret_cast<const char **>(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<std::string *>(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<AuString>(message);
// auto pTrace = AuMakeSharedThrow<StackTrace>(trace);
if (ShouldPushErrorStackInternal())
{
auto pMessage = AuMakeSharedThrow<ThreadMessage>();
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<LPCSTR>(caller), reinterpret_cast<HMODULE *>(&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}, {}", AuUInt(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--;
}