/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Debug.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "Debug.hpp" #include #include "MemoryCrunch.hpp" #if defined(AURORA_PLATFORM_WIN32) #include "ExceptionWatcher.NT.hpp" #include "ExceptionWatcher.Win32.hpp" #include "Stack.Win32.hpp" #endif #if defined(AURORA_IS_LINUX_DERIVED) #include "Stack.Unix.hpp" #endif #if defined(AURORA_IS_POSIX_DERIVED) #include "ExceptionWatcher.Unix.hpp" #endif #include "ErrorStack.hpp" namespace Aurora::Debug { static thread_local AuUInt32 tlsLastBackTrace = 0xFFFFFFFF; static thread_local StackTrace tlsLastStackTrace; static thread_local AuString tlsLastExceptionMessage; static thread_local EFailureCategory tlsCategory = EFailureCategory::kFailureNone; static AuUInt32 gStackTraceFence; static AuUInt32 gFenceId; static AuThreadPrimitives::SpinLock gLock; static AuUInt32 gFenceOSError = -1; AuUInt32 GetOSErrorFence() { return gFenceOSError; } #if defined(AURORA_IS_MODERNNT_DERIVED) AuString GetOSErrorStringWin32(DWORD error) { AuString ret; #if defined(AURORA_PLATFORM_WIN32) char *err = nullptr; if (!FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&err, 0, NULL)) { return {}; } try { ret = err; } catch (...) {} LocalFree(err); #endif return ret; } AuOptional TryFetchOSError() { static OSError_t lastError{}; OSError_t ret{}; ret.first = GetLastError(); if ((ret.first == ERROR_SUCCESS) || (ret.first == WSAEWOULDBLOCK)) { return {}; } if (lastError.first == ret.first) { return lastError; } ret.second = GetOSErrorStringWin32(lastError.first); gFenceOSError++; gFenceId++; return lastError = ret; } AuOptional TryGetOrFetchOSError() { static AuOptional lastErrorError; static AuOptional lastErrorFence; AuOptional tempError = TryFetchOSError(); if (!tempError) { return lastErrorError; } if (lastErrorFence) { if (lastErrorFence.value() != GetOSErrorFence()) { SetLastError(ERROR_SUCCESS); Telemetry::InsertOSError(tempError.value()); } } lastErrorFence = GetOSErrorFence(); lastErrorError = tempError; return tempError; } #else AuOptional TryFetchOSError() { return {}; } AuOptional TryGetOrFetchOSError() { return {}; } #endif static AuUInt32 gFenceCError = -1; AuUInt32 GetCErrorFence() { return gFenceCError; } AuOptional TryFetchCError() { static AuUInt32 lastError = 0; auto errorNumber = errno; if (!errorNumber) { return {}; } if (lastError == errorNumber) { return errorNumber; } gFenceCError++; gFenceId++; return lastError = errorNumber; } AuOptional TryGetOrFetchCError() { static AuOptional lastErrorError; static AuOptional lastErrorFence; AuOptional tempError = TryFetchCError(); if (!tempError) { return lastErrorError; } if (lastErrorFence) { if (lastErrorFence.value() != GetCErrorFence()) { errno = 0; Telemetry::InsertCError(tempError.value()); } } lastErrorFence = GetCErrorFence(); lastErrorError = tempError; return tempError; } AuUInt32 ReportStackTrace(const StackTrace& trace, const AuString& message) { AU_LOCK_GUARD(gLock); tlsLastStackTrace = trace; tlsLastExceptionMessage = message; tlsLastBackTrace = gStackTraceFence++; gFenceId++; return tlsLastBackTrace; } AuUInt32 GetFenceId() { return gFenceId++; } AUKN_SYM AuString GetLastErrorStack() { return StringifyStackTrace(GetLastStackTrace()); } AUKN_SYM StackTrace GetLastStackTrace() { AU_LOCK_GUARD(gLock); return tlsLastStackTrace; } AUKN_SYM StackTrace GetStackTrace() { #if defined(AURORA_PLATFORM_WIN32) || defined(AURORA_IS_LINUX_DERIVED) return PlatformWalkCallStack(); #else return {}; #endif } AUKN_SYM AuString GetLastException() { AU_LOCK_GUARD(gLock); return tlsLastExceptionMessage; } AUKN_SYM OSError_t GetLastSystemMessage() { return TryGetOrFetchOSError().value_or(OSError_t{}); } AUKN_SYM void ErrorCategorySet(EFailureCategory category) { tlsCategory = category; } AUKN_SYM void ErrorCategoryClear() { tlsCategory = EFailureCategory::kFailureNone; } AUKN_SYM EFailureCategory ErrorCategoryGet() { return tlsCategory; } AUKN_SYM void PrintError() { AuUInt32 rng = GetFenceId(); Telemetry::BeginBlock(); try { Telemetry::InsertManualFence(rng); static AuUInt32 cLastFence = 0; auto cFence = GetCErrorFence(); auto cError = TryGetOrFetchCError(); if ((cError) && (cFence != cLastFence)) { AuLogWarn("Language Error: {} ({})", strerror(*cError), *cError); cLastFence = cFence; } static AuUInt32 osLastFence = 0; auto osFence = GetOSErrorFence(); auto osError = TryGetOrFetchOSError(); if ((osError) && (osFence != osLastFence)) { AuLogWarn("Operating System Error: {} (0x{:x})", osError->second, osError->first); osLastFence = osFence; } Telemetry::InsertBackTrace(tlsLastBackTrace); Telemetry::InsertManualFence(rng); } catch (...) { } Telemetry::EndBlock(); } AUKN_SYM void CheckErrors() { AuUInt32 rng = GetFenceId(); Telemetry::BeginBlock(); try { Telemetry::InsertManualFence(rng); TryGetOrFetchCError(); TryGetOrFetchOSError(); Telemetry::InsertBackTrace(tlsLastBackTrace); Telemetry::InsertManualFence(rng); } catch (...) { } Telemetry::EndBlock(); } #if 0 // compile me somewhere without public headers for compat if i care for some reason later (i probably wont) AUKN_SYM void _PushError(AuUInt address, EFailureCategory category, const char *msg, AuUInt16 uLine); AUKN_SYM void _PushError(AuUInt address, EFailureCategory category, const char *msg) { _PushError(address, category, msg, 0); } #endif AUKN_SYM void _PushError(AuUInt address, EFailureCategory category, const char *msg, AuUInt16 uLine) { LastError error {address, category, msg}; // Oi, developer #if defined(AU_CFG_ID_DEBUG) DebugBreak(); #endif tlsCategory = category; try { if (ShouldPushErrorStackInternal()) { auto pMessage = AuMakeSharedThrow(); pMessage->uDebugBuildSourceLineHint = uLine; pMessage->pStringMessage = AuMakeSharedThrow(msg); pMessage->eFailureCategory = category; PushErrorStackInternal(pMessage); } } catch (...) { } // Cry about it to telemetry with other errors if available AuUInt32 rng = GetFenceId(); Telemetry::BeginBlock(); try { Telemetry::InsertManualFence(rng); Telemetry::InsertMsgError(error); TryGetOrFetchCError(); TryGetOrFetchOSError(); Telemetry::InsertBackTrace(tlsLastBackTrace); Telemetry::InsertManualFence(rng); } catch (...) { } Telemetry::EndBlock(); // Eh, dont spam nested to a logger if its not going to make sense to a normie if ((category == EFailureCategory::kFailureNested) && (msg == nullptr)) { return; } // Print to console if internal #if defined(DEBUG) || defined(STAGING) PrintError(); // Is anyone listening? AuLogWarn("ERROR: {}", error.pDbgMessage); #endif } AUKN_SYM AuString StackTraceEntry::Stringify() const { AU_DEBUG_MEMCRUNCH; const auto frame = *this; AuString backTraceBuffer; try { backTraceBuffer.reserve(512 - 32); // 512 seems like a nice length minus some overhead for an allocators header backTraceBuffer += fmt::format("\tAddress: 0x{:x} (0x{:x})", frame.relAddress ? frame.relAddress : frame.address, frame.relAddress ? frame.address : frame.relAddress); if (frame.module) { auto modName = frame.module.value(); if (modName.size()) { backTraceBuffer += fmt::format(" within {}", modName); } else { backTraceBuffer += ", invalid module"; } } else { backTraceBuffer += ", unknown module"; } if (frame.label) { auto parts = AuSplitString(frame.label.value(), "+"); if (auto resultName = DemangleName(AuString(parts[0]))) { if (resultName.value() == parts[0]) { backTraceBuffer += fmt::format(" ({}) \n", frame.label.value()); } else { backTraceBuffer += fmt::format(" ({} a/k/a {}) \n", resultName.value(), frame.label.value()); } } else if (auto resultName = DemangleName(frame.label.value())) { if (resultName.value() == frame.label.value()) { backTraceBuffer += fmt::format(" ({}) \n", frame.label.value()); } else { backTraceBuffer += fmt::format(" ({} a/k/a {}) \n", resultName.value(), frame.label.value()); } } else { backTraceBuffer += fmt::format(" ({}) \n", frame.label.value()); } } else { backTraceBuffer += ", unknown function\n"; } if (frame.file) { const auto &re = frame.file.value(); backTraceBuffer += fmt::format("\t\t{}:{} ({}) \n", AuGet<0>(re), AuGet<1>(re), AuGet<2>(re)); } else { backTraceBuffer += "\t\t[proprietary]\n"; } return backTraceBuffer; } catch (...) { return {}; } } AUKN_SYM AuString StringifyStackTrace(const StackTrace &backtrace) { AU_DEBUG_MEMCRUNCH; AuString backTraceBuffer; try { backTraceBuffer.reserve(2048); backTraceBuffer += "Unwinding call frame:"; for (const auto &frame : backtrace) { backTraceBuffer += "\n"; backTraceBuffer += frame.Stringify(); } return backTraceBuffer; } catch (...) { return {}; } } void InitDebug() { InitMemoryCrunch(); #if defined(AURORA_PLATFORM_WIN32) InitWin32(); #endif #if defined(AURORA_IS_MODERNNT_DERIVED) InitNT(); #endif #if defined(AURORA_IS_POSIX_DERIVED) InitUNIX(); #endif } }