327 lines
12 KiB
C++
327 lines
12 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: ExceptionWatcher.Win32.cpp
|
|
Date: 2021-6-12
|
|
Author: Reece
|
|
***/
|
|
#include <RuntimeInternal.hpp>
|
|
#include "Debug.hpp"
|
|
#include "ExceptionWatcher.Win32.hpp"
|
|
|
|
#include <Process/ProcessMap.Win32.hpp>
|
|
#include <Telemetry/Telemetry.hpp>
|
|
|
|
#include <Windows.h>
|
|
#include <Dbghelp.h>
|
|
#include <codecvt>
|
|
|
|
#pragma comment(lib,"Dbghelp.lib")
|
|
|
|
#include <ehdata.h>
|
|
|
|
static thread_local int gDebugLocked = 0;
|
|
|
|
namespace Aurora::Debug
|
|
{
|
|
static void ParseStack(CONTEXT *ctx, StackTrace &backTrace)
|
|
{
|
|
char buffer[sizeof(SYMBOL_INFO) + (MAX_SYM_NAME + 1) * sizeof(char)] = { 0 };
|
|
AuString backTraceBuffer;
|
|
HMODULE hModule;
|
|
DWORD disp;
|
|
DWORD64 displacement = 0;
|
|
HANDLE process = GetCurrentProcess();
|
|
STACKFRAME64 stack = { 0 };
|
|
CONTEXT cpy = *ctx;
|
|
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
|
|
|
|
#if defined(AURORA_ARCH_X64)
|
|
stack.AddrPC.Offset = ctx->Rip;
|
|
stack.AddrPC.Mode = AddrModeFlat;
|
|
stack.AddrStack.Offset = ctx->Rsp;
|
|
stack.AddrStack.Mode = AddrModeFlat;
|
|
stack.AddrFrame.Offset = ctx->Rbp;
|
|
stack.AddrFrame.Mode = AddrModeFlat;
|
|
#elif defined(AURORA_ARCH_XX86)
|
|
stack.AddrPC.Offset = ctx->Eip;
|
|
stack.AddrPC.Mode = AddrModeFlat;
|
|
stack.AddrStack.Offset = ctx->Esp;
|
|
stack.AddrStack.Mode = AddrModeFlat;
|
|
stack.AddrFrame.Offset = ctx->Ebp;
|
|
stack.AddrFrame.Mode = AddrModeFlat;
|
|
#endif
|
|
|
|
for (ULONG frame = 0; ; frame++)
|
|
{
|
|
StackTraceEntry frameCurrent;
|
|
|
|
auto result = StackWalk64
|
|
(
|
|
#if defined(AURORA_ARCH_X64)
|
|
IMAGE_FILE_MACHINE_AMD64,
|
|
#else
|
|
IMAGE_FILE_MACHINE_I386,
|
|
#endif
|
|
INVALID_HANDLE_VALUE,
|
|
INVALID_HANDLE_VALUE,
|
|
&stack,
|
|
&cpy,
|
|
NULL,
|
|
SymFunctionTableAccess64,
|
|
SymGetModuleBase64,
|
|
NULL
|
|
);
|
|
|
|
if (!result)
|
|
{
|
|
break;
|
|
}
|
|
|
|
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
pSymbol->MaxNameLen = MAX_SYM_NAME;
|
|
|
|
frameCurrent.address = stack.AddrPC.Offset;
|
|
|
|
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)(stack.AddrPC.Offset), &hModule))
|
|
{
|
|
if (hModule != NULL)
|
|
{
|
|
frameCurrent.module = Process::ModuleToPath(hModule);
|
|
}
|
|
}
|
|
|
|
if (SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, pSymbol))
|
|
{
|
|
frameCurrent.label = pSymbol->Name;
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
IMAGEHLP_LINE64 line;
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
|
|
if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, &line))
|
|
{
|
|
frameCurrent.file = std::make_tuple(line.FileName, line.LineNumber, 0);
|
|
}
|
|
#endif
|
|
|
|
backTrace.push_back(frameCurrent);
|
|
}
|
|
}
|
|
|
|
#define EXCEPTION_ENTRY(n) {n, #n}
|
|
static const AuHashMap<DWORD, AuString> 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<DWORD, bool> 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;
|
|
}
|
|
|
|
void InitWin32()
|
|
{
|
|
#if defined(DEBUG) || defined(INTERNAL)
|
|
SymInitialize(GetCurrentProcess(), NULL, TRUE);
|
|
#endif
|
|
|
|
AddVectoredExceptionHandler(1,
|
|
[](_EXCEPTION_POINTERS *ExceptionInfo) -> LONG
|
|
{
|
|
Telemetry::NewBlockboxEntry entry;
|
|
entry.type = Telemetry::ENewBlackBoxEntry::eWinCxxException;
|
|
std::exception *exception = nullptr;
|
|
|
|
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 >= 3)
|
|
{
|
|
SysPanic("Nested Exception");
|
|
}
|
|
|
|
bool cxxThrow = ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == EH_PURE_MAGIC_NUMBER1;
|
|
bool cxxThrowPure = ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == EH_MAGIC_NUMBER1;
|
|
|
|
if ((ExceptionInfo->ExceptionRecord->ExceptionCode == EH_EXCEPTION_NUMBER) &&
|
|
(ExceptionInfo->ExceptionRecord->NumberParameters >= 4) &&
|
|
(cxxThrow) ||
|
|
(cxxThrowPure)
|
|
)
|
|
{
|
|
HMODULE handle {};
|
|
auto *throwInfo = reinterpret_cast<ThrowInfo *>(ExceptionInfo->ExceptionRecord->ExceptionInformation[2]);
|
|
|
|
if (throwInfo)
|
|
{
|
|
auto attribs = throwInfo->attributes;
|
|
|
|
if (_EH_RELATIVE_TYPEINFO)
|
|
{
|
|
handle = reinterpret_cast<HMODULE>(ExceptionInfo->ExceptionRecord->ExceptionInformation[3]);
|
|
}
|
|
|
|
#if defined(_EH_RELATIVE_TYPEINFO)
|
|
const auto catchableTypeArray = reinterpret_cast<const CatchableTypeArray*>(static_cast<AuUInt>(throwInfo->pCatchableTypeArray) + reinterpret_cast<AuUInt>(handle));
|
|
const auto type = reinterpret_cast<CatchableType*>(static_cast<AuUInt>(catchableTypeArray->arrayOfCatchableTypes[0]) + reinterpret_cast<AuUInt>(handle));
|
|
const auto descriptor = reinterpret_cast<TypeDescriptor*>(static_cast<AuUInt>(type->pType) + reinterpret_cast<AuUInt>(handle));
|
|
#else
|
|
const auto catchableTypeArray = reinterpret_cast<const CatchableTypeArray*>(static_cast<AuUInt>(throwInfo->pCatchableTypeArray));
|
|
const auto type = reinterpret_cast<CatchableType*>(static_cast<AuUInt>(catchableTypeArray->arrayOfCatchableTypes[0]));
|
|
const auto descriptor = reinterpret_cast<TypeDescriptor*>(static_cast<AuUInt>(type->pType));
|
|
#endif
|
|
|
|
if (strncmp(descriptor->name, ".?AVException@", ArraySize(".?AVException@") - 1) == 0)
|
|
{
|
|
exception = reinterpret_cast<std::exception *>(ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
|
|
if (IsReadable(exception))
|
|
{
|
|
entry.wincxx.str = exception->what();
|
|
}
|
|
}
|
|
else if (((type->properties & CT_IsSimpleType) != 0) &&
|
|
((type->properties & CT_IsWinRTHandle) == 0))
|
|
{
|
|
// assert descriptor->name == ".PEAD"? `DEAP.`?
|
|
auto possibleStringPointer = reinterpret_cast<const char **>(ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
|
|
if (IsReadable(possibleStringPointer))
|
|
{
|
|
auto string = *possibleStringPointer;
|
|
if (IsReadable(string))
|
|
{
|
|
entry.wincxx.str = string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isCritical = TryFind(kExceptionFatalTable, ExceptionInfo->ExceptionRecord->ExceptionCode);
|
|
if (entry.wincxx.str.empty())
|
|
{
|
|
const AuString *msg;
|
|
if (TryFind(kExceptionTable, ExceptionInfo->ExceptionRecord->ExceptionCode, msg))
|
|
{
|
|
entry.wincxx.str = *msg;
|
|
}
|
|
else
|
|
{
|
|
entry.wincxx.str = std::to_string(ExceptionInfo->ExceptionRecord->ExceptionCode);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (minimal < 2)
|
|
{
|
|
ParseStack(ExceptionInfo->ContextRecord, entry.wincxx.stack.backtrace);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
|
|
}
|
|
|
|
#if defined(INTERNAL) || defined(DEBUG)
|
|
bool isInternal = true;
|
|
#else
|
|
bool isInternal = false;
|
|
#endif
|
|
|
|
if ((isCritical || isInternal) && (minimal == 0))
|
|
{
|
|
LogWarn("NT Exception: 0x{:x}, {}", ExceptionInfo->ExceptionRecord->ExceptionCode, entry.wincxx.str);
|
|
LogWarn("{}", StringifyStackTrace(entry.wincxx.stack.backtrace));
|
|
}
|
|
|
|
try
|
|
{
|
|
Telemetry::Report(entry);
|
|
|
|
if (isCritical)
|
|
{
|
|
Telemetry::Mayday();
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
|
|
}
|
|
|
|
gDebugLocked = 0;
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
});
|
|
}
|
|
} |