Reece
c1976d771b
[*] Disambiguate auContainerUtils [*] Optimize AuLogXXX / remove alloc [*] Didn't commit two auROXTL idiom updates (ExceptionWatcher and Logging)
419 lines
13 KiB
C++
419 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)
|
|
};
|
|
#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 < STATUS_GUARD_PAGE_VIOLATION)
|
|
{
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
|
|
{
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
if (ExceptionInfo->ExceptionRecord->ExceptionCode == CONTROL_C_EXIT)
|
|
{
|
|
Exit::PostLevel(AuThreads::GetThread(), Exit::ETriggerLevel::eSigTerminate);
|
|
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);
|
|
}
|
|
} |