AuroraRuntime/Source/Debug/ExceptionWatcher.Win32.cpp
Reece d7c6d66fad [+] AuBitsToLower, AuBitsToHigher (returns half of an input word)
[+] AuPopCnt
[+] NormalizePath, GetFileFromPath, GetDirectoryFromPath, GoUpToSeparator
[*] Fix Version Helpers again
[*] Fix registry locale memory check under read registry in SWInfo
[*] Breakout portable process code away from evil win32 code
[*] .dynlib -> .dylib under macos
2022-01-27 05:52:45 +00:00

367 lines
11 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 <Source/RuntimeInternal.hpp>
#include "Debug.hpp"
#include "ExceptionWatcher.NT.hpp"
#include "ExceptionWatcher.Win32.hpp"
#include "Stack.Win32.hpp"
#include <Source/Process/ProcessMap.Win32.hpp>
#include <Source/Telemetry/Telemetry.hpp>
#include <Windows.h>
#include <Dbghelp.h>
#include <codecvt>
#include <vcruntime_exception.h>
#include <ehdata.h>
#include <Source/Process/ProcessMap.hpp>
#include <Source/IO/FS/FS.hpp>
static thread_local int gDebugLocked = 0;
namespace Aurora::Debug
{
#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;
}
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<ThrowInfo *>(ExceptionInfo->ExceptionRecord->ExceptionInformation[2]);
if (pThrowInfo)
{
auto attribs = pThrowInfo->attributes;
if (_EH_RELATIVE_TYPEINFO)
{
handle = reinterpret_cast<HMODULE>(ExceptionInfo->ExceptionRecord->ExceptionInformation[3]);
}
exception = reinterpret_cast<void *>(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);
}
}