AuroraRuntime/Source/Debug/ExceptionWatcher.Unix.cpp

536 lines
15 KiB
C++

/***
Copyright (C) 2021-2024 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: ExceptionWatcher.Linux.cpp
Date: 2021-6-12
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "ExceptionWatcher.Unix.hpp"
#include "Debug.hpp"
#include <Source/Telemetry/Telemetry.hpp>
#include <Source/Grug/AuGrug.hpp>
#include <Source/Console/Console.hpp>
#include <Source/Exit/AuExit.hpp>
#include "ErrorStack.hpp"
#if defined(AURORA_PLATFORM_LINUX)
#include "unwind.h"
#endif
#include "Stack.Unix.hpp"
#include <cxxabi.h>
#if defined(AURORA_PLATFORM_LINUX) && defined(AURORA_ARCH_X64)
#define DEBUG_ABI_ALPHA
#endif
#if defined(AURORA_PLATFORM_LINUX) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
#define DEBUG_ABI_IS_IT_AND_SYSV_LIKE
#endif
#if defined(AURORA_PLATFORM_LINUX)
#include <libunwind.h>
#endif
namespace Aurora::Debug
{
static const AuUInt8 kCriticalAlwaysCoreDumpSignals[] {
SIGABRT,
SIGBUS,
SIGILL,
SIGIOT,
SIGQUIT,
SIGSEGV,
SIGSYS,
SIGTRAP,
//SIGUNUSED,
SIGFPE,
SIGXFSZ
};
static const AuUInt8 kTypeIgnoreNOP = 0;
static const AuUInt8 kTypeIgnoreAndLog = 1;
static const AuUInt8 kTypeCoreDump = 2;
static AuUInt8 gSignalHandlerMap[64] {};
static void SaveMinidump(void *optContext,
void *pException,
int iSignal,
AuUInt32 uThread,
AuString &pathOut)
{
}
static void PlatformHandleFatalEx(bool fatal, bool bNoExit, void *context, AuUInt32 uThreadID)
{
AuString path;
SaveMinidump(context, nullptr, 0, uThreadID, path);
if (fatal)
{
if (path.size())
{
Telemetry::NewBlackBoxEntryMinidump report {};
report.includesRx = false;
report.resource.path = path;
report.resource.type = Telemetry::ENewBlackBoxResourceType::eLocal;
Telemetry::ReportDyingBreath(report);
}
PosixTerminate();
}
}
struct SigArrow : Grug::Arrow
{
int signal {};
void *context {};
AuUInt32 uPosixThread {};
AuUInt thread;
AuString signalStr;
};
static void SendLogMessage(Grug::Arrow *pArrow)
{
auto pArrowEx = AuStaticCast<SigArrow>(pArrow);
AuLogDbg("Caught signal {} ({}) on {}/{}", strsignal(pArrowEx->signal), pArrowEx->signal, pArrowEx->thread, pArrowEx->uPosixThread);
Exit::PostLevel((AuThreads::IAuroraThread *)pArrowEx->thread, Exit::ETriggerLevel::eProblematicEvent);
}
static void SendCoreDump(Grug::Arrow *pArrow)
{
auto pArrowEx = AuStaticCast<SigArrow>(pArrow);
{
StackTrace trace;
if (pArrowEx->context)
{
trace = DumpContext(pArrowEx->context);
}
if (gRuntimeConfig.debug.bPrintExceptionStackTracesOut)
{
AuLogWarn("Fatal Signal: 0x{:x}, {} ({})", pArrowEx->thread, strsignal(pArrowEx->signal), pArrowEx->signal);
AuLogWarn("{}", StringifyStackTrace(trace));
}
{
Telemetry::NewBlockboxEntry entry;
entry.type = Telemetry::ENewBlackBoxEntry::eStackWarning;
entry.stack.fenceId = ReportStackTrace(trace, AuToString(pArrowEx->signal));
entry.stack.backtrace = trace;
Telemetry::Report(entry);
}
}
{
Exit::PostLevel((AuThreads::IAuroraThread *)pArrowEx->thread, Exit::ETriggerLevel::eFatalException);
}
{
AuString path;
SaveMinidump(pArrowEx->context, nullptr, pArrowEx->signal, pArrowEx->uPosixThread, path);
if (path.size())
{
Telemetry::NewBlackBoxEntryMinidump report {};
report.includesRx = false;
report.resource.path = path;
report.resource.type = Telemetry::ENewBlackBoxResourceType::eLocal;
Telemetry::ReportDyingBreath(report);
}
}
{
Grug::GrugFlushWrites();
Grug::GrugFlushFlushs();
Console::Pump();
}
PosixTerminate();
}
static void SendCoreDumpOfException(Grug::Arrow *pArrow)
{
auto pArrowEx = AuStaticCast<SigArrow>(pArrow);
{
StackTrace trace;
if (pArrowEx->context)
{
trace = DumpContext(pArrowEx->context);
}
if (gRuntimeConfig.debug.bPrintExceptionStackTracesOut)
{
AuLogWarn("Fatal Exception: 0x{:x}, {}", pArrowEx->thread, pArrowEx->signalStr);
AuLogWarn("{}", StringifyStackTrace(trace));
}
{
Telemetry::NewBlockboxEntry entry;
entry.type = Telemetry::ENewBlackBoxEntry::eStackWarning;
entry.stack.fenceId = ReportStackTrace(trace, AuToString(pArrowEx->signal));
entry.stack.backtrace = trace;
Telemetry::Report(entry);
}
}
{
Exit::PostLevel((AuThreads::IAuroraThread *)pArrowEx->thread, Exit::ETriggerLevel::eFatalException);
}
{
AuString path;
SaveMinidump(pArrowEx->context, nullptr, pArrowEx->signal, pArrowEx->uPosixThread, path);
if (path.size())
{
Telemetry::NewBlackBoxEntryMinidump report {};
report.includesRx = false;
report.resource.path = path;
report.resource.type = Telemetry::ENewBlackBoxResourceType::eLocal;
Telemetry::ReportDyingBreath(report);
}
}
{
Grug::GrugFlushWrites();
Grug::GrugFlushFlushs();
Console::Pump();
}
PosixTerminate();
}
static void PosixCrashTrap(int signal, siginfo_t *si, void *context)
{
void (*pSendExitSignal_f)(Grug::Arrow *);
SigArrow arrow;
arrow.signal = signal;
arrow.thread = AuThreads::GetThreadId();
arrow.context = context;
arrow.uPosixThread = getpid();
switch (gSignalHandlerMap[signal])
{
case kTypeIgnoreNOP:
return;
case kTypeIgnoreAndLog:
pSendExitSignal_f = SendLogMessage;
break;
case kTypeCoreDump:
pSendExitSignal_f = SendCoreDump;
break;
}
Grug::HurlArrow(&arrow, pSendExitSignal_f, {});
Grug::ArrowWait(&arrow);
if (pSendExitSignal_f == SendCoreDump)
{
PosixTerminate();
}
}
static void PosixSendExceptionCrash(const AuString &str, void *context)
{
SigArrow arrow;
arrow.signalStr = str;
arrow.thread = AuThreads::GetThreadId();
arrow.context = context;
arrow.uPosixThread = getpid();
Grug::HurlArrow(&arrow, SendCoreDumpOfException, {});
Grug::ArrowWait(&arrow);
PosixTerminate();
}
void PlatformHandleFatal(bool bFatal, bool bNoExit)
{
PlatformHandleFatalEx(bFatal, bNoExit, nullptr, getpid());
}
static void InstallSignalHandlerOnSignal(AuUInt8 uSignal)
{
struct sigaction action =
{
.sa_handler = (void (*)(int))PosixCrashTrap,
.sa_flags = SA_ONSTACK
};
sigemptyset(&action.sa_mask);
sigaction(uSignal, &action, nullptr);
}
static void RemoveSignalHandlerOnSignal(AuUInt8 uSignal)
{
struct sigaction action =
{
.sa_handler = SIG_DFL,
.sa_flags = SA_ONSTACK
};
sigemptyset(&action.sa_mask);
sigaction(uSignal, &action, nullptr);
}
static void OverrideCriticalSignals()
{
for (AU_ITERATE_N(i, AuArraySize(kCriticalAlwaysCoreDumpSignals)))
{
auto uSignal = kCriticalAlwaysCoreDumpSignals[i];
gSignalHandlerMap[uSignal] = kTypeCoreDump;
InstallSignalHandlerOnSignal(uSignal);
}
}
void InitUNIX()
{
OverrideCriticalSignals();
}
void DeinitSignalHandlers()
{
for (AU_ITERATE_N(uSignal, AuArraySize(gSignalHandlerMap)))
{
if (gSignalHandlerMap[uSignal] == kTypeIgnoreNOP)
{
continue;
}
RemoveSignalHandlerOnSignal(uSignal);
}
}
static void ReportException(void *pException,
std::type_info *pInfo,
const StackTrace &trace,
AuConsumer<const AuString &> func = {})
{
AuString message { "EMPTY" };
if (pInfo)
{
//AuLogDbg("Base RTTI of the type info: {}", ((char ****)pInfo)[0][-1][1]);
bool bIsStdExceptionDerived {};
#if defined(DEBUG_ABI_IS_IT_AND_SYSV_LIKE)
if (strcmp(((char ****)pInfo)[0][-1][1], "N10__cxxabiv120__si_class_type_infoE") == 0)
{
char **pInfoCur = ((char ****)pException)[0][-1];
while (pInfoCur)
{
if (strcmp(pInfoCur[1], "St9exception") == 0)
{
bIsStdExceptionDerived = true;
break;
}
auto pOld = pInfoCur;
pInfoCur = (char **)pInfoCur[2];
if (pOld == pInfoCur)
{
break;
}
}
}
#endif
if (*pInfo == typeid(const char*))
{
message = fmt::format("Exception Type: {}, Message: {}", pInfo->name(), pException ? *(const char**)pException : "<NULL>");
}
else if (*pInfo == typeid(char* const))
{
message = fmt::format("Exception Type: {}, Message: {}", pInfo->name(), pException ? *(const char**)pException : "<NULL>");
}
#define ADD_FMT_PRIM(type) \
else if (*pInfo == typeid(type)) \
{ \
message = fmt::format("Exception Type: {}, Data: {}", pInfo->name(), pException ? *(type*)pException : type {}); \
}
ADD_FMT_PRIM(std::string)
ADD_FMT_PRIM(double)
ADD_FMT_PRIM(float)
ADD_FMT_PRIM(AuUInt8)
ADD_FMT_PRIM(AuUInt16)
ADD_FMT_PRIM(AuUInt32)
ADD_FMT_PRIM(AuUInt64)
ADD_FMT_PRIM(AuInt8)
ADD_FMT_PRIM(AuInt16)
ADD_FMT_PRIM(AuInt32)
ADD_FMT_PRIM(AuInt64)
else if (bIsStdExceptionDerived)
{
message = fmt::format("Exception Type: {}, Message: {}", pInfo->name(), ((std::exception *)pException)->what());
}
else
{
message = fmt::format("Exception Type: {}", pInfo->name());
}
}
if (func)
{
func(message);
}
{
Telemetry::NewBlockboxEntry entry;
entry.type = Telemetry::ENewBlackBoxEntry::eStackWarning;
entry.stack.fenceId = ReportStackTrace(trace, message);
entry.stack.backtrace = trace;
Telemetry::Report(entry);
auto pStr = AuMakeSharedPanic<AuString>(message);
// auto pTrace = AuMakeSharedThrow<StackTrace>(trace);
if (ShouldPushErrorStackInternal())
{
auto pMessage = AuMakeSharedThrow<ThreadMessage>();
pMessage->optStackTrace = trace;
pMessage->pStringMessage = pStr;
PushErrorStackInternal(pMessage);
}
}
}
static void PosixHandleExceptionThrow(void *pException, std::type_info *pInfo, bool bLinuxException)
{
AU_DEBUG_MEMCRUNCH;
#if defined(AURORA_PLATFORM_LINUX)
::unw_context_t uc;
::unw_getcontext(&uc);
void *pContext = &uc;
auto trace = DumpContext(uc);
#else
auto trace = GetStackTrace();
void *pContext = nullptr;
#endif
AuString reportFatal;
if (gRuntimeConfig.debug.bPrintExceptionStackTracesOut)
{
ReportException(pException, pInfo, trace, [&](const AuString &str)
{
if (bLinuxException)
{
AuLogWarn("Local Linux Exception: 0x{:x}, {}", AuUInt(pException), str.c_str());
}
else
{
AuLogWarn("Local Clang Exception: 0x{:x}, {}", AuUInt(pException), str.c_str());
}
AuLogWarn("{}", StringifyStackTrace(trace));
if (gRuntimeConfig.debug.bIsExceptionThrowFatal)
{
reportFatal = str;
}
});
}
else
{
if (gRuntimeConfig.debug.bIsExceptionThrowFatal)
{
ReportException(pException, pInfo, trace, [&](const AuString &str)
{
reportFatal = str;
});
}
else
{
ReportException(pException, pInfo, trace);
}
}
if (gRuntimeConfig.debug.bIsExceptionThrowFatal)
{
PosixSendExceptionCrash(reportFatal, pContext);
}
else
{
AuExit::PostLevel(AuThreads::GetThread(), AuExit::ETriggerLevel::eProblematicEvent);
}
}
static AU_NORETURN void __au_cxa_throw(void *pException, std::type_info *pInfo, void (*fDtor)(void *pThis))
{
if (Grug::IsGrug())
{
SysAssert(p__cxa_throw, "NST");
p__cxa_throw(pException, pInfo, fDtor);
}
{
PosixHandleExceptionThrow(pException, pInfo, false);
}
{
SysAssert(p__cxa_throw, "NST");
p__cxa_throw(pException, pInfo, fDtor);
}
}
#if defined(AURORA_PLATFORM_LINUX)
static _Unwind_Reason_Code __au_Unwind_RaiseException(void *pException, std::type_info *pInfo, struct _Unwind_Exception *pUnwind)
{
if (Grug::IsGrug())
{
SysAssert(p_Unwind_RaiseException, "URE");
return (_Unwind_Reason_Code)p_Unwind_RaiseException(pUnwind);
}
{
PosixHandleExceptionThrow(pException, pInfo, true);
}
{
SysAssert(p_Unwind_RaiseException, "URE");
return (_Unwind_Reason_Code)p_Unwind_RaiseException(pUnwind);
}
}
#endif
}
AUKN_SYM void AU_NORETURN __au_cxa_throw(void *pException, std::type_info *pInfo, void (*fDtor)(void *pThis))
{
Aurora::Debug::__au_cxa_throw(pException, pInfo, fDtor);
}
#if defined(AURORA_PLATFORM_LINUX)
AUKN_SYM extern "C" _Unwind_Reason_Code _au_Unwind_RaiseException(struct _Unwind_Exception *pUnwind)
{
_Unwind_Exception *exception_header = pUnwind;
auto pException = (void **)(&pUnwind[1]);
#if defined(DEBUG_ABI_ALPHA)
auto pInfo = *(std::type_info **)((char *)pUnwind - 80);
// TODO: Test for clangs termination handler to somewhat verify that we are parsing a known structure layout
#else
std::type_info *pInfo { nullptr };
#endif
return Aurora::Debug::__au_Unwind_RaiseException(pException, pInfo, pUnwind);
}
#endif