468 lines
11 KiB
C++
468 lines
11 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: Debug.cpp
|
|
Date: 2021-6-12
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "Debug.hpp"
|
|
#include <Source/Telemetry/Telemetry.hpp>
|
|
#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
|
|
|
|
#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<OSError_t> 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<OSError_t> TryGetOrFetchOSError()
|
|
{
|
|
static AuOptional<OSError_t> lastErrorError;
|
|
static AuOptional<AuUInt32> lastErrorFence;
|
|
|
|
AuOptional<OSError_t> 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<OSError_t> TryFetchOSError()
|
|
{
|
|
return {};
|
|
}
|
|
|
|
AuOptional<OSError_t> TryGetOrFetchOSError()
|
|
{
|
|
return {};
|
|
}
|
|
|
|
#endif
|
|
|
|
static AuUInt32 gFenceCError = -1;
|
|
AuUInt32 GetCErrorFence()
|
|
{
|
|
return gFenceCError;
|
|
}
|
|
|
|
AuOptional<AuUInt32> TryFetchCError()
|
|
{
|
|
static AuUInt32 lastError = 0;
|
|
|
|
auto errorNumber = errno;
|
|
if (!errorNumber)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (lastError == errorNumber)
|
|
{
|
|
return errorNumber;
|
|
}
|
|
|
|
gFenceCError++;
|
|
gFenceId++;
|
|
return lastError = errorNumber;
|
|
}
|
|
|
|
AuOptional<AuUInt32> TryGetOrFetchCError()
|
|
{
|
|
static AuOptional<AuUInt32> lastErrorError;
|
|
static AuOptional<AuUInt32> lastErrorFence;
|
|
|
|
AuOptional<AuUInt32> 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<ThreadMessage>();
|
|
pMessage->uDebugBuildSourceLineHint = uLine;
|
|
pMessage->pStringMessage = AuMakeSharedThrow<AuString>(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 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", 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
|
|
}
|
|
} |