536 lines
15 KiB
C++
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 |