/*** 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 #include "ExceptionWatcher.Unix.hpp" #include "Debug.hpp" #include #include #include #include #include "ErrorStack.hpp" #if defined(AURORA_PLATFORM_LINUX) #include "unwind.h" #endif #include "Stack.Unix.hpp" #include #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 #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(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(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(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 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 : ""); } else if (*pInfo == typeid(char* const)) { message = fmt::format("Exception Type: {}, Message: {}", pInfo->name(), pException ? *(const char**)pException : ""); } #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(message); // auto pTrace = AuMakeSharedThrow(trace); if (ShouldPushErrorStackInternal()) { auto pMessage = AuMakeSharedThrow(); 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