Added libbacktrace support.

This commit is contained in:
Бранимир Караџић
2024-10-24 21:08:47 -07:00
parent 34d2948860
commit c875164bd1
4 changed files with 236 additions and 2 deletions

View File

@@ -12,27 +12,69 @@
namespace bx
{
class StringView;
class WriterI;
class Error;
/// Break in debugger.
///
void debugBreak();
/// Write string to debug output.
///
void debugOutput(const char* _out);
/// @param[in] _str Zero terminated string to write.
///
void debugOutput(const char* _str);
/// Write string to debug output.
///
/// @param[in] _str StringView to write.
///
void debugOutput(const StringView& _str);
/// Write formatted string to debug output.
///
void debugPrintfVargs(const char* _format, va_list _argList);
/// Write formatted string to debug output.
///
void debugPrintf(const char* _format, ...);
/// Write hex data into debug output.
///
void debugPrintfData(const void* _data, uint32_t _size, const char* _format, ...);
/// Return debug output writer.
///
struct WriterI* getDebugOut();
/// @returns Debug output writer.
///
WriterI* getDebugOut();
/// Capture current callstack.
///
/// @param[in] _skip Skip top N stack frames.
/// @param[in] _max Maximum frame to capture.
/// @param[out] _outStack Stack frames array. Must be at least `_max` elements.
///
/// @returns Number of stack frames captured.
///
uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack);
/// Write callstack.
///
/// @param[in] _writer Writer.
/// @param[in] _stack Callstack.
/// @param[in] _num Number of stack addresses in `_stack` array.
/// @param[out] _err Error.
///
/// @returns Number of bytes writen to `_writer`.
///
int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err);
/// Capture call stack, and write it to debug output.
///
/// @param[in] _skip Skip top N stack frames.
///
void debugOutputCallstack(uint32_t _skip);
} // namespace bx

View File

@@ -35,6 +35,8 @@ namespace bx
pos += snprintf(&temp[pos], max(0, sizeof(temp)-pos), "\n");
debugOutput(temp);
debugOutputCallstack(2);
return true;
}

View File

@@ -8,6 +8,20 @@
#include <bx/readerwriter.h> // WriterI
#include <inttypes.h> // PRIx*
#ifndef BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE
# define BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE 0
#elif BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE
# if !BX_PLATFORM_LINUX || !BX_COMPILER_GCC
# error "libbackrace is only supported on GCC/Linux."
# endif // BX_PLATFORM_LINUX && BX_COMPILER_GCC
#endif // BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE
#if BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE
# include <execinfo.h> // backtrace
# include <backtrace.h> // backtrace_syminfo
# include <cxxabi.h> // abi::__cxa_demangle
#endif // BX_CONFIG_CALLSTACK_*
#if BX_CRT_NONE
# include <bx/crt0.h>
#elif BX_PLATFORM_ANDROID
@@ -199,4 +213,170 @@ namespace bx
return &s_debugOut;
}
#if BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE
uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack)
{
const uint32_t max = _skip+_max+1;
void** tmp = (void**)alloca(sizeof(uintptr_t)*max);
const uint32_t numFull = backtrace(tmp, max);
const uint32_t skip = min(_skip + 1 /* skip self */, numFull);
const uint32_t num = numFull - skip;
memCopy(_outStack, tmp + skip, sizeof(uintptr_t)*num);
return num;
}
struct StackTraceContext
{
StackTraceContext()
{
state = backtrace_create_state(NULL, 0, NULL, NULL);
}
struct backtrace_state* state;
};
static StackTraceContext s_stCtx;
struct CallbackData
{
StringView resolvedName;
StringView fileName;
int32_t line;
};
static void backtraceSymInfoCb(void* _data, uintptr_t _pc, const char* _symName, uintptr_t _symVal, uintptr_t _symSize)
{
BX_UNUSED(_pc, _symVal);
CallbackData* cbData = (CallbackData*)_data;
cbData->resolvedName.set(_symName, _symSize);
}
static int backtraceFullCb(void* _data, uintptr_t _pc, const char* _fileName, int32_t _lineNo, const char* _function)
{
BX_UNUSED(_pc, _function);
CallbackData* cbData = (CallbackData*)_data;
if (NULL == _fileName)
{
cbData->fileName.set("<Unknown?>");
cbData->line = -1;
}
else
{
cbData->fileName.set(_fileName);
cbData->line = _lineNo;
}
return 1;
}
// If offset in UTF-8 string doesn't land on rune, walk back until first byte of rune is reached.
static const char* fixPtrToRune(const char* _strBegin, const char* _curr)
{
for (; _curr > _strBegin && (*_curr & 0xc0) == 0x80; --_curr);
return _curr;
}
StringView strTail(const StringView _str, uint32_t _num)
{
return StringView(
fixPtrToRune(_str.getPtr(), _str.getTerm() - min(_num, _str.getLength() ) )
, _str.getTerm()
);
}
int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err)
{
BX_ERROR_SCOPE(_err);
char demangleBuf[4096];
size_t demangleLen = BX_COUNTOF(demangleBuf);
int32_t total = write(_writer, _err, "Callstack (%d):\n", _num);
CallbackData cbData;
for (uint32_t ii = 0; ii < _num && _err->isOk(); ++ii)
{
backtrace_pcinfo(s_stCtx.state, _stack[ii], backtraceFullCb, NULL, &cbData);
StringView demangledName;
if (1 == backtrace_syminfo(s_stCtx.state, _stack[ii], backtraceSymInfoCb, NULL, &cbData) )
{
demangleLen = BX_COUNTOF(demangleBuf);
int32_t demangleStatus;
abi::__cxa_demangle(cbData.resolvedName.getPtr(), demangleBuf, &demangleLen, &demangleStatus);
if (0 == demangleStatus)
{
demangledName.set(demangleBuf, demangleLen);
}
else
{
demangledName = cbData.resolvedName;
}
}
else
{
demangledName = "???";
}
constexpr uint32_t width = 40;
const StringView fn = strTail(cbData.fileName, width);
total += write(_writer, _err
, "\t%2d: %-*S % 5d: %p %S\n"
, ii
, width
, &fn
, cbData.line
, _stack[ii]
, &demangledName
);
if (0 == strCmp(demangledName, "main", 4) )
{
if (0 != _num-1-ii)
{
total += write(_writer, _err
, "\t... %d more stack frames below 'main'.\n"
, _num-1-ii
);
}
break;
}
}
return total;
}
#else
uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack)
{
BX_UNUSED(_skip, _max, _outStack);
return 0;
}
int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err)
{
BX_UNUSED(_writer, _stack, _num, _err);
return 0;
}
#endif // BX_CONFIG_CALLSTACK_*
void debugOutputCallstack(uint32_t _skip)
{
uintptr_t stack[32];
const uint32_t num = getCallStack(_skip + 1 /* skip self */, BX_COUNTOF(stack), stack);
writeCallstack(getDebugOut(), stack, num, ErrorIgnore{});
}
} // namespace bx

View File

@@ -6,6 +6,12 @@
#define CATCH_CONFIG_RUNNER
#include "test.h"
#include <bx/string.h>
#include <bx/file.h>
namespace bx
{
void debugOutputCallstack(uint32_t _skip);
}
bool testAssertHandler(const bx::Location& _location, const char* _format, va_list _argList)
{
@@ -13,6 +19,10 @@ bool testAssertHandler(const bx::Location& _location, const char* _format, va_li
bx::vprintf(_format, _argList);
bx::printf("\n");
uintptr_t stack[32];
const uint32_t num = bx::getCallStack(2 /* skip self */, BX_COUNTOF(stack), stack);
bx::writeCallstack(bx::getStdOut(), stack, num, bx::ErrorIgnore{});
// Throwing exceptions is required for testing asserts being trigged.
// Use REQUIRE_ASSERTS to test asserts.
throw std::exception();