/*** 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 #if defined(AURORA_PLATFORM_WIN32) #include "ExceptionWatcher.Win32.hpp" #endif namespace Aurora::Debug { static thread_local AuUInt32 tlsLastBackTrace = 0xFFFFFFFF; static thread_local StackTrace tlsLastStackTrace; static thread_local AuString tlsLastExceptionMessage; 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 {}; } ret = err; 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() { return {}; } AUKN_SYM AuString GetLastException() { AU_LOCK_GUARD(gLock); return tlsLastExceptionMessage; } AUKN_SYM OSError_t GetLastSystemMessage() { return TryGetOrFetchOSError().value_or(OSError_t{}); } 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(); } AUKN_SYM void _PushError(AuUInt address, EFailureCategory category, const char *msg) { LastError error {address, category, msg ? msg : ""}; // Oi, developer #if defined(DEBUG) DebugBreak(); #endif // 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(); // Is anyone listening? // Print to console if internal // Eh, dont spam nested to a logger if its not going to make sense to a normie if ((category == EFailureCategory::kFailureNested) && (msg == nullptr)) { return; } #if defined(DEBUG) || defined(STAGING) PrintError(); AuLogWarn("ERROR: {}", error.dbg); #endif } AUKN_SYM AuString StackTraceEntry::Stringify() const { const auto frame = *this; AuString backTraceBuffer; try { backTraceBuffer.reserve(512 - 32); // 512 seems like a nice length minus some overhead for a bucket allocator 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) { 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", std::get<0>(re), std::get<1>(re), std::get<2>(re)); } else { backTraceBuffer += "\t\t[proprietary]\n"; } return backTraceBuffer; } catch (...) { return {}; } } AUKN_SYM AuString StringifyStackTrace(const StackTrace &backtrace) { 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() { #if defined(AURORA_PLATFORM_WIN32) InitWin32(); #endif } }