Browse Source

Added libbacktrace support.

Бранимир Караџић 1 year ago
parent
commit
c875164bd1
4 changed files with 236 additions and 2 deletions
  1. 44 2
      include/bx/debug.h
  2. 2 0
      src/bx.cpp
  3. 180 0
      src/debug.cpp
  4. 10 0
      tests/run_test.cpp

+ 44 - 2
include/bx/debug.h

@@ -12,27 +12,69 @@
 namespace bx
 namespace bx
 {
 {
 	class StringView;
 	class StringView;
+	class WriterI;
+	class Error;
 
 
+	/// Break in debugger.
 	///
 	///
 	void debugBreak();
 	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);
 	void debugOutput(const StringView& _str);
 
 
+	/// Write formatted string to debug output.
 	///
 	///
 	void debugPrintfVargs(const char* _format, va_list _argList);
 	void debugPrintfVargs(const char* _format, va_list _argList);
 
 
+	/// Write formatted string to debug output.
 	///
 	///
 	void debugPrintf(const char* _format, ...);
 	void debugPrintf(const char* _format, ...);
 
 
+	/// Write hex data into debug output.
 	///
 	///
 	void debugPrintfData(const void* _data, uint32_t _size, const char* _format, ...);
 	void debugPrintfData(const void* _data, uint32_t _size, const char* _format, ...);
 
 
+	/// Return debug output writer.
+	///
+	/// @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.
 	///
 	///
-	struct WriterI* getDebugOut();
+	void debugOutputCallstack(uint32_t _skip);
 
 
 } // namespace bx
 } // namespace bx
 
 

+ 2 - 0
src/bx.cpp

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

+ 180 - 0
src/debug.cpp

@@ -8,6 +8,20 @@
 #include <bx/readerwriter.h> // WriterI
 #include <bx/readerwriter.h> // WriterI
 #include <inttypes.h>        // PRIx*
 #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
 #if BX_CRT_NONE
 #	include <bx/crt0.h>
 #	include <bx/crt0.h>
 #elif BX_PLATFORM_ANDROID
 #elif BX_PLATFORM_ANDROID
@@ -199,4 +213,170 @@ namespace bx
 		return &s_debugOut;
 		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
 } // namespace bx

+ 10 - 0
tests/run_test.cpp

@@ -6,6 +6,12 @@
 #define CATCH_CONFIG_RUNNER
 #define CATCH_CONFIG_RUNNER
 #include "test.h"
 #include "test.h"
 #include <bx/string.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)
 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::vprintf(_format, _argList);
 	bx::printf("\n");
 	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.
 	// Throwing exceptions is required for testing asserts being trigged.
 	// Use REQUIRE_ASSERTS to test asserts.
 	// Use REQUIRE_ASSERTS to test asserts.
 	throw std::exception();
 	throw std::exception();