From c8128850f8b92727d8b0a47e55b5edb44837eb84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Sat, 27 Sep 2025 12:51:02 -0700 Subject: [PATCH] Implement getCallStackFast/Exact for fast call-stack backtrace. --- include/bx/debug.h | 18 +- include/bx/filepath.h | 4 + include/bx/inline/string.inl | 19 + include/bx/string.h | 9 + src/bx.cpp | 2 +- src/debug.cpp | 902 ++++++++++++++++++++++++++++++++--- src/filepath.cpp | 6 + tests/run_test.cpp | 2 +- tests/string_test.cpp | 15 +- 9 files changed, 908 insertions(+), 69 deletions(-) diff --git a/include/bx/debug.h b/include/bx/debug.h index f5bc37d..82e4d32 100644 --- a/include/bx/debug.h +++ b/include/bx/debug.h @@ -49,7 +49,7 @@ namespace bx /// WriterI* getDebugOut(); - /// Capture current callstack. + /// Capture current callstack fast. /// /// @param[in] _skip Skip top N stack frames. /// @param[in] _max Maximum frame to capture. @@ -57,7 +57,17 @@ namespace bx /// /// @returns Number of stack frames captured. /// - uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack); + uint32_t getCallStackFast(uint32_t _skip, uint32_t _max, uintptr_t* _outStack); + + /// Capture current callstack with slower but more accurate method. + /// + /// @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 getCallStackExact(uint32_t _skip, uint32_t _max, uintptr_t* _outStack); /// Write callstack. /// @@ -68,9 +78,9 @@ namespace bx /// /// @returns Number of bytes writen to `_writer`. /// - int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err); + int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err); - /// Capture call stack, and write it to debug output. + /// Capture call stack with `bx::getCallStackExact`, and write it to debug output. /// /// @param[in] _skip Skip top N stack frames. /// diff --git a/include/bx/filepath.h b/include/bx/filepath.h index ca38985..9a8429f 100644 --- a/include/bx/filepath.h +++ b/include/bx/filepath.h @@ -60,6 +60,10 @@ namespace bx /// FilePath(const StringView& _str); + /// Assign file path from string. + /// + FilePath& operator=(const char* _rhs); + /// Assign file path from string. /// FilePath& operator=(const StringView& _rhs); diff --git a/include/bx/inline/string.inl b/include/bx/inline/string.inl index d7108c8..c526a56 100644 --- a/include/bx/inline/string.inl +++ b/include/bx/inline/string.inl @@ -187,6 +187,25 @@ namespace bx return m_0terminated; } + inline bool operator==(const StringView& _lhs, const StringView& _rhs) + { + return 0 == strCmp(_lhs, _rhs); + } + + inline bool overlap(const StringView& _a, const StringView& _b) + { + return _a.getTerm() > _b.getPtr() + && _b.getTerm() > _a.getPtr() + ; + } + + inline bool contain(const StringView& _a, const StringView& _b) + { + return _a.getPtr() <= _b.getPtr() + && _a.getTerm() >= _b.getTerm() + ; + } + template inline FixedStringT::FixedStringT() : m_len(0) diff --git a/include/bx/string.h b/include/bx/string.h index fb59b9c..9452806 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -137,6 +137,15 @@ namespace bx bool m_0terminated; }; + /// Compare two string views. + bool operator==(const StringView& _lhs, const StringView& _rhs); + + /// Returns true if two string views overlap. + bool overlap(const StringView& _a, const StringView& _b); + + /// Returns true if string view `_a` contains string view `_b`. + bool contain(const StringView& _a, const StringView& _b); + /// Fixed capacity string. /// template diff --git a/src/bx.cpp b/src/bx.cpp index 4122fab..80c613d 100644 --- a/src/bx.cpp +++ b/src/bx.cpp @@ -42,7 +42,7 @@ namespace bx total += write(&smb, "\n\n", &err); uintptr_t stack[32]; - const uint32_t num = getCallStack(2 /* skip self */ + _skip, BX_COUNTOF(stack), stack); + const uint32_t num = getCallStackExact(2 /* skip self */ + _skip, BX_COUNTOF(stack), stack); total += writeCallstack(&smb, stack, num, &err); total += write(&smb, &err, diff --git a/src/debug.cpp b/src/debug.cpp index b1b3b78..b557ffd 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -7,9 +7,33 @@ #include // isPrint #include // WriterI #include // exit +#include // ProcessReader #include // PRIx* +#if BX_PLATFORM_LINUX || BX_PLATFORM_OSX +# include // abi::__cxa_demangle +# include // backtrace, backtrace_symbols +# include // _Unwind_Backtrace +# include +#endif // BX_PLATFORM_* + +#ifndef BX_CONFIG_CALLSTACK_USE_EXECINFO +# if defined(_EXECINFO_H) || defined(_EXECINFO_H_) +# define BX_CONFIG_CALLSTACK_USE_EXECINFO 1 +# else +# define BX_CONFIG_CALLSTACK_USE_EXECINFO 0 +# endif // defined... +#endif // BX_CONFIG_CALLSTACK_USE_EXECINFO + +#ifndef BX_CONFIG_CALLSTACK_USE_UNWIND +# if defined(_UNWIND_H) || defined(__UNWIND_H__) +# define BX_CONFIG_CALLSTACK_USE_UNWIND 1 +# else +# define BX_CONFIG_CALLSTACK_USE_UNWIND 0 +# endif // defined... +#endif // BX_CONFIG_CALLSTACK_USE_UNWIND + #ifndef BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE # define BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE 0 #elif BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE @@ -19,9 +43,7 @@ #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_* #ifndef BX_CONFIG_EXCEPTION_HANDLING_USE_WINDOWS_SEH @@ -29,35 +51,14 @@ #endif // BX_CONFIG_EXCEPTION_HANDLING_USE_WINDOWS_SEH #ifndef BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS -# define BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS 0 \ - | (BX_PLATFORM_LINUX && !BX_CRT_NONE) +# define BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS ( (0 \ + | BX_PLATFORM_LINUX \ + | BX_PLATFORM_OSX \ + ) && !BX_CRT_NONE) #endif // BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS #if BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS # include -#elif BX_CONFIG_EXCEPTION_HANDLING_USE_WINDOWS_SEH - -struct ExceptionRecord -{ - uint32_t exceptionCode; - uint32_t exceptionFlags; - - ExceptionRecord* exceptionRecord; - - uintptr_t exceptionAddress; - uint32_t numberParameters; - uintptr_t exceptionInformation[15]; -}; - -struct ExceptionPointers -{ - ExceptionRecord* exceptionRecord; - void* contextRecord; -}; - -typedef uint32_t (__stdcall* TopLevelExceptionFilterFn)(ExceptionPointers* _exceptionInfo); - -extern "C" __declspec(dllimport) TopLevelExceptionFilterFn __stdcall SetUnhandledExceptionFilter(TopLevelExceptionFilterFn _topLevelExceptionFilter); #endif // BX_CONFIG_EXCEPTION_HANDLING_* #if BX_CRT_NONE @@ -251,21 +252,331 @@ namespace bx return &s_debugOut; } -#if BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE - uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) +#if BX_CONFIG_CALLSTACK_USE_UNWIND + struct UnwindCallbackData { - const uint32_t max = _skip+_max+1; - void** tmp = (void**)BX_STACK_ALLOC(sizeof(uintptr_t)*max); + uint32_t skip; + uint32_t max; + uint32_t num; + uintptr_t* outStack; + }; - const uint32_t numFull = backtrace(tmp, max); + static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* _ctx, void* _arg) + { + UnwindCallbackData& ucd = *(UnwindCallbackData*)_arg; + + if (ucd.num < ucd.max) + { + if (0 < ucd.skip) + { + --ucd.skip; + return _URC_NO_REASON; + } + + uintptr_t addr = _Unwind_GetIP(_ctx); + if (0 == addr) + { + return _URC_END_OF_STACK; + } + + ucd.outStack[ucd.num++] = uintptr_t(addr); + return _URC_NO_REASON; + } + + return _URC_END_OF_STACK; + } +#endif // BX_CONFIG_CALLSTACK_USE_UNWIND + + BX_NO_INLINE uint32_t getCallStackUnwind(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_CONFIG_CALLSTACK_USE_UNWIND + UnwindCallbackData ucd = + { + .skip = _skip + 1, + .max = _max, + .num = 0, + .outStack = _outStack, + }; + + _Unwind_Backtrace(unwindCallback, &ucd); + + return ucd.num; +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_CONFIG_CALLSTACK_USE_UNWIND + } + + BX_NO_INLINE uint32_t getCallStackExecInfoBacktrace(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_CONFIG_CALLSTACK_USE_EXECINFO + const uint32_t max = _skip+_max+1; + uintptr_t* tmp = (uintptr_t*)BX_STACK_ALLOC(sizeof(uintptr_t)*max); + + const uint32_t numFull = backtrace( (void**)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; +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_CONFIG_CALLSTACK_USE_EXECINFO } + static const uintptr_t* nextStackFrame(const uintptr_t* _stackFrame) + { + const uintptr_t* newStackFrame = (const uintptr_t*)*_stackFrame; + + if (newStackFrame <= _stackFrame) + { + return NULL; + } + + if (uintptr_t(newStackFrame) & (sizeof(uintptr_t) - 1) ) + { + return NULL; + } + + return newStackFrame; + } + + BX_NO_INLINE uint32_t getCallStackSystemVAbi(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { + if (BX_ENABLED( (BX_PLATFORM_LINUX || BX_PLATFORM_OSX) && BX_ARCH_64BIT) ) + { +#if BX_COMPILER_GCC || BX_COMPILER_CLANG + const uintptr_t* stackFrame = (const uintptr_t*)__builtin_frame_address(0); +#else + const uintptr_t* stackFrame = NULL; +#endif // BX_COMPILER_... + + uint32_t num = 0; + + while (NULL != stackFrame + && num < _max) + { + if (uintptr_t(0) == stackFrame[1]) + { + break; + } + + if (BX_UNLIKELY(0 < _skip) ) + { + --_skip; + } + else + { + _outStack[num++] = stackFrame[1]; + } + + stackFrame = nextStackFrame(stackFrame); + } + + return num; + } + + return 0; + } + + BX_NO_INLINE uint32_t getCallStackGccBuiltin(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_COMPILER_GCC || BX_COMPILER_CLANG + BX_PRAGMA_DIAGNOSTIC_PUSH(); + BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wframe-address"); + + uint32_t num = 0; + +#define RETURN_ADDRESS(_x) \ + if (num < _max) \ + { \ + if (0 < _skip) \ + { \ + --_skip; \ + } \ + else \ + { \ + if (NULL == __builtin_frame_address(_x) ) \ + { \ + return num; \ + } \ + \ + void* addr = __builtin_return_address(_x); \ + \ + if (NULL == addr) \ + { \ + return num; \ + } \ + \ + _outStack[num++] = uintptr_t(addr); \ + } \ + } \ + else \ + { \ + return num; \ + } + + RETURN_ADDRESS(0); + RETURN_ADDRESS(1); + RETURN_ADDRESS(2); + RETURN_ADDRESS(3); + RETURN_ADDRESS(4); + RETURN_ADDRESS(5); + RETURN_ADDRESS(6); + RETURN_ADDRESS(7); + RETURN_ADDRESS(8); + RETURN_ADDRESS(9); + + RETURN_ADDRESS(10); + RETURN_ADDRESS(11); + RETURN_ADDRESS(12); + RETURN_ADDRESS(13); + RETURN_ADDRESS(14); + RETURN_ADDRESS(15); + RETURN_ADDRESS(16); + RETURN_ADDRESS(17); + RETURN_ADDRESS(18); + RETURN_ADDRESS(19); + + RETURN_ADDRESS(20); + RETURN_ADDRESS(21); + RETURN_ADDRESS(22); + RETURN_ADDRESS(23); + RETURN_ADDRESS(24); + RETURN_ADDRESS(25); + RETURN_ADDRESS(26); + RETURN_ADDRESS(27); + RETURN_ADDRESS(28); + RETURN_ADDRESS(29); + + RETURN_ADDRESS(30); + RETURN_ADDRESS(31); + +#undef RETURN_ADDRESS + + BX_PRAGMA_DIAGNOSTIC_POP(); + + return num; +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_COMPILER_GCC || BX_COMPILER_CLANG + } + +#if !BX_PLATFORM_WINDOWS +# define __stdcall +#endif // !BX_PLATFORM_WINDOWS + + typedef uint16_t (__stdcall* RtlCaptureStackBackTraceFn)(uint32_t _framesToSkip, uint32_t _framesToCapture, void** _outBacktrace, uint32_t* _outhash); + + uint16_t __stdcall stubTryLoadRtlCaptureStackBackTrace(uint32_t _framesToSkip, uint32_t _framesToCapture, void** _outBacktrace, uint32_t* _outhash); + RtlCaptureStackBackTraceFn RtlCaptureStackBackTrace = stubTryLoadRtlCaptureStackBackTrace; + + uint16_t __stdcall stubRtlCaptureStackBackTrace(uint32_t _framesToSkip, uint32_t _framesToCapture, void** _outBacktrace, uint32_t* _outhash) + { + BX_UNUSED(_framesToSkip, _framesToCapture, _outBacktrace, _outhash); + return 0; + } + + uint16_t __stdcall stubTryLoadRtlCaptureStackBackTrace(uint32_t _framesToSkip, uint32_t _framesToCapture, void** _outBacktrace, uint32_t* _outhash) + { + void* kernel32Dll = bx::dlopen("kernel32.dll"); + + RtlCaptureStackBackTraceFn fn = NULL != kernel32Dll + ? bx::dlsym(kernel32Dll, "RtlCaptureStackBackTrace") + : NULL + ; + + RtlCaptureStackBackTrace = NULL != fn + ? fn + : stubRtlCaptureStackBackTrace + ; + + return RtlCaptureStackBackTrace(_framesToSkip, _framesToCapture, _outBacktrace, _outhash); + } + + BX_NO_INLINE uint32_t getCallStackWinRtl(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_PLATFORM_WINDOWS + return RtlCaptureStackBackTrace(_skip + 1 /* skip self */, _max, (void**)_outStack, NULL); +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_PLATFORM_WINDOWS + } + + BX_NO_INLINE uint32_t getCallStackWinAbi(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { + if (BX_ENABLED(BX_PLATFORM_WINDOWS && BX_CPU_X86 && BX_ARCH_32BIT) ) + { + const uintptr_t* stackFrame = (uintptr_t*)&_skip - 2; + + uint32_t num = 0; + + while (NULL != stackFrame + && num < _max) + { + if (uintptr_t(0) == stackFrame[1]) + { + break; + } + + if (BX_UNLIKELY(0 < _skip) ) + { + --_skip; + } + else + { + _outStack[num++] = stackFrame[1]; + } + + stackFrame = nextStackFrame(stackFrame); + } + + return num; + } + else if (BX_ENABLED(BX_PLATFORM_WINDOWS && BX_CPU_X86 && BX_ARCH_64BIT) ) + { + return getCallStackWinRtl(_skip + 1 /* skip self */, _max, _outStack); + } + + return 0; + } + + BX_NO_INLINE uint32_t getCallStackFast(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_PLATFORM_LINUX || BX_PLATFORM_OSX + return getCallStackSystemVAbi(_skip + 1 /* skip self */, _max, _outStack); +#elif BX_PLATFORM_WINDOWS + return getCallStackWinAbi(_skip + 1 /* skip self */, _max, _outStack); +#elif BX_COMPILER_GCC || BX_COMPILER_CLANG + return getCallStackGccBuiltin(_skip + 1 /* skip self */, _max, _outStack); +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_PLATFORM_* + } + + BX_NO_INLINE uint32_t getCallStackExact(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + { +#if BX_PLATFORM_LINUX + return getCallStackUnwind(_skip + 1 /* skip self */, _max, _outStack); +#elif BX_PLATFORM_OSX + return getCallStackExecInfoBacktrace(_skip + 1 /* skip self */, _max, _outStack); +#elif BX_PLATFORM_WINDOWS + return getCallStackWinRtl(_skip + 1 /* skip self */, _max, _outStack); +#else + BX_UNUSED(_skip, _max, _outStack); + return 0; +#endif // BX_PLATFORM_* + } + + static constexpr uint32_t kWidth = 80; + static constexpr uint32_t kWidthPc = 16; + +#if BX_CONFIG_CALLSTACK_USE_LIB_BACKTRACE struct StackTraceContext { StackTraceContext() @@ -312,7 +623,7 @@ namespace bx return 1; } - int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err) + int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err) { BX_ERROR_SCOPE(_err); @@ -321,8 +632,7 @@ namespace bx int32_t total = write(_writer, _err, "Callstack (%d):\n", _num); - constexpr uint32_t kWidth = 40; - total += write(_writer, _err, "\t #: %-*s Line: PC --- Function ---\n", kWidth, "File ---"); + total += write(_writer, _err, "\t #: %-*s Line %-*s Function ---\n", kWidth, "File ---", kWidthPc, "PC ---"); CallbackData cbData; @@ -330,9 +640,10 @@ namespace bx { backtrace_pcinfo(s_stCtx.state, _stack[ii], backtraceFullCb, NULL, &cbData); - StringView demangledName; + StringView demangledName = ""; - if (1 == backtrace_syminfo(s_stCtx.state, _stack[ii], backtraceSymInfoCb, NULL, &cbData) ) + if (1 == backtrace_syminfo(s_stCtx.state, _stack[ii], backtraceSymInfoCb, NULL, &cbData) + && !cbData.resolvedName.isEmpty() ) { demangleLen = BX_COUNTOF(demangleBuf); int32_t demangleStatus; @@ -340,26 +651,19 @@ namespace bx if (0 == demangleStatus) { - demangledName.set(demangleBuf, demangleLen); + demangledName.set({demangleBuf, narrowCast(demangleLen) }); } - else - { - demangledName = cbData.resolvedName; - } - } - else - { - demangledName = "???"; } - const StringView fn = strTail(cbData.fileName, kWidth); + const StringView fileName = strTail(cbData.fileName, kWidth); total += write(_writer, _err - , "\t%2d: %-*S % 5d: %p %S\n" + , "\t%2d: %-*S % 5d %*p %S\n" , ii , kWidth - , &fn + , &fileName , cbData.line + , kWidthPc , _stack[ii] , &demangledName ); @@ -380,27 +684,480 @@ namespace bx return total; } -#else +#elif BX_PLATFORM_WINDOWS - uint32_t getCallStack(uint32_t _skip, uint32_t _max, uintptr_t* _outStack) + struct DbgHelpSymbolInfo { - BX_UNUSED(_skip, _max, _outStack); - return 0; + uint32_t SizeOfStruct; + uint32_t TypeIndex; + uint64_t Reserved[2]; + uint32_t Index; + uint32_t Size; + uint64_t ModBase; + uint32_t Flags; + uint64_t Value; + uint64_t Address; + uint32_t Register; + uint32_t Scope; + uint32_t Tag; + uint32_t NameLen; + uint32_t MaxNameLen; + char Name[512]; + }; + + struct ImageHlpLine + { + uint32_t SizeOfStruct; + void* Key; + uint32_t LineNumber; + const char* FileName; + uintptr_t Address; + }; + + typedef bool (__stdcall* SymInitializeFn)(void* _process, const char* _userSearchPath, bool _invadeProcess); + typedef bool (__stdcall* SymCleanupFn)(void* _process); + typedef bool (__stdcall* SymFromAddrFn)(void* _process, uint64_t _address, uint64_t* _outDisplacement, DbgHelpSymbolInfo* _inoutSymbol); + typedef bool (__stdcall* SymGetLineFromAddrFn)(void* _process, uintptr_t _address, uint32_t* _outDisplacement, ImageHlpLine* _inoutLine); + + constexpr uint32_t kSymOptUndName = 0x00000002; + constexpr uint32_t kSymOptDeferredLoads = 0x00000004; + constexpr uint32_t kSymOptLoadLines = 0x00000010; + + static void* kCurrentProcess = (void*)UINTPTR_MAX; + + typedef uint32_t (__stdcall* SymSetOptionsFn)(uint32_t _options); + + struct DbgHelpSymbolResolve + { + DbgHelpSymbolResolve() + { + m_imageHlpDll = dlopen("dbghelp.dll"); + + if (NULL != m_imageHlpDll) + { + m_symInitialize = dlsym(m_imageHlpDll, "SymInitialize"); + m_symCleanup = dlsym(m_imageHlpDll, "SymCleanup"); + m_symFromAddr = dlsym(m_imageHlpDll, "SymFromAddr"); + m_symSetOptions = dlsym(m_imageHlpDll, "SymSetOptions"); + m_symGetLineFromAddr = dlsym(m_imageHlpDll, BX_ARCH_32BIT ? "SymGetLineFromAddr" : "SymGetLineFromAddr64"); + + if (true + && NULL != m_symInitialize + && NULL != m_symCleanup + && NULL != m_symFromAddr + && NULL != m_symSetOptions + && NULL != m_symGetLineFromAddr + ) + { + m_symSetOptions(kSymOptUndName | kSymOptDeferredLoads | kSymOptLoadLines); + + char symCache[1024] = "SRV*"; + uint32_t len = BX_COUNTOF(symCache) - 4; + if (getEnv(symCache + 4, &len, "TEMP") ) + { + len += 4; + snprintf(symCache + len, BX_COUNTOF(symCache) - len, "\\SymbolCache*http://msdl.microsoft.com/download/symbols;"); + } + else + { + symCache[0] = '\0'; + } + + if (!m_symInitialize(kCurrentProcess, symCache, true) ) + { + cleanup(); + } + } + } + } + + ~DbgHelpSymbolResolve() + { + cleanup(); + } + + bool hasSymbols() const + { + return NULL != m_imageHlpDll; + } + + bool resolve(uintptr_t _addr, FixedString1024& _outFunctionName, FilePath& _outFilePath, int32_t& _outLine) const + { + if (hasSymbols() ) + { + DbgHelpSymbolInfo dhsi = {}; + dhsi.SizeOfStruct = sizeof(DbgHelpSymbolInfo) - sizeof(dhsi.Name); + dhsi.MaxNameLen = sizeof(dhsi.Name); + + ImageHlpLine ihl = {}; + ihl.SizeOfStruct = sizeof(ImageHlpLine); + + uint32_t lineDisplacement; + + if (true + && m_symFromAddr(kCurrentProcess, _addr, NULL, &dhsi) + && m_symGetLineFromAddr(kCurrentProcess, _addr, &lineDisplacement, &ihl) + ) + { + _outFunctionName.set(dhsi.Name); + _outFilePath.set(ihl.FileName); + _outLine = ihl.LineNumber; + + return true; + } + } + + return false; + } + + void cleanup() + { + m_symCleanup(kCurrentProcess); + + m_symInitialize = NULL; + m_symCleanup = NULL; + m_symFromAddr = NULL; + m_symSetOptions = NULL; + m_symGetLineFromAddr = NULL; + + dlclose(m_imageHlpDll); + + m_imageHlpDll = NULL; + } + + void* m_imageHlpDll = NULL; + + SymInitializeFn m_symInitialize = NULL; + SymCleanupFn m_symCleanup = NULL; + SymFromAddrFn m_symFromAddr = NULL; + SymSetOptionsFn m_symSetOptions = NULL; + SymGetLineFromAddrFn m_symGetLineFromAddr = NULL; + }; + + static DbgHelpSymbolResolve s_dbgHelpSymbolResolve; + + int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err) + { + int32_t total = write(_writer, _err, "Callstack (%d):\n", _num); + + total += write(_writer, _err, "\t #: %-*s Line %-*s Function ---\n", kWidth, "File ---", kWidthPc, "PC ---"); + + for (uint32_t ii = 0; ii < _num && _err->isOk(); ++ii) + { + FixedString1024 functionName; + FilePath filePath; + int32_t line; + + if (!s_dbgHelpSymbolResolve.resolve(_stack[ii], functionName, filePath, line)) + { + functionName = ""; + filePath = ""; + line = -1; + } + + const StringView fileName = strTail(filePath, kWidth); + + total += write(_writer, _err + , "\t%2d: %-*S % 5d %*p %s\n" + , ii + , kWidth + , &fileName + , line + , kWidthPc + , _stack[ii] + , functionName.getCPtr() + ); + } + + return total; } - int32_t writeCallstack(WriterI* _writer, uintptr_t* _stack, uint32_t _num, Error* _err) +#elif BX_CONFIG_CALLSTACK_USE_EXECINFO + + StringView strConsumeTo(StringView& _input, const StringView& _find) { - BX_UNUSED(_writer, _stack, _num, _err); - return 0; + const StringView to = strFind(_input, _find); + + if (!to.isEmpty() ) + { + const StringView result(_input.getPtr(), to.getPtr() ); + + _input.set(to.getTerm(), _input.getTerm() ); + + return result; + } + + return StringView(); + } + + int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err) + { + int32_t total = write(_writer, _err, "Callstack (%d):\n", _num); + + // Linux: + // ([function]<+offset>)[
] + // + // macOS: + //
+ + char** symbols = backtrace_symbols( (void* const*) _stack, _num); + +# if BX_PLATFORM_LINUX + + struct Addr2Line + { + StringView functionName; + StringView filePath; + int32_t line; + }; + + char addr2LineBuffer[32<<10]; + Addr2Line* addr2Line = (Addr2Line*)BX_STACK_ALLOC(_num * sizeof(Addr2Line) ); + + { + FixedStringT<4096> args; + FilePath first; + + for (uint32_t ii = 0; ii < _num; ++ii) + { + Addr2Line& a2l = addr2Line[ii]; + + a2l.functionName = ""; + a2l.filePath = ""; + a2l.line = -1; + } + + for (uint32_t ii = 0; ii < _num; ++ii) + { + const StringView line(symbols[ii]); + + const StringView parenS = strFind(line, '('); + + if (parenS.isEmpty() ) + { + continue; + } + + const FilePath filePath({ line.getPtr(), parenS.getPtr() }); + + StringView offset(parenS.getTerm(), line.getTerm() ); + const StringView parenE = strFind(offset, ')'); + + if (parenE.isEmpty() ) + { + continue; + } + + offset.set(parenS.getTerm(), parenE.getPtr() ); + + if (first.isEmpty() ) + { + first.set(filePath); + + args.append("-C -f -e "); + args.append(filePath); + } + + if (first != filePath) + { + continue; + } + + args.append(" "); + args.append(offset); + } + + { + ProcessReader reader; + if (open(&reader, "addr2line", args, ErrorIgnore{}) ) + { + int32_t bytes = read(&reader, addr2LineBuffer, sizeof(addr2LineBuffer), ErrorIgnore{}); + + if (-1 != bytes) + { + uint32_t ii = 0; + for (LineReader lr({addr2LineBuffer, bytes}); !lr.isDone(); ++ii) + { + const StringView functionName = lr.next(); + + const StringView filePath = lr.next(); + + const StringView colon = strFind(filePath, ':'); + + if (!colon.isEmpty() ) + { + Addr2Line& a2l = addr2Line[ii]; + + a2l.functionName = functionName; + a2l.filePath = StringView(filePath.getPtr(), colon.getPtr() ); + fromString(&a2l.line, StringView(strWord({ colon.getTerm(), filePath.getTerm() }) ) ); + } + } + } + + close(&reader); + } + } + } +# elif BX_PLATFORM_OSX + + char atosBuffer[32<<10]; + +# endif // BX_PLATFORM_* + + total += write(_writer, _err, "\t #: %-*s Line %-*s Function ---\n", kWidth, "File ---", kWidthPc, "PC ---"); + + for (uint32_t ii = 0; ii < _num && _err->isOk(); ++ii) + { + FixedString1024 functionName; + FilePath filePath(""); + int32_t line = -1; + +# if BX_PLATFORM_LINUX + filePath = addr2Line[ii].filePath; + line = addr2Line[ii].line; + functionName = addr2Line[ii].functionName; + +# elif BX_PLATFORM_OSX + StringView atosFunctionName(""); + + functionName.set( + strWord( + strLTrimSpace( + strLTrimNonSpace( + strLTrimSpace( + strLTrimNonSpace( + strLTrimSpace( + strLTrimNonSpace( + symbols[ii] + ) ) ) ) ) ) ) + ); + + Dl_info info; + dladdr( (const void*)_stack[ii], &info); + + { + char args[4096]; + + snprintf(args, BX_COUNTOF(args), "--fullPath -o %s -l %x %x" + , info.dli_fname + , info.dli_fbase + , _stack[ii] + ); + + ProcessReader reader; + if (open(&reader, "atos", args, ErrorIgnore{}) ) + { + int32_t bytes = read(&reader, atosBuffer, sizeof(atosBuffer), ErrorIgnore{}); + + if (-1 != bytes) + { + for (LineReader lr({atosBuffer, bytes}); !lr.isDone();) + { + StringView input = lr.next(); + + atosFunctionName = strConsumeTo(input, " ("); + + if (atosFunctionName.isEmpty() ) + { + break; + } + + filePath = strConsumeTo(input, ":"); + + if (filePath.isEmpty() ) + { + filePath = ""; + break; + } + + const StringView lineStr = strConsumeTo(input, ")"); + + if (!lineStr.isEmpty() ) + { + fromString(&line, lineStr); + } + } + } + + close(&reader); + } + } + + char demangleBuf[4096]; + size_t demangleLen = BX_COUNTOF(demangleBuf); + int32_t demangleStatus; + abi::__cxa_demangle(functionName.getCPtr(), demangleBuf, &demangleLen, &demangleStatus); + + if (0 == demangleStatus) + { + functionName.set({demangleBuf, narrowCast(demangleLen) }); + } + else + { + functionName = atosFunctionName; + } + +# endif // BX_PLATFORM_* + + const StringView fileName = strTail(filePath, kWidth); + + total += write(_writer, _err + , "\t%2d: %-*S % 5d %*p %s\n" + , ii + , kWidth + , &fileName + , line + , kWidthPc + , _stack[ii] + , functionName.getCPtr() + ); + } + + /*backtrace_symbols_*/::free(symbols); + + return total; + } + +#else + + int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err) + { + int32_t total = write(_writer, _err, "Callstack (%d): - symbol resolve is not available\n", _num); + + total += write(_writer, _err, "\t #: %-*s Line %-*s Function ---\n", kWidth, "File ---", kWidthPc, "PC ---"); + + const StringView fileName = ""; + const StringView demangledName = ""; + + for (uint32_t ii = 0; ii < _num && _err->isOk(); ++ii) + { + total += write(_writer, _err + , "\t%2d: %-*S % 5d %*p %S\n" + , ii + , kWidth + , &fileName + , -1 + , kWidthPc + , _stack[ii] + , &demangledName + ); + } + + return total; } #endif // BX_CONFIG_CALLSTACK_* void debugOutputCallstack(uint32_t _skip) { + char temp[8192]; + StaticMemoryBlockWriter smb(temp, BX_COUNTOF(temp) ); + uintptr_t stack[32]; - const uint32_t num = getCallStack(_skip + 1 /* skip self */, BX_COUNTOF(stack), stack); - writeCallstack(getDebugOut(), stack, num, ErrorIgnore{}); + const uint32_t num = getCallStackExact(_skip + 1 /* skip self */, BX_COUNTOF(stack), stack); + const int32_t total = writeCallstack(&smb, stack, num, ErrorIgnore{}); + + write(getDebugOut(), temp, total, ErrorIgnore{}); } #if BX_CONFIG_EXCEPTION_HANDLING_USE_POSIX_SIGNALS @@ -424,18 +1181,17 @@ namespace bx { BX_TRACE("ExceptionHandler - POSIX"); - stack_t stack; + stack_t stack = {}; stack.ss_sp = s_stack; stack.ss_size = sizeof(s_stack); stack.ss_flags = 0; sigaltstack(&stack, &m_oldStack); - struct sigaction sa; + struct sigaction sa = {}; sa.sa_handler = NULL; sa.sa_sigaction = signalActionHandler; sa.sa_mask = { 0 }; sa.sa_flags = SA_ONSTACK | SA_SIGINFO; - sa.sa_restorer = NULL; for (uint32_t ii = 0; ii < BX_COUNTOF(s_signalInfo); ++ii) { @@ -501,6 +1257,28 @@ namespace bx { /* EXCEPTION_STACK_OVERFLOW */ 0xc00000fdu, "Stack overflow." }, }; + struct ExceptionRecord + { + uint32_t exceptionCode; + uint32_t exceptionFlags; + + ExceptionRecord* exceptionRecord; + + uintptr_t exceptionAddress; + uint32_t numberParameters; + uintptr_t exceptionInformation[15]; + }; + + struct ExceptionPointers + { + ExceptionRecord* exceptionRecord; + void* contextRecord; + }; + + typedef uint32_t (__stdcall* TopLevelExceptionFilterFn)(ExceptionPointers* _exceptionInfo); + + extern "C" __declspec(dllimport) TopLevelExceptionFilterFn __stdcall SetUnhandledExceptionFilter(TopLevelExceptionFilterFn _topLevelExceptionFilter); + struct ExceptionHandler { ExceptionHandler() @@ -526,7 +1304,7 @@ namespace bx } } - if (assertFunction(Location("Exception Handler", UINT32_MAX), 2 + if (assertFunction(Location("Exception Handler", UINT32_MAX), 7 /* topLevelExceptionFilter function + 6 deep stack of OS exception handler */ , "%s Exception Code %x" , name , _info->exceptionRecord->exceptionCode diff --git a/src/filepath.cpp b/src/filepath.cpp index ec3b652..9dfc78e 100644 --- a/src/filepath.cpp +++ b/src/filepath.cpp @@ -303,6 +303,12 @@ namespace bx set(_filePath); } + FilePath& FilePath::operator=(const char* _rhs) + { + set(_rhs); + return *this; + } + FilePath& FilePath::operator=(const StringView& _rhs) { set(_rhs); diff --git a/tests/run_test.cpp b/tests/run_test.cpp index b9af298..8e82fba 100644 --- a/tests/run_test.cpp +++ b/tests/run_test.cpp @@ -19,7 +19,7 @@ bool testAssertHandler(const bx::Location& _location, uint32_t _skip, const char bx::printf("\n"); uintptr_t stack[32]; - const uint32_t num = bx::getCallStack(2 /* skip self */ + _skip, BX_COUNTOF(stack), stack); + const uint32_t num = bx::getCallStackExact(2 /* skip self */ + _skip, BX_COUNTOF(stack), stack); bx::writeCallstack(bx::getStdOut(), stack, num, bx::ErrorIgnore{}); // Throwing exceptions is required for testing asserts being trigged. diff --git a/tests/string_test.cpp b/tests/string_test.cpp index ce91b60..434b379 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -45,7 +45,7 @@ TEST_CASE("StringLiteral", "[string]") REQUIRE(5 == sv.getLength() ); REQUIRE(5 == bx::strLen(sv) ); - REQUIRE(0 == bx::strCmp("abvgd", sv) ); + REQUIRE("abvgd" == sv); } TEST_CASE("stringPrintfTy", "[string]") @@ -218,6 +218,19 @@ TEST_CASE("strCmpV sort", "[string][sort]") } } +TEST_CASE("overlap", "[string]") +{ + const char* test = "The Quick Brown Fox Jumps Over The Lazy Dog."; + + const bx::StringView quick = bx::strFind(test, "Quick"); + + REQUIRE(bx::overlap(quick, test) ); + + const bx::StringView empty; + REQUIRE(!bx::overlap(quick, empty) ); + REQUIRE(!bx::overlap(test, empty) ); +} + TEST_CASE("strRFind", "[string]") { const char* test = "test";