335 lines
9.7 KiB
C++
335 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 (fatal && !bNoExit)
|
|
{
|
|
// TODO: why? remove?
|
|
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--;
|
|
} |