diff --git a/include/bx/debug.h b/include/bx/debug.h index 83d5556..74eb720 100644 --- a/include/bx/debug.h +++ b/include/bx/debug.h @@ -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 diff --git a/src/bx.cpp b/src/bx.cpp index cc51420..b84de3f 100644 --- a/src/bx.cpp +++ b/src/bx.cpp @@ -35,6 +35,8 @@ namespace bx pos += snprintf(&temp[pos], max(0, sizeof(temp)-pos), "\n"); debugOutput(temp); + debugOutputCallstack(2); + return true; } diff --git a/src/debug.cpp b/src/debug.cpp index 6d6cb0e..3ea972e 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -8,6 +8,20 @@ #include // WriterI #include // 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 // backtrace +# include // backtrace_syminfo +# include // abi::__cxa_demangle +#endif // BX_CONFIG_CALLSTACK_* + #if BX_CRT_NONE # include #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(""); + 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 diff --git a/tests/run_test.cpp b/tests/run_test.cpp index a67f549..48361ca 100644 --- a/tests/run_test.cpp +++ b/tests/run_test.cpp @@ -6,6 +6,12 @@ #define CATCH_CONFIG_RUNNER #include "test.h" #include +#include + +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();