AuroraRuntime/Source/Debug/ExceptionWatcher.Win32.cpp
J Reece Wilson 1c78c18997 [+] Exit::CancelExit()
[*] Treat SIGTERM the same as SIGINT. SIGINT is somewhat of an arachic signal meaning, "hey dumb unix app, fuck the process group, start reading from stdin to listen to the user." Nowadays, this doesn't mean anything other than "hey, a human asked us to terminate from a TTY" - basically the same as SIGTERM, except SIGTERM is more likely to be a scheduled or otherwise expected shutdown event.
2022-04-07 06:22:50 +01:00

420 lines
13 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>
#include <Source/Grug/Grug.hpp>
#include <Source/Exit/Exit.hpp>
#include <WerApi.h>
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),
EXCEPTION_ENTRY(DBG_CONTROL_C)
};
#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 CALLBACK HandleVectorException(_EXCEPTION_POINTERS *ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == DBG_CONTROL_C)
{
Exit::PostLevel(AuThreads::GetThread(), Exit::ETriggerLevel::eSigTerminate);
return Exit::gHasCanceled ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
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 >= 3) &&
((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 = AuExists(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(_AU_USE_EXTENDED_FWD_FACING_DEBUGGING)
bool isInternal = true;
#else
bool isInternal = false;
#endif
if (pThrowInfo)
{
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);
}
Grug::Arrow empty;
Grug::HurlRaiseProblematicEvent(&empty);
}
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 AuLocale::TimeDateToFileNameISO8601(tm) + ".dmp";
}
catch (...)
{
return "errordate.dmp";
}
}
#if defined(DEBUG) || defined(INTERNAL)
void SaveMinidump(_EXCEPTION_POINTERS *ExceptionInfo, bool isFatal)
{
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;
AuString utf8Path;
while (path.empty())
{
try
{
utf8Path = AuIOFS::NormalizePathRet("./Logs/Crashes/" + GetDumpName());
path = Locale::ConvertFromUTF8(utf8Path);
}
catch (...)
{
}
}
AuIOFS::CreateDirectories(utf8Path, true);
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 (...)
{
}
if (isFatal)
{
__fastfail('fokd');
}
}
#endif
void BlackboxReport(_EXCEPTION_POINTERS *ExceptionInfo, bool fatal)
{
AuString path;
HANDLE hFile;
std::wstring wpath;
AuString utf8Path;
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
{
utf8Path = path + "Crashes/" + GetDumpName();
wpath = Locale::ConvertFromUTF8(utf8Path);
}
catch (...)
{
}
}
AuIOFS::CreateDirectories(utf8Path, true);
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);
if (fatal)
{
__fastfail('fokd');
}
}
static void CacheInternalBuildSymbols()
{
#if defined(DEBUG) || defined(STAGING)
SymInitialize(GetCurrentProcess(), NULL, TRUE);
#endif
}
static void DisableWindowsErrorReporting()
{
// Windows has this annoying watchdog that triggers when your main loop doesnt respond after a while
// It's aggressive in its approach, giving the users to forcefully terminate as soon as they spam click a busy app,
// - or never, if the user decides its time for a coffee break the time an app goes grey
// It's too easy to trigger the watchdog and impossible to stop it from deciding the windowed application must die
AuString procName;
if (!Process::GetProcName(procName))
{
AuLogWarn("Couldn't disable Microsoft Glowware Reporting!");
return;
}
// Yes, this is prone to module-name conflict issues because it's a dumb registry utility function
// This isn't what we want, but it's probably what the user wants
// MSFT is such a loving company, they'll do almost nothing to ensure they wont steal all your data
WerAddExcludedApplication(AuLocale::ConvertFromUTF8(procName).c_str(), false);
}
void InitWin32()
{
// ...
CacheInternalBuildSymbols();
// ...
DisableWindowsErrorReporting();
// ..
AddVectoredExceptionHandler(1, HandleVectorException);
}
}