From b43069a8e95b95bcd34e058376a7be0d15848319 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 6 Mar 2026 12:20:02 +0900 Subject: [PATCH 01/13] Improve stack trace and implement crash handling --- Fleece/Support/Backtrace+signals-posix.cc | 0 Fleece/Support/Backtrace+signals-win32.cc | 192 +++++++ Fleece/Support/Backtrace.cc | 613 ++++++++++++---------- Fleece/Support/Backtrace.hh | 47 +- cmake/platform_apple.cmake | 2 + cmake/platform_linux.cmake | 19 +- cmake/platform_win.cmake | 13 +- 7 files changed, 598 insertions(+), 288 deletions(-) create mode 100644 Fleece/Support/Backtrace+signals-posix.cc create mode 100644 Fleece/Support/Backtrace+signals-win32.cc diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc new file mode 100644 index 00000000..e69de29b diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc new file mode 100644 index 00000000..167de993 --- /dev/null +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -0,0 +1,192 @@ +#include "Backtrace.hh" +#include +#include +#include +#include +#include +#include +#include + +namespace fleece { + using namespace std; + + class BacktraceSignalHandlerWin32 : public BacktraceSignalHandler { + public: + BacktraceSignalHandlerWin32() + : _reporter_thread([]() { + { + unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::running; }); + } + if ( crashed() == crash_status::crashed ) { handle_stacktrace(); } + { + unique_lock lk(mtx()); + crashed() = crash_status::ending; + } + cv().notify_one(); + }) { + SetUnhandledExceptionFilter(crash_handler); + signal(SIGABRT, signal_handler); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalid_parameter_handler); + } + + BacktraceSignalHandlerWin32(const BacktraceSignalHandlerWin32&) = delete; + BacktraceSignalHandlerWin32& operator=(const BacktraceSignalHandlerWin32&) = delete; + BacktraceSignalHandlerWin32(BacktraceSignalHandlerWin32&&) = delete; + BacktraceSignalHandlerWin32& operator=(BacktraceSignalHandlerWin32&&) = delete; + + ~BacktraceSignalHandlerWin32() { + { + std::unique_lock lk(mtx()); + crashed() = crash_status::normal_exit; + } + + cv().notify_one(); + + _reporter_thread.join(); + } + private: + enum class crash_status { running, crashed, normal_exit, ending }; + + static crash_status& crashed() { + static crash_status data; + return data; + } + + static mutex& mtx() { + static mutex data; + return data; + } + + static std::string& violation_type() { + static std::string type; + return type; + } + + static condition_variable& cv() { + static condition_variable data; + return data; + } + + static std::shared_ptr& captured_backtrace() { + static std::shared_ptr bt; + return bt; + } + + thread _reporter_thread; + + static const constexpr int signal_skip_recs = +#ifdef __clang__ + // With clang, RtlCaptureContext also captures the stack frame of the + // current function Below that, there are 3 internal Windows functions + 4 +#else + // With MSVC cl, RtlCaptureContext misses the stack frame of the current + // function The first entries during StackWalk are the 3 internal Windows + // functions + 3 +#endif + ; + + static inline void terminator() { + violation_type() = "Pure Virtual Function Call"; + crash_handler_immediate(signal_skip_recs); + abort(); + } + + static inline void signal_handler(int sig) { + violation_type() = (sig == SIGABRT) ? "Abort Signal (SIGABRT)" : "Signal"; + crash_handler_immediate(signal_skip_recs); + abort(); + } + + static inline void __cdecl invalid_parameter_handler(const wchar_t*, const wchar_t*, const wchar_t*, + unsigned int, uintptr_t) { + violation_type() = "Invalid CRT Parameter"; + crash_handler_immediate(signal_skip_recs); + abort(); + } + + NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) { + // The exception info supplies a trace from exactly where the issue was, + // no need to skip records + ostringstream oss; + oss << "Exception 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(8) << info->ExceptionRecord->ExceptionCode; + violation_type() = oss.str(); + crash_handler_immediate(0, info->ContextRecord); + return EXCEPTION_CONTINUE_SEARCH; + } + + NOINLINE static void crash_handler_immediate(int skip, CONTEXT* ct = nullptr) { + // Capture the backtrace directly in the crash handler. + // If we have an exception context (ct != nullptr), use it to capture the stack trace + // from exactly the point of the crash. + if (ct != nullptr) { + // We have an exception context - use it directly + captured_backtrace() = Backtrace::capture(0, 32, ct); + } else { + // We're in a signal handler without exception context. + // Try to capture current context, though this may not work well. + captured_backtrace() = Backtrace::capture(skip, 32 + skip); + } + + // Log immediately without waiting for reporter thread + // This avoids deadlocks, especially with a debugger attached + if ( sLogger ) { + stringstream out; + out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + captured_backtrace()->writeTo(out); + out << "\n******************** Now terminating ********************\n"; + sLogger(out.str()); + } else { + cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + captured_backtrace()->writeTo(cerr); + cerr << "\n******************** Now terminating ********************\n"; + } + } + + static void crash_handler_thread(int skip, CONTEXT* ct = nullptr) { + // Capture the backtrace for threaded handling + if (ct != nullptr) { + captured_backtrace() = Backtrace::capture(0, 32, ct); + } else { + captured_backtrace() = Backtrace::capture(skip, 32 + skip); + } + + { + unique_lock lk(mtx()); + crashed() = crash_status::crashed; + } + + cv().notify_one(); + + { + unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::crashed; }); + } + } + + static void handle_stacktrace() { + // Use the backtrace that was captured in the crash handler + auto bt = captured_backtrace(); + if ( sLogger ) { + stringstream out; + out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(out); + out << "\n******************** Now terminating ********************\n"; + sLogger(out.str()); + } else { + cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(cerr); + cerr << "\n******************** Now terminating ********************\n"; + } + } + }; +} // namespace fleece + +fleece::BacktraceSignalHandler& getSignalHandler() { + static fleece::BacktraceSignalHandlerWin32 handler; + return handler; +} \ No newline at end of file diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 2c95f867..43576fa4 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -20,181 +20,223 @@ #include #include "betterassert.hh" +fleece::BacktraceSignalHandler& getSignalHandler(); +std::function fleece::BacktraceSignalHandler::sLogger; #ifndef _MSC_VER -#pragma mark - UNIX IMPLEMENTATION: +# include // dladdr() +# include // abi::__cxa_demangle() + +# if defined(__ANDROID__) || (defined(__linux__) && defined(HAVE_LIBBACKTRACE)) +# include // part of compiler runtime, no extra link needed +# ifdef HAVE_LIBBACKTRACE +# include +# endif + +struct BacktraceState { + void** current; + void** end; +}; + +static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { + BacktraceState* state = static_cast(arg); + uintptr_t pc = _Unwind_GetIP(context); + if ( pc ) { + if ( state->current == state->end ) return _URC_END_OF_STACK; + *state->current++ = reinterpret_cast(pc); + } + return _URC_NO_REASON; +} -#include // dladdr() +static int backtrace(void** buffer, int max, void* context = nullptr) { + BacktraceState state = {buffer, buffer + max}; + _Unwind_Backtrace(unwindCallback, &state); + return int(state.current - buffer); +} -#ifndef __ANDROID__ - #define HAVE_EXECINFO - #include // backtrace(), backtrace_symbols() -#else - #include // free - #include // _Unwind_Backtrace(), etc. -#endif +# ifdef HAVE_LIBBACKTRACE +static backtrace_state* getBacktraceState() { + static std::once_flag once; + static backtrace_state* state; + std::call_once(once, [] { state = backtrace_create_state(nullptr, /*threaded=*/1, nullptr, nullptr); }); + return state; +} -#ifndef _MSC_VER - #include // abi::__cxa_demangle() - #define HAVE_UNMANGLE -#endif +struct ResolvedInfo { + const char* function = nullptr; + const char* file = nullptr; + int line = 0; + uintptr_t symval = 0; // function start address, for offset calculation +}; + +static ResolvedInfo backtraceResolve(uintptr_t pc) { + ResolvedInfo info; + backtrace_pcinfo( + getBacktraceState(), pc, + [](void* data, uintptr_t, const char* file, int line, const char* fn) -> int { + auto* r = static_cast(data); + if ( fn && !r->function ) r->function = fn; + if ( file && !r->file ) { + r->file = file; + r->line = line; + } + return 0; + }, + nullptr, &info); + // Always call syminfo: provides symval (function start) for offset, and function name fallback + backtrace_syminfo( + getBacktraceState(), pc, + [](void* data, uintptr_t, const char* sym, uintptr_t symval, uintptr_t) { + auto* r = static_cast(data); + if ( !r->function && sym ) r->function = sym; + if ( symval ) r->symval = symval; + }, + nullptr, &info); + return info; +} +# endif // HAVE_LIBBACKTRACE + +# else +# define HAVE_EXECINFO +# include +# endif namespace fleece { using namespace std; - -#ifndef HAVE_EXECINFO - // Use libunwind to emulate backtrace(). This is limited in that it won't traverse any stack - // frames before a signal handler, so it's not useful for logging the stack upon a crash. - // Adapted from https://stackoverflow.com/a/28858941 - // See also https://stackoverflow.com/q/29559347 for more details & possible workarounds - - struct BacktraceState { - void** current; - void** end; - }; - - static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { - BacktraceState* state = static_cast(arg); - uintptr_t pc = _Unwind_GetIP(context); - if (pc) { - if (state->current == state->end) { - return _URC_END_OF_STACK; - } else { - *state->current++ = reinterpret_cast(pc); - } - } - return _URC_NO_REASON; - } - - static int backtrace(void** buffer, int max) { - BacktraceState state = {buffer, buffer + max}; - _Unwind_Backtrace(unwindCallback, &state); - - return int(state.current - buffer); - } -#endif - - - static char* unmangle(const char *function) { -#ifdef HAVE_UNMANGLE - int status; + static char* unmangle(const char* function) { + int status; size_t unmangledLen; - char *unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); - if (unmangled && status == 0) - return unmangled; + char* unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); + if ( unmangled && status == 0 ) return unmangled; free(unmangled); -#endif return (char*)function; } - Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { precondition(i < _addrs.size()); - frameInfo frame = { }; - Dl_info info; - if (dladdr(_addrs[i], &info)) { - frame.pc = _addrs[i]; - frame.offset = (size_t)frame.pc - (size_t)info.dli_saddr; - frame.function = info.dli_sname; - frame.library = info.dli_fname; - const char *slash = strrchr(frame.library, '/'); - if (slash) - frame.library = slash + 1; + frameInfo frame = {}; + frame.pc = _addrs[i]; + + // dladdr gives us the library name regardless of symbol visibility, + // and a fallback saddr/sname for exported symbols. + Dl_info dlInfo = {}; + dladdr(frame.pc, &dlInfo); + if ( dlInfo.dli_fname ) { + frame.library = dlInfo.dli_fname; + if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; } - +# if defined(HAVE_LIBBACKTRACE) + uintptr_t pc = reinterpret_cast(frame.pc); + if ( i > 0 ) pc -= 1; // return address → call site + if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); + auto resolved = backtraceResolve(pc); + frame.function = resolved.function; + frame.file = resolved.file; + frame.line = resolved.line; + // Prefer symval from libbacktrace (works for hidden-visibility symbols); + // fall back to dladdr's saddr for exported symbols. + if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; + else if ( dlInfo.dli_saddr ) + frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; +# else + frame.function = dlInfo.dli_sname; // exported symbols only + if ( dlInfo.dli_saddr ) frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; + if ( dlInfo.dli_fbase ) { + uintptr_t pc = reinterpret_cast(frame.pc); + if ( i > 0 ) pc -= 1; + frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); + } +# endif return frame; } - const char* Backtrace::getSymbol(unsigned i) const { -#if !defined(__ANDROID__) +# if defined(HAVE_LIBBACKTRACE) + // delegate to getFrame which already uses libbacktrace + static thread_local string tName; + auto frame = getFrame(i); + if ( frame.function ) { + tName = frame.function; + return tName.c_str(); + } + return nullptr; +# elif defined(HAVE_EXECINFO) precondition(i < _addrs.size()); - if (!_symbols) - _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); - if (_symbols) { + if ( !_symbols ) _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); + if ( _symbols ) { const char* s = _symbols[i]; -#if __APPLE__ - // Skip line number and whitespace: - while (*s && isdigit(*s)) - ++s; - while (*s && isspace(*s)) - ++s; -#else - // ?? On Linux is there a path here ?? - const char *slash = strrchr(s, '/'); - if (slash) - s = slash + 1; -#endif +# if __APPLE__ + while ( *s && isdigit(*s) ) ++s; + while ( *s && isspace(*s) ) ++s; +# else + const char* slash = strrchr(s, '/'); + if ( slash ) s = slash + 1; +# endif return s; } -#endif +# endif return nullptr; } - // If any of these strings occur in a backtrace, suppress further frames. static constexpr const char* kTerminalFunctions[] = { - "_C_A_T_C_H____T_E_S_T_", - "Catch::TestInvokerAsFunction::invoke() const", - "litecore::actor::Scheduler::task(unsigned)", - "litecore::actor::GCDMailbox::safelyCall", + "_C_A_T_C_H____T_E_S_T_", + "Catch::TestInvokerAsFunction::invoke() const", + "litecore::actor::Scheduler::task(unsigned)", + "litecore::actor::GCDMailbox::safelyCall", }; - static constexpr struct {const char *old, *nuu;} kAbbreviations[] = { - {"(anonymous namespace)", "(anon)"}, - {"std::__1::", "std::"}, - {"std::basic_string, std::allocator >", - "std::string"}, + static constexpr struct { + const char *old, *nuu; + } kAbbreviations[] = { + {"(anonymous namespace)", "(anon)"}, + {"std::__1::", "std::"}, + {"std::basic_string, std::allocator >", "std::string"}, }; - - static void replace(std::string &str, string_view oldStr, string_view newStr) { + static void replace(std::string& str, string_view oldStr, string_view newStr) { string::size_type pos = 0; - while (string::npos != (pos = str.find(oldStr, pos))) { + while ( string::npos != (pos = str.find(oldStr, pos)) ) { str.replace(pos, oldStr.size(), newStr); pos += newStr.size(); } } - - bool Backtrace::writeTo(ostream &out) const { - for (unsigned i = 0; i < unsigned(_addrs.size()); ++i) { - if (i > 0) - out << '\n'; + bool Backtrace::writeTo(ostream& out) const { + for ( unsigned i = 0; i < unsigned(_addrs.size()); ++i ) { + if ( i > 0 ) out << '\n'; out << '\t'; - char *cstr = nullptr; - auto frame = getFrame(i); - int len; - bool stop = false; - if (frame.function) { - string name = Unmangle(frame.function); - // Stop when we hit a unit test, or other known functions: - for (auto fn : kTerminalFunctions) { - if (name.find(fn) != string::npos) - stop = true; - } - // Abbreviate some C++ verbosity: - for (auto &abbrev : kAbbreviations) - replace(name, abbrev.old, abbrev.nuu); - len = asprintf(&cstr, "%2u %-25s %s + %zu", - i, frame.library, name.c_str(), frame.offset); - } else if (const char* s = getSymbol(i)) { - len = asprintf(&cstr, "%2u %s", i, s); + char* cstr = nullptr; + auto frame = getFrame(i); + int len; + bool stop = false; + const char* lib = frame.library ? frame.library : "?"; + string name = frame.function ? Unmangle(frame.function) : string(); + if ( !name.empty() ) { + for ( auto fn : kTerminalFunctions ) + if ( name.find(fn) != string::npos ) stop = true; + for ( auto& abbrev : kAbbreviations ) replace(name, abbrev.old, abbrev.nuu); + } + if ( frame.file ) { + const char* fileBase = strrchr(frame.file, '/'); + fileBase = fileBase ? fileBase + 1 : frame.file; + len = asprintf(&cstr, "%2u %-25s %s + %zu (%s:%d)", i, lib, name.empty() ? "?" : name.c_str(), + frame.offset, fileBase, frame.line); } else { - len = asprintf(&cstr, "%2u %p", i, _addrs[i]); + len = asprintf(&cstr, "%2u %-25s %s + %zu [0x%zx]", i, lib, name.empty() ? "?" : name.c_str(), + frame.offset, frame.imageOffset); } - if (len < 0) - return false; + if ( len < 0 ) return false; - if(len > 0) { + if ( len > 0 ) { out.write(cstr, size_t(len)); free(cstr); } - if (stop) { + if ( stop ) { out << "\n\t ... (" << (_addrs.size() - i - 1) << " more suppressed) ..."; break; } @@ -202,76 +244,157 @@ namespace fleece { return true; } - - std::string FunctionName(const void *pc) { + std::string FunctionName(const void* pc) { +# if defined(__linux__) && !defined(__ANDROID__) && defined(HAVE_LIBBACKTRACE) + uintptr_t addr = reinterpret_cast(pc) - 1; + if ( const char* name = backtraceResolve(addr).function ) return Unmangle(name); + return {}; +# else Dl_info info = {}; dladdr(pc, &info); - if (info.dli_sname) - return Unmangle(info.dli_sname); - else - return ""; + if ( info.dli_sname ) return Unmangle(info.dli_sname); + return {}; +# endif } -} +} // namespace fleece -#else // _MSC_VER -#pragma mark - WINDOWS IMPLEMENTATION: +#else // _MSC_VER +# pragma mark - WINDOWS IMPLEMENTATION: -#pragma comment(lib, "Dbghelp.lib") -#include -#include -#include "asprintf.h" -#include -#include +# pragma comment(lib, "Dbghelp.lib") +# include +# include +# include "asprintf.h" +# include +# include using namespace std; namespace fleece { -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + static void initialize_symbols() { + static bool initialized = false; + if (!initialized) { + auto process = GetCurrentProcess(); + SymInitialize(process, nullptr, TRUE); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + initialized = true; + } + } + + static int backtrace(void** buffer, int max, void* context) { + CONTEXT localCtx; + CONTEXT* myCtx; + if (context) { + memcpy(&localCtx, context, sizeof(CONTEXT)); + myCtx = &localCtx; + } else { + RtlCaptureContext(&localCtx); + myCtx = &localCtx; + } + + if ( max == 0 ) return 0; + + // Initialize symbol handler for StackWalk64 + initialize_symbols(); + + // StackWalk64 needs a valid thread handle. If we have a captured context, + // we use GetCurrentThread() since we're walking while in a valid thread context. + HANDLE thread = GetCurrentThread(); + auto process = GetCurrentProcess(); + STACKFRAME64 s; + memset(&s, 0, sizeof(STACKFRAME64)); + + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrPC.Mode = AddrModeFlat; +# if defined(_M_X64) + s.AddrPC.Offset = myCtx->Rip; + s.AddrStack.Offset = myCtx->Rsp; + s.AddrFrame.Offset = myCtx->Rbp; + auto machine_type = IMAGE_FILE_MACHINE_AMD64; +# elif defined(_M_ARM64) + s.AddrPC.Offset = myCtx->Pc; + s.AddrStack.Offset = myCtx->Sp; + s.AddrFrame.Offset = myCtx->R11; + auto machine_type = IMAGE_FILE_MACHINE_ARM64; +# else +# error Unsupported architecture +# endif + + auto size = 0; + for (int i = 0; i < max; i++) { + SetLastError(0); + if (!StackWalk64(machine_type, process, thread, &s, myCtx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + break; + } + + if ( s.AddrReturn.Offset == 0 ) { + break; + } + + buffer[i] = (void*)s.AddrReturn.Offset; + size++; + } - static inline int backtrace(void** buffer, int max) { - return (int)CaptureStackBackTrace(0, (DWORD)max, buffer, nullptr); + return size; } + bool Backtrace::writeTo(ostream& out) const { + const auto process = GetCurrentProcess(); + SYMBOL_INFO* symbol = nullptr; + IMAGEHLP_LINE64* line = nullptr; + bool success = false; - bool Backtrace::writeTo(ostream &out) const { - const auto process = GetCurrentProcess(); - SYMBOL_INFO *symbol = nullptr; - IMAGEHLP_LINE64 *line = nullptr; - bool success = false; - SymInitialize(process, nullptr, TRUE); - DWORD symOptions = SymGetOptions(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - SymSetOptions(symOptions); - - symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO)+1023 * sizeof(TCHAR)); - if (!symbol) - goto exit; - symbol->MaxNameLen = 1024; + // Initialize symbol handler for symbol resolution + initialize_symbols(); + + symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 1023 * sizeof(TCHAR)); + if ( !symbol ) goto exit; + symbol->MaxNameLen = 1024; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); DWORD displacement; line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); - if (!line) - goto exit; + if ( !line ) goto exit; line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); - for (unsigned i = 0; i < _addrs.size(); i++) { - if (i > 0) - out << "\r\n"; + for ( unsigned i = 0; i < _addrs.size(); i++ ) { + if ( i > 0 ) out << "\r\n"; out << '\t'; const auto address = (DWORD64)_addrs[i]; - SymFromAddr(process, address, nullptr, symbol); - char* cstr = nullptr; - if (SymGetLineFromAddr64(process, address, &displacement, line)) { - asprintf(&cstr, "at %s in %s: line: %lu: address: 0x%0llX", - symbol->Name, line->FileName, line->LineNumber, symbol->Address); + + // Module basename + void* moduleBase = nullptr; + RtlPcToFileHeader(_addrs[i], &moduleBase); + DWORD64 rva = moduleBase ? (address - (DWORD64)moduleBase) : 0; + char modulePath[4096] = "?"; + if ( moduleBase ) { + int mlen = GetModuleFileNameA((HMODULE)moduleBase, modulePath, sizeof(modulePath)); + if ( mlen > 0 ) { + char* p = modulePath + mlen - 1; + while ( p > modulePath && p[-1] != '\\' ) --p; + memmove(modulePath, p, strlen(p) + 1); + } + } + + // Function name and offset from symbol start + DWORD64 fnOffset = 0; + BOOL symFound = SymFromAddr(process, address, &fnOffset, symbol); + + const char* fn = symFound ? symbol->Name : "?"; + char* cstr = nullptr; + if ( SymGetLineFromAddr64(process, address, &displacement, line) ) { + const char* fileBase = strrchr(line->FileName, '\\'); + fileBase = fileBase ? fileBase + 1 : line->FileName; + asprintf(&cstr, "%2u %-25s %s + %llu (%s:%lu)", i, modulePath, fn, fnOffset, fileBase, + line->LineNumber); } else { - asprintf(&cstr, "at %s, address 0x%0llX", - symbol->Name, symbol->Address); + asprintf(&cstr, "%2u %-25s %s + %llu [0x%llx]", i, modulePath, fn, fnOffset, rva); } - if (!cstr) - goto exit; + if ( !cstr ) goto exit; out << cstr; free(cstr); } @@ -284,155 +407,115 @@ namespace fleece { return success; } -#else - static inline int backtrace(void** buffer, int max) { - return (int)CaptureStackBackTrace(0, (DWORD)max, buffer, nullptr); - } - - // Symbolication is not possible within a UWP application, has to be performed - // using external tools. - bool Backtrace::writeTo(ostream& out) const { - for (unsigned i = 0; i < _addrs.size(); i++) { - void* moduleBase = nullptr; - RtlPcToFileHeader(_addrs[i], &moduleBase); - auto moduleBaseTyped = (const unsigned char *)moduleBase; - char modulePath[4096]; - if(moduleBase) { - int length = GetModuleFileNameA((HMODULE)moduleBase, modulePath, 4096) - 1; - char* pos = modulePath + length; - if(length > 0) { - while(length-- > 0 && pos[0] != '\\') { - pos--; - } - } else { - strcpy(pos, " "); - } - - if(i > 0) { - out << "\r\n"; - } - - out << '\t'; - out << pos << "+0x" << setw(sizeof(size_t) * 2) << setfill('0') << hex << (uint32_t)((unsigned char *)_addrs[i] - moduleBaseTyped) << dec; - } else { - out << "\t 0x" << setw(sizeof(size_t) * 2) << setfill('0') << hex << _addrs[i] << dec; - } - } - - return true; - } -#endif + static char* unmangle(const char* function) { return (char*)function; } +} // namespace fleece - static char* unmangle(const char *function) { - return (char*)function; - } - -} - -#endif // _MSC_VER +#endif // _MSC_VER #pragma mark - COMMON CODE: - namespace fleece { - Backtrace::~Backtrace() { - free(_symbols); - } + Backtrace::~Backtrace() { free(_symbols); } - std::string Unmangle(const char *name NONNULL) { - auto unmangled = unmangle(name); - std::string result = unmangled; - if (unmangled != name) - free(unmangled); + std::string Unmangle(const char* name NONNULL) { + auto unmangled = unmangle(name); + std::string result = unmangled; + if ( unmangled != name ) free(unmangled); return result; } + std::string Unmangle(const std::type_info& type) { return Unmangle(type.name()); } - std::string Unmangle(const std::type_info &type) { - return Unmangle(type.name()); - } - - - shared_ptr Backtrace::capture(unsigned skipFrames, unsigned maxFrames) { + shared_ptr Backtrace::capture(unsigned skipFrames, unsigned maxFrames, void* context) { // (By capturing afterwards, we avoid the (many) stack frames associated with make_shared) auto bt = make_shared(0, 0); - bt->_capture(skipFrames + 1, maxFrames); + bt->_capture(skipFrames + 1, maxFrames, context); return bt; } Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames) { - if (maxFrames > 0) - _capture(skipFrames + 1, maxFrames); + if ( maxFrames > 0 ) _capture(skipFrames + 1, maxFrames); } - - void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames) { - _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = backtrace(&_addrs[0], skipFrames + maxFrames); + void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { + _addrs.resize(++skipFrames + maxFrames); // skip this frame + auto n = backtrace(&_addrs[0], skipFrames + maxFrames, context); _addrs.resize(n); skip(skipFrames); } - void Backtrace::skip(unsigned nFrames) { _addrs.erase(_addrs.begin(), _addrs.begin() + min(size_t(nFrames), _addrs.size())); } - string Backtrace::toString() const { stringstream out; writeTo(out); return out.str(); } - - void Backtrace::writeCrashLog(ostream &out) { + void Backtrace::writeCrashLog(ostream& out) { Backtrace bt(4); - auto xp = current_exception(); - if (xp) { + auto xp = current_exception(); + if ( xp ) { out << "Uncaught exception:\n\t"; try { rethrow_exception(xp); - } catch(const exception& x) { - const char *name = typeid(x).name(); - char *unmangled = unmangle(name); - out << unmangled << ": " << x.what() << "\n"; - if (unmangled != name) - free(unmangled); - } catch (...) { - out << "unknown exception type\n"; - } + } catch ( const exception& x ) { + const char* name = typeid(x).name(); + char* unmangled = unmangle(name); + out << unmangled << ": " << x.what() << "\n"; + if ( unmangled != name ) free(unmangled); + } catch ( ... ) { out << "unknown exception type\n"; } } out << "Backtrace:"; bt.writeTo(out); } + static BacktraceSignalHandler sSignalHandler = getSignalHandler(); + + void Backtrace::handleTerminate(const function& logger) { + // ---- Code below gets called by C++ runtime on an uncaught exception --- + if ( logger ) { + stringstream out; + writeCrashLog(out); + logger(out.str()); + } else { + cerr << "\n\n******************** C++ fatal error ********************\n"; + writeCrashLog(cerr); + cerr << "\n******************** Now terminating ********************\n"; + } + } void Backtrace::installTerminateHandler(function logger) { static once_flag sOnce; call_once(sOnce, [&] { - static auto const sLogger = std::move(logger); + static auto const sLogger = std::move(logger); static terminate_handler const sOldHandler = set_terminate([] { - // ---- Code below gets called by C++ runtime on an uncaught exception --- - if (sLogger) { - stringstream out; - writeCrashLog(out); - sLogger(out.str()); - } else { - cerr << "\n\n******************** C++ fatal error ********************\n"; - writeCrashLog(cerr); - cerr << "\n******************** Now terminating ********************\n"; - } + // ---- Code below gets called by C++ runtime on a call to terminate --- + handleTerminate(sLogger); // Chain to old handler: sOldHandler(); // Just in case the old handler doesn't abort: abort(); // ---- End of handler ---- }); + + static unexpected_handler const sOldUnexpectedHandler = set_unexpected([] { + // ---- Code below gets called by C++ runtime on an unexpected exception (e.g. noexcept violation) --- + handleTerminate(sLogger); + // Chain to old handler: + sOldUnexpectedHandler(); + // Just in case the old handler doesn't abort: + abort(); + // ---- End of handler ---- + }); + + BacktraceSignalHandler::setLogger(sLogger); }); } -} +} // namespace fleece diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index d2f6f1f8..fdfaf006 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -20,17 +20,24 @@ #include namespace fleece { + class BacktraceSignalHandler { + public: + static void setLogger(std::function logger) { sLogger = std::move(logger); } + + protected: + static std::function sLogger; + }; /** Captures a backtrace of the current thread, and can convert it to human-readable form. */ class Backtrace { - public: + public: /// Captures a backtrace and returns a shared pointer to the instance. - [[nodiscard]] static std::shared_ptr capture(unsigned skipFrames =0, unsigned maxFrames =50); + [[nodiscard]] static std::shared_ptr capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); /// Captures a backtrace, unless maxFrames is zero. /// @param skipFrames Number of frames to skip at top of stack /// @param maxFrames Maximum number of frames to capture - explicit Backtrace(unsigned skipFrames =0, unsigned maxFrames =50); + explicit Backtrace(unsigned skipFrames = 0, unsigned maxFrames = 50); ~Backtrace(); @@ -46,18 +53,19 @@ namespace fleece { // Direct access to stack frames: struct frameInfo { - const void* pc; ///< Program counter - size_t offset; ///< Byte offset of pc in function - const char *function; ///< Name of (nearest) known function - const char *library; ///< Name of dynamic library containing the function + const void* pc; ///< Program counter + size_t offset; ///< Byte offset of pc in function + const char* function; ///< Name of (nearest) known function + const char* library; ///< Name of dynamic library containing the function }; /// The number of stack frames captured. - unsigned size() const {return (unsigned)_addrs.size();} + unsigned size() const { return (unsigned)_addrs.size(); } /// Returns info about a stack frame. 0 is the top. frameInfo getFrame(unsigned) const; - frameInfo operator[] (unsigned i) {return getFrame(i);} + + frameInfo operator[](unsigned i) { return getFrame(i); } /// Installs a C++ terminate_handler that will log a backtrace and info about any uncaught /// exception. By default it then calls the preexisting terminate_handler, which usually @@ -69,25 +77,24 @@ namespace fleece { /// Only the first call to this function has any effect; subsequent calls are ignored. static void installTerminateHandler(std::function logger); - private: - void _capture(unsigned skipFrames =0, unsigned maxFrames =50); - const char* getSymbol(unsigned i) const; - [[nodiscard]] char* printFrame(unsigned i) const; - static void writeCrashLog(std::ostream&); + private: + void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); + const char* getSymbol(unsigned i) const; + static void writeCrashLog(std::ostream&); + static void handleTerminate(const std::function& logger); - std::vector _addrs; // Array of PCs in backtrace, top first - mutable char** _symbols { nullptr }; + std::vector _addrs; // Array of PCs in backtrace, top first + mutable char** _symbols{nullptr}; }; - /// Attempts to return the unmangled name of the type. (If it fails, returns the mangled name.) std::string Unmangle(const std::type_info&); /// Attempts to unmangle a name. (If it fails, returns the input string unaltered.) - std::string Unmangle(const char *name NONNULL); + std::string Unmangle(const char* name NONNULL); /// Returns the name of the function at the given address, or an empty string if none. /// The name will be unmangled, if possible. - std::string FunctionName(const void *pc); + std::string FunctionName(const void* pc); -} +} // namespace fleece diff --git a/cmake/platform_apple.cmake b/cmake/platform_apple.cmake index 10848757..a94a633f 100644 --- a/cmake/platform_apple.cmake +++ b/cmake/platform_apple.cmake @@ -18,6 +18,7 @@ function(set_source_files) Fleece/Integration/ObjC/MArray+ObjC.mm Fleece/Integration/ObjC/MDict+ObjC.mm Fleece/Integration/ObjC/MValue+ObjC.mm + Fleece/Support/Backtrace+signals-posix.cc ObjC/Encoder+ObjC.mm ObjC/Fleece+CoreFoundation.mm ObjC/slice+CoreFoundation.cc @@ -36,6 +37,7 @@ function(set_base_platform_files) set( ${APPLE_SSS_RESULT} + Fleece/Support/Backtrace+signals-posix.cc ObjC/slice+CoreFoundation.cc ObjC/slice+ObjC.mm PARENT_SCOPE diff --git a/cmake/platform_linux.cmake b/cmake/platform_linux.cmake index d6da2d60..a40baef0 100644 --- a/cmake/platform_linux.cmake +++ b/cmake/platform_linux.cmake @@ -8,11 +8,26 @@ function(set_source_files) endif() set_source_files_base(RESULT BASE_SRC_FILES) - set(${LINUX_SSS_RESULT} ${BASE_SRC_FILES} PARENT_SCOPE) + set( + ${LINUX_SSS_RESULT} + ${BASE_SRC_FILES} + Fleece/Support/Backtrace+signals-posix.cc + PARENT_SCOPE + ) endfunction() function(set_base_platform_files) - # No-op + set(oneValueArgs RESULT) + cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED LINUX_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set( + ${LINUX_SSS_RESULT} + Fleece/Support/Backtrace+signals-posix.cc + PARENT_SCOPE + ) endfunction() function(set_test_source_files) diff --git a/cmake/platform_win.cmake b/cmake/platform_win.cmake index 4646c100..5223516c 100644 --- a/cmake/platform_win.cmake +++ b/cmake/platform_win.cmake @@ -13,12 +13,23 @@ function(set_source_files) ${BASE_SRC_FILES} MSVC/vasprintf-msvc.c MSVC/asprintf.c + Fleece/Support/Backtrace+signals-win32.cc PARENT_SCOPE ) endfunction() function(set_base_platform_files) - # No-op + set(oneValueArgs RESULT) + cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED WIN_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set( + ${WIN_SSS_RESULT} + Fleece/Support/Backtrace+signals-win32.cc + PARENT_SCOPE + ) endfunction() function(set_test_source_files) From f4a94153bd16a593bad0a3f618e8c93b443283f4 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Tue, 10 Mar 2026 10:08:54 +0900 Subject: [PATCH 02/13] Improvements on backtrace and POSIX signal handling --- Fleece/Support/Backtrace+signals-posix.cc | 121 ++++++++++++++++++++++ Fleece/Support/Backtrace+signals-win32.cc | 119 +++------------------ Fleece/Support/Backtrace.cc | 47 ++++++--- Fleece/Support/Backtrace.hh | 35 ++++--- cmake/platform_linux.cmake | 42 +++++++- 5 files changed, 230 insertions(+), 134 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc index e69de29b..9f50b605 100644 --- a/Fleece/Support/Backtrace+signals-posix.cc +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -0,0 +1,121 @@ +#include "Backtrace.hh" +#include +#include +#include +#include +#include +#include + +namespace fleece { + using namespace std; + + class BacktraceSignalHandlerPosix : public BacktraceSignalHandler { + public: + static vector signals() { + return { + SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGIOT, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ, +#if defined(__APPLE__) + SIGEMT, +#endif + }; + } + + BacktraceSignalHandlerPosix() { + constexpr size_t stack_size = 1024 * 1024 * 8; + _stack_content = malloc(stack_size); + if ( _stack_content ) { + stack_t ss; + ss.ss_sp = _stack_content; + ss.ss_size = stack_size; + ss.ss_flags = 0; + sigaltstack(&ss, nullptr); + } + + auto signal_vector = signals(); + for ( size_t i = 0; i < signal_vector.size(); ++i ) { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); + sigfillset(&action.sa_mask); + sigdelset(&action.sa_mask, signal_vector[i]); +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif + action.sa_sigaction = &sig_handler; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + sigaction(signal_vector[i], &action, nullptr); + } + } + + BacktraceSignalHandlerPosix(const BacktraceSignalHandlerPosix&) = delete; + BacktraceSignalHandlerPosix& operator=(const BacktraceSignalHandlerPosix&) = delete; + BacktraceSignalHandlerPosix(BacktraceSignalHandlerPosix&&) = delete; + BacktraceSignalHandlerPosix& operator=(BacktraceSignalHandlerPosix&&) = delete; + + private: + void* _stack_content; + + static string& violation_type() { + static string type; + return type; + } + + static void* extract_pc(void* context) { + auto* uctx = static_cast(context); +#ifdef REG_RIP // x86_64 Linux + return reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); +#elif defined(__aarch64__) +# if defined(__APPLE__) + return reinterpret_cast(uctx->uc_mcontext->__ss.__pc); +# else + return reinterpret_cast(uctx->uc_mcontext.pc); +# endif +#elif defined(__APPLE__) && defined(__x86_64__) + return reinterpret_cast(uctx->uc_mcontext->__ss.__rip); +#else +# error "Unsupported architecture" +#endif + } + + NOINLINE static void crash_handler_immediate(void* error_addr) { + shared_ptr bt; + if ( error_addr ) { + bt = Backtrace::capture(error_addr); + } else { + bt = Backtrace::capture(); + } + + if ( sLogger ) { + stringstream out; + out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(out); + out << "\n******************** Now terminating ********************\n"; + sLogger(out.str()); + } else { + cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(cerr); + cerr << "\n******************** Now terminating ********************\n"; + } + } + + [[noreturn]] static void sig_handler(int signo, siginfo_t* info, void* context) { + const char* name = strsignal(signo); + violation_type() = name ? name : "Signal " + to_string(signo); + + crash_handler_immediate(extract_pc(context)); + + raise(info->si_signo); + + _exit(EXIT_FAILURE); + } + }; +} // namespace fleece + +fleece::BacktraceSignalHandler& getSignalHandler() { + static fleece::BacktraceSignalHandlerPosix handler; + return handler; +} diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc index 167de993..8c476f49 100644 --- a/Fleece/Support/Backtrace+signals-win32.cc +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -1,6 +1,4 @@ #include "Backtrace.hh" -#include -#include #include #include #include @@ -11,20 +9,8 @@ namespace fleece { using namespace std; class BacktraceSignalHandlerWin32 : public BacktraceSignalHandler { - public: - BacktraceSignalHandlerWin32() - : _reporter_thread([]() { - { - unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::running; }); - } - if ( crashed() == crash_status::crashed ) { handle_stacktrace(); } - { - unique_lock lk(mtx()); - crashed() = crash_status::ending; - } - cv().notify_one(); - }) { + public: + BacktraceSignalHandlerWin32() { SetUnhandledExceptionFilter(crash_handler); signal(SIGABRT, signal_handler); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); @@ -37,46 +23,12 @@ namespace fleece { BacktraceSignalHandlerWin32(BacktraceSignalHandlerWin32&&) = delete; BacktraceSignalHandlerWin32& operator=(BacktraceSignalHandlerWin32&&) = delete; - ~BacktraceSignalHandlerWin32() { - { - std::unique_lock lk(mtx()); - crashed() = crash_status::normal_exit; - } - - cv().notify_one(); - - _reporter_thread.join(); - } - private: - enum class crash_status { running, crashed, normal_exit, ending }; - - static crash_status& crashed() { - static crash_status data; - return data; - } - - static mutex& mtx() { - static mutex data; - return data; - } - - static std::string& violation_type() { - static std::string type; + private: + static string& violation_type() { + static string type; return type; } - static condition_variable& cv() { - static condition_variable data; - return data; - } - - static std::shared_ptr& captured_backtrace() { - static std::shared_ptr bt; - return bt; - } - - thread _reporter_thread; - static const constexpr int signal_skip_recs = #ifdef __clang__ // With clang, RtlCaptureContext also captures the stack frame of the @@ -103,74 +55,29 @@ namespace fleece { } static inline void __cdecl invalid_parameter_handler(const wchar_t*, const wchar_t*, const wchar_t*, - unsigned int, uintptr_t) { + unsigned int, uintptr_t) { violation_type() = "Invalid CRT Parameter"; crash_handler_immediate(signal_skip_recs); abort(); } NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) { - // The exception info supplies a trace from exactly where the issue was, - // no need to skip records ostringstream oss; - oss << "Exception 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(8) << info->ExceptionRecord->ExceptionCode; + oss << "Exception 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(8) + << info->ExceptionRecord->ExceptionCode; violation_type() = oss.str(); crash_handler_immediate(0, info->ContextRecord); return EXCEPTION_CONTINUE_SEARCH; } NOINLINE static void crash_handler_immediate(int skip, CONTEXT* ct = nullptr) { - // Capture the backtrace directly in the crash handler. - // If we have an exception context (ct != nullptr), use it to capture the stack trace - // from exactly the point of the crash. - if (ct != nullptr) { - // We have an exception context - use it directly - captured_backtrace() = Backtrace::capture(0, 32, ct); + shared_ptr bt; + if ( ct != nullptr ) { + bt = Backtrace::capture(0, 32, ct); } else { - // We're in a signal handler without exception context. - // Try to capture current context, though this may not work well. - captured_backtrace() = Backtrace::capture(skip, 32 + skip); + bt = Backtrace::capture(skip, 32 + skip); } - // Log immediately without waiting for reporter thread - // This avoids deadlocks, especially with a debugger attached - if ( sLogger ) { - stringstream out; - out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - captured_backtrace()->writeTo(out); - out << "\n******************** Now terminating ********************\n"; - sLogger(out.str()); - } else { - cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - captured_backtrace()->writeTo(cerr); - cerr << "\n******************** Now terminating ********************\n"; - } - } - - static void crash_handler_thread(int skip, CONTEXT* ct = nullptr) { - // Capture the backtrace for threaded handling - if (ct != nullptr) { - captured_backtrace() = Backtrace::capture(0, 32, ct); - } else { - captured_backtrace() = Backtrace::capture(skip, 32 + skip); - } - - { - unique_lock lk(mtx()); - crashed() = crash_status::crashed; - } - - cv().notify_one(); - - { - unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::crashed; }); - } - } - - static void handle_stacktrace() { - // Use the backtrace that was captured in the crash handler - auto bt = captured_backtrace(); if ( sLogger ) { stringstream out; out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; @@ -189,4 +96,4 @@ namespace fleece { fleece::BacktraceSignalHandler& getSignalHandler() { static fleece::BacktraceSignalHandlerWin32 handler; return handler; -} \ No newline at end of file +} diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 43576fa4..92230c32 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -20,7 +20,7 @@ #include #include "betterassert.hh" -fleece::BacktraceSignalHandler& getSignalHandler(); +fleece::BacktraceSignalHandler& getSignalHandler(); std::function fleece::BacktraceSignalHandler::sLogger; #ifndef _MSC_VER @@ -275,7 +275,7 @@ namespace fleece { static void initialize_symbols() { static bool initialized = false; - if (!initialized) { + if ( !initialized ) { auto process = GetCurrentProcess(); SymInitialize(process, nullptr, TRUE); DWORD symOptions = SymGetOptions(); @@ -286,9 +286,9 @@ namespace fleece { } static int backtrace(void** buffer, int max, void* context) { - CONTEXT localCtx; + CONTEXT localCtx; CONTEXT* myCtx; - if (context) { + if ( context ) { memcpy(&localCtx, context, sizeof(CONTEXT)); myCtx = &localCtx; } else { @@ -303,8 +303,8 @@ namespace fleece { // StackWalk64 needs a valid thread handle. If we have a captured context, // we use GetCurrentThread() since we're walking while in a valid thread context. - HANDLE thread = GetCurrentThread(); - auto process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + auto process = GetCurrentProcess(); STACKFRAME64 s; memset(&s, 0, sizeof(STACKFRAME64)); @@ -312,7 +312,7 @@ namespace fleece { s.AddrFrame.Mode = AddrModeFlat; s.AddrPC.Mode = AddrModeFlat; # if defined(_M_X64) - s.AddrPC.Offset = myCtx->Rip; + s.AddrPC.Offset = myCtx->Rip; s.AddrStack.Offset = myCtx->Rsp; s.AddrFrame.Offset = myCtx->Rbp; auto machine_type = IMAGE_FILE_MACHINE_AMD64; @@ -326,15 +326,14 @@ namespace fleece { # endif auto size = 0; - for (int i = 0; i < max; i++) { + for ( int i = 0; i < max; i++ ) { SetLastError(0); - if (!StackWalk64(machine_type, process, thread, &s, myCtx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + if ( !StackWalk64(machine_type, process, thread, &s, myCtx, NULL, SymFunctionTableAccess64, + SymGetModuleBase64, NULL) ) { break; } - if ( s.AddrReturn.Offset == 0 ) { - break; - } + if ( s.AddrReturn.Offset == 0 ) { break; } buffer[i] = (void*)s.AddrReturn.Offset; size++; @@ -436,17 +435,35 @@ namespace fleece { return bt; } - Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames) { - if ( maxFrames > 0 ) _capture(skipFrames + 1, maxFrames); + shared_ptr Backtrace::capture(void* from, unsigned maxFrames, void* context) { + auto bt = make_shared(0, 0); + bt->_capture(from, maxFrames, context); + return bt; + } + + Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames, void* context) { + if ( maxFrames > 0 ) _capture(skipFrames + 1, maxFrames, context); } void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = backtrace(&_addrs[0], skipFrames + maxFrames, context); + auto n = backtrace(&_addrs[0], skipFrames + maxFrames); _addrs.resize(n); skip(skipFrames); } + void Backtrace::_capture(void* from, unsigned maxFrames, void* context) { + _addrs.resize(maxFrames + 8); + auto n = backtrace(&_addrs[0], maxFrames + 8); + _addrs.resize(n); + for ( int i = 0; i < n; ++i ) { + if ( _addrs[i] == from ) { + skip(i); + break; + } + } + } + void Backtrace::skip(unsigned nFrames) { _addrs.erase(_addrs.begin(), _addrs.begin() + min(size_t(nFrames), _addrs.size())); } diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index fdfaf006..8b858d5a 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -21,10 +21,10 @@ namespace fleece { class BacktraceSignalHandler { - public: + public: static void setLogger(std::function logger) { sLogger = std::move(logger); } - protected: + protected: static std::function sLogger; }; @@ -32,12 +32,19 @@ namespace fleece { class Backtrace { public: /// Captures a backtrace and returns a shared pointer to the instance. - [[nodiscard]] static std::shared_ptr capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); + [[nodiscard]] static std::shared_ptr capture(unsigned skipFrames = 0, unsigned maxFrames = 50, + void* context = nullptr); + + /// Captures a backtrace from the given starting point and returns a shared pointer to the instance. + [[nodiscard]] static std::shared_ptr capture(void* from, unsigned maxFrames = 50, + void* context = nullptr); /// Captures a backtrace, unless maxFrames is zero. /// @param skipFrames Number of frames to skip at top of stack /// @param maxFrames Maximum number of frames to capture - explicit Backtrace(unsigned skipFrames = 0, unsigned maxFrames = 50); + /// @param context If non-null, a platform-specific context to capture from + /// @param from If non-null, the address to start from instead of the current frame + explicit Backtrace(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); ~Backtrace(); @@ -53,10 +60,13 @@ namespace fleece { // Direct access to stack frames: struct frameInfo { - const void* pc; ///< Program counter - size_t offset; ///< Byte offset of pc in function - const char* function; ///< Name of (nearest) known function - const char* library; ///< Name of dynamic library containing the function + const void* pc; ///< Program counter + size_t offset; ///< Byte offset of pc in function + const char* function; ///< Name of (nearest) known function + const char* library; ///< Name of dynamic library containing the function + size_t imageOffset; ///< Byte offset of pc in the library/image + const char* file; ///< Source file name, if available + int line; ///< Source line number, if available }; /// The number of stack frames captured. @@ -78,10 +88,11 @@ namespace fleece { static void installTerminateHandler(std::function logger); private: - void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); - const char* getSymbol(unsigned i) const; - static void writeCrashLog(std::ostream&); - static void handleTerminate(const std::function& logger); + void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); + void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); + const char* getSymbol(unsigned i) const; + static void writeCrashLog(std::ostream&); + static void handleTerminate(const std::function& logger); std::vector _addrs; // Array of PCs in backtrace, top first mutable char** _symbols{nullptr}; diff --git a/cmake/platform_linux.cmake b/cmake/platform_linux.cmake index a40baef0..72bf8f19 100644 --- a/cmake/platform_linux.cmake +++ b/cmake/platform_linux.cmake @@ -1,4 +1,36 @@ include("${CMAKE_CURRENT_LIST_DIR}/platform_base.cmake") +include(CheckCXXSourceCompiles) + +# libbacktrace is bundled inside GCC's private library directory. +# Ask GCC where it keeps the library and header so that builds with +# other compilers (e.g. Clang) can find it. +find_program(_gcc NAMES gcc) +if(_gcc) + execute_process( + COMMAND ${_gcc} -print-file-name=libbacktrace.a + OUTPUT_VARIABLE _gcc_bt_lib OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + if(_gcc_bt_lib AND NOT _gcc_bt_lib STREQUAL "libbacktrace.a") + get_filename_component(_gcc_lib_dir "${_gcc_bt_lib}" DIRECTORY) + find_path(BACKTRACE_INCLUDE_DIR backtrace.h HINTS "${_gcc_lib_dir}/include") + if(BACKTRACE_INCLUDE_DIR) + set(BACKTRACE_LIBRARY "${_gcc_bt_lib}") + set(CMAKE_REQUIRED_INCLUDES "${BACKTRACE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${BACKTRACE_LIBRARY}") + check_cxx_source_compiles(" + #include + int main() { + backtrace_state* s = backtrace_create_state(nullptr, 0, nullptr, nullptr); + return s ? 0 : 1; + } + " HAVE_LIBBACKTRACE) + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) + endif() + endif() + unset(_gcc_bt_lib) + unset(_gcc_lib_dir) +endif() +unset(_gcc) function(set_source_files) set(oneValueArgs RESULT) @@ -18,7 +50,7 @@ endfunction() function(set_base_platform_files) set(oneValueArgs RESULT) - cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) + cmake_parse_arguments(LINUX_SSS "" "${oneValueArgs}" "" ${ARGN}) if(NOT DEFINED LINUX_SSS_RESULT) message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") endif() @@ -51,6 +83,14 @@ function(setup_build) ) endforeach() + if(HAVE_LIBBACKTRACE) + foreach(platform FleeceBase FleeceObjects) + target_compile_definitions(${platform} PRIVATE HAVE_LIBBACKTRACE) + target_include_directories(${platform} PRIVATE "${BACKTRACE_INCLUDE_DIR}") + target_link_libraries(${platform} PRIVATE "${BACKTRACE_LIBRARY}") + endforeach() + endif() + foreach(platform FleeceObjects FleeceBase) target_compile_options( ${platform} PRIVATE From e2e00448f2a3611f09beafc472b3f13395595a64 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Tue, 10 Mar 2026 10:59:41 +0900 Subject: [PATCH 03/13] A few Windows fixes --- Fleece/Support/Backtrace+signals-win32.cc | 2 +- Fleece/Support/Backtrace.cc | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc index 8c476f49..7a6ae824 100644 --- a/Fleece/Support/Backtrace+signals-win32.cc +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -73,7 +73,7 @@ namespace fleece { NOINLINE static void crash_handler_immediate(int skip, CONTEXT* ct = nullptr) { shared_ptr bt; if ( ct != nullptr ) { - bt = Backtrace::capture(0, 32, ct); + bt = Backtrace::capture(0u, 32, ct); } else { bt = Backtrace::capture(skip, 32 + skip); } diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 92230c32..cda12a6a 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -193,6 +193,7 @@ namespace fleece { } kAbbreviations[] = { {"(anonymous namespace)", "(anon)"}, {"std::__1::", "std::"}, + {"std::__cxx11::", "std::"}, {"std::basic_string, std::allocator >", "std::string"}, }; @@ -447,14 +448,14 @@ namespace fleece { void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = backtrace(&_addrs[0], skipFrames + maxFrames); + auto n = backtrace(&_addrs[0], skipFrames + maxFrames, context); _addrs.resize(n); skip(skipFrames); } void Backtrace::_capture(void* from, unsigned maxFrames, void* context) { _addrs.resize(maxFrames + 8); - auto n = backtrace(&_addrs[0], maxFrames + 8); + auto n = backtrace(&_addrs[0], maxFrames + 8, context); _addrs.resize(n); for ( int i = 0; i < n; ++i ) { if ( _addrs[i] == from ) { From 700e955959b293e9ffa7e878df99796649452de3 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Wed, 11 Mar 2026 11:19:27 +0900 Subject: [PATCH 04/13] macOS specific fixes 1. set_unexpected is supposed to be removed in c++17 2. Need to unwind through the signal trampoline 3. iOS will not allow signal handling in app store, probably --- Fleece/Support/Backtrace+signals-posix.cc | 20 +++++---- Fleece/Support/Backtrace.cc | 50 +++++++++++++++++------ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc index 9f50b605..08b0e5d0 100644 --- a/Fleece/Support/Backtrace+signals-posix.cc +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -2,9 +2,14 @@ #include #include #include -#include +#include +#include #include +#ifdef __APPLE__ +#include +#else #include +#endif namespace fleece { using namespace std; @@ -81,10 +86,10 @@ namespace fleece { #endif } - NOINLINE static void crash_handler_immediate(void* error_addr) { + NOINLINE static void crash_handler_immediate(void* context) { shared_ptr bt; - if ( error_addr ) { - bt = Backtrace::capture(error_addr); + if ( context ) { + bt = Backtrace::capture(extract_pc(context), 50, context); } else { bt = Backtrace::capture(); } @@ -106,11 +111,12 @@ namespace fleece { const char* name = strsignal(signo); violation_type() = name ? name : "Signal " + to_string(signo); - crash_handler_immediate(extract_pc(context)); + crash_handler_immediate(context); - raise(info->si_signo); + signal(signo, SIG_DFL); + raise(signo); - _exit(EXIT_FAILURE); + _exit(128 + signo); } }; } // namespace fleece diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index cda12a6a..0b5e5eb6 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -96,6 +96,40 @@ static ResolvedInfo backtraceResolve(uintptr_t pc) { } # endif // HAVE_LIBBACKTRACE +# elif defined(__APPLE__) +# define HAVE_EXECINFO +# include +# include + +int cbl_backtrace(void** buffer, int max, void* context) { + if (max == 0) return 0; + if (context == nullptr) { + return backtrace(buffer, max); + } + + auto* uc = static_cast(context); +#if defined (__x86_64__) + buffer[0] = reinterpret_cast(uc->uc_mcontext->__ss.__rip); + void** fp = (void**)uc->uc_mcontext->__ss.__rbp; +#elif defined(__aarch64__) || defined(__arm64__) + buffer[0] = reinterpret_cast(uc->uc_mcontext->__ss.__pc); + void** fp = reinterpret_cast(uc->uc_mcontext->__ss.__fp); +#else + #error "Unsupported architecture" +#endif + + int i = 1; + while (fp && i < max) { + void* next_fp = *fp; + if ( reinterpret_cast(next_fp) <= reinterpret_cast(fp) ) break; // invalid frame pointer + void* ret_addr = *(fp + 1); + if ( !ret_addr ) break; + buffer[i++] = ret_addr; + fp = static_cast(next_fp); + } + + return i; +} # else # define HAVE_EXECINFO # include @@ -448,14 +482,14 @@ namespace fleece { void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = backtrace(&_addrs[0], skipFrames + maxFrames, context); + auto n = cbl_backtrace(&_addrs[0], skipFrames + maxFrames, context); _addrs.resize(n); skip(skipFrames); } void Backtrace::_capture(void* from, unsigned maxFrames, void* context) { _addrs.resize(maxFrames + 8); - auto n = backtrace(&_addrs[0], maxFrames + 8, context); + auto n = cbl_backtrace(&_addrs[0], maxFrames + 8, context); _addrs.resize(n); for ( int i = 0; i < n; ++i ) { if ( _addrs[i] == from ) { @@ -493,7 +527,9 @@ namespace fleece { bt.writeTo(out); } +#if !TARGET_OS_IOS static BacktraceSignalHandler sSignalHandler = getSignalHandler(); +#endif void Backtrace::handleTerminate(const function& logger) { // ---- Code below gets called by C++ runtime on an uncaught exception --- @@ -522,16 +558,6 @@ namespace fleece { // ---- End of handler ---- }); - static unexpected_handler const sOldUnexpectedHandler = set_unexpected([] { - // ---- Code below gets called by C++ runtime on an unexpected exception (e.g. noexcept violation) --- - handleTerminate(sLogger); - // Chain to old handler: - sOldUnexpectedHandler(); - // Just in case the old handler doesn't abort: - abort(); - // ---- End of handler ---- - }); - BacktraceSignalHandler::setLogger(sLogger); }); } From 908e0bf34817fdf0917158b49a455ef2dc8f66fe Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Thu, 12 Mar 2026 11:04:31 +0900 Subject: [PATCH 05/13] Rewrite the entire output process to be signal safe No std, no snprintf, almost nothing is allowed inside a signal handler --- Fleece/Support/Backtrace+signals-posix.cc | 57 ++++++--- Fleece/Support/Backtrace.cc | 145 +++++++++++++++++++--- Fleece/Support/Backtrace.hh | 19 ++- cmake/platform_apple.cmake | 1 - 4 files changed, 181 insertions(+), 41 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc index 08b0e5d0..4351882e 100644 --- a/Fleece/Support/Backtrace+signals-posix.cc +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -13,6 +13,7 @@ namespace fleece { using namespace std; + using namespace signal_safe; class BacktraceSignalHandlerPosix : public BacktraceSignalHandler { public: @@ -86,24 +87,29 @@ namespace fleece { #endif } - NOINLINE static void crash_handler_immediate(void* context) { - shared_ptr bt; - if ( context ) { - bt = Backtrace::capture(extract_pc(context), 50, context); + NOINLINE static void crash_handler_immediate(siginfo_t* info, void* context) { + void* buffer[50]; + int n = Backtrace::raw_capture(buffer, 50, context); + const char* name = strsignal(info->si_signo); + write_to_and_stderr(sLogFD, "\n\n******************** Process Crash: ", 38); + if (name) { + write_to_and_stderr(sLogFD, name); } else { - bt = Backtrace::capture(); + write_to_and_stderr(sLogFD, "Signal: ", 8); + write_long(info->si_signo, sLogFD); } - if ( sLogger ) { - stringstream out; - out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - bt->writeTo(out); - out << "\n******************** Now terminating ********************\n"; - sLogger(out.str()); - } else { - cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - bt->writeTo(cerr); - cerr << "\n******************** Now terminating ********************\n"; + write_to_and_stderr(sLogFD, " Timestamp: ", 12); + char timestamp[20]; + snprintf(timestamp, 20, "%lld", static_cast(time(nullptr))); + write_long(time(nullptr), sLogFD); + + write_to_and_stderr(sLogFD, " *******************\n", 21); + Backtrace::writeTo(buffer, n, sLogFD); + write_to_and_stderr(sLogFD, "\n******************** Now terminating ********************\n", 59); + if (sLogFD != -1) { + fsync(sLogFD); + close(sLogFD); } } @@ -111,7 +117,7 @@ namespace fleece { const char* name = strsignal(signo); violation_type() = name ? name : "Signal " + to_string(signo); - crash_handler_immediate(context); + crash_handler_immediate(info, context); signal(signo, SIG_DFL); raise(signo); @@ -119,9 +125,20 @@ namespace fleece { _exit(128 + signo); } }; + + // Awkwardly defined here to avoid FleeceBase getting its own copy since it also compiles Backtrace.cc + volatile sig_atomic_t BacktraceSignalHandler::sLogFD = -1; + + void BacktraceSignalHandler::setLogPath(const char* path) { + if (sLogFD != -1) { + close(sLogFD); + } + + sLogFD = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + } } // namespace fleece -fleece::BacktraceSignalHandler& getSignalHandler() { - static fleece::BacktraceSignalHandlerPosix handler; - return handler; -} +#if !TARGET_OS_IOS +[[maybe_unused]] +static fleece::BacktraceSignalHandlerPosix handler; +#endif \ No newline at end of file diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 0b5e5eb6..f12d8a13 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -20,9 +20,6 @@ #include #include "betterassert.hh" -fleece::BacktraceSignalHandler& getSignalHandler(); -std::function fleece::BacktraceSignalHandler::sLogger; - #ifndef _MSC_VER # include // dladdr() # include // abi::__cxa_demangle() @@ -101,7 +98,7 @@ static ResolvedInfo backtraceResolve(uintptr_t pc) { # include # include -int cbl_backtrace(void** buffer, int max, void* context) { +int fleece::Backtrace::raw_capture(void** buffer, int max, void* context) { if (max == 0) return 0; if (context == nullptr) { return backtrace(buffer, max); @@ -138,6 +135,7 @@ int cbl_backtrace(void** buffer, int max, void* context) { namespace fleece { using namespace std; + using namespace signal_safe; static char* unmangle(const char* function) { int status; @@ -148,10 +146,18 @@ namespace fleece { return (char*)function; } - Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { - precondition(i < _addrs.size()); + static const char* unmangle_safe(const char* function) { + static char buffer[1024]; + int status; + size_t unmangledLen; + char* unmangled = abi::__cxa_demangle(function, buffer, &unmangledLen, &status); + if ( unmangled && status == 0 ) return unmangled; + return function; + } + + Backtrace::frameInfo Backtrace::getFrame(const void* pc, bool stack_top) { frameInfo frame = {}; - frame.pc = _addrs[i]; + frame.pc = pc; // dladdr gives us the library name regardless of symbol visibility, // and a fallback saddr/sname for exported symbols. @@ -163,7 +169,7 @@ namespace fleece { } # if defined(HAVE_LIBBACKTRACE) uintptr_t pc = reinterpret_cast(frame.pc); - if ( i > 0 ) pc -= 1; // return address → call site + if ( !stack_top ) pc -= 1; // return address → call site if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); auto resolved = backtraceResolve(pc); frame.function = resolved.function; @@ -179,13 +185,18 @@ namespace fleece { if ( dlInfo.dli_saddr ) frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; if ( dlInfo.dli_fbase ) { uintptr_t pc = reinterpret_cast(frame.pc); - if ( i > 0 ) pc -= 1; + if ( !stack_top ) pc -= 1; frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); } # endif return frame; } + Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { + precondition(i < _addrs.size()); + return getFrame(_addrs[i], i == 0); + } + const char* Backtrace::getSymbol(unsigned i) const { # if defined(HAVE_LIBBACKTRACE) // delegate to getFrame which already uses libbacktrace @@ -254,12 +265,15 @@ namespace fleece { if ( name.find(fn) != string::npos ) stop = true; for ( auto& abbrev : kAbbreviations ) replace(name, abbrev.old, abbrev.nuu); } +#ifndef __APPLE__ if ( frame.file ) { const char* fileBase = strrchr(frame.file, '/'); fileBase = fileBase ? fileBase + 1 : frame.file; len = asprintf(&cstr, "%2u %-25s %s + %zu (%s:%d)", i, lib, name.empty() ? "?" : name.c_str(), frame.offset, fileBase, frame.line); - } else { + } else +#endif + { len = asprintf(&cstr, "%2u %-25s %s + %zu [0x%zx]", i, lib, name.empty() ? "?" : name.c_str(), frame.offset, frame.imageOffset); } @@ -279,6 +293,43 @@ namespace fleece { return true; } + void Backtrace::writeTo(void** addresses, int size, int fd) { + for ( unsigned i = 0; i < unsigned(size); ++i ) { + if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); + write_to_and_stderr(fd, "\t", 1); + auto frame = getFrame(addresses[i], i == 0); + const char* lib = frame.library ? frame.library : "?"; + const char* name = frame.function ? unmangle(frame.function) : nullptr; +#ifndef __APPLE__ + if ( frame.file ) { + const char* fileBase = strrchr(frame.file, '/'); + fileBase = fileBase ? fileBase + 1 : frame.file; + len = snprintf(cstr, 1024, "%2u %-25s %s + %zu (%s:%d)", i, lib, !name ? "?" : name, + frame.offset, fileBase, frame.line); + } else +#endif + { + if (i < 10) write_to_and_stderr(fd, " ", 1); + write_ulong(i, fd); + const size_t lib_len = strlen(lib); + write_to_and_stderr(fd, " ", 1); + write_to_and_stderr(fd, lib, lib_len); + for (size_t j = lib_len; j < 26; ++j) write_to_and_stderr(fd, " ", 1); + if (name) { + write_to_and_stderr(fd, name, strlen(name)); + } else { + write_to_and_stderr(fd, "?", 1); + } + + write_to_and_stderr(fd, " + ", 3); + write_ulong(frame.offset, fd); + write_to_and_stderr(fd, " [", 2); + write_hex_offset(frame.imageOffset, fd); + write_to_and_stderr(fd, "]", 1); + } + } + } + std::string FunctionName(const void* pc) { # if defined(__linux__) && !defined(__ANDROID__) && defined(HAVE_LIBBACKTRACE) uintptr_t addr = reinterpret_cast(pc) - 1; @@ -482,14 +533,14 @@ namespace fleece { void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = cbl_backtrace(&_addrs[0], skipFrames + maxFrames, context); + auto n = raw_capture(&_addrs[0], skipFrames + maxFrames, context); _addrs.resize(n); skip(skipFrames); } void Backtrace::_capture(void* from, unsigned maxFrames, void* context) { _addrs.resize(maxFrames + 8); - auto n = cbl_backtrace(&_addrs[0], maxFrames + 8, context); + auto n = raw_capture(&_addrs[0], maxFrames + 8, context); _addrs.resize(n); for ( int i = 0; i < n; ++i ) { if ( _addrs[i] == from ) { @@ -527,10 +578,6 @@ namespace fleece { bt.writeTo(out); } -#if !TARGET_OS_IOS - static BacktraceSignalHandler sSignalHandler = getSignalHandler(); -#endif - void Backtrace::handleTerminate(const function& logger) { // ---- Code below gets called by C++ runtime on an uncaught exception --- if ( logger ) { @@ -557,9 +604,71 @@ namespace fleece { abort(); // ---- End of handler ---- }); - - BacktraceSignalHandler::setLogger(sLogger); }); } } // namespace fleece + +namespace signal_safe { + void write_long(long long value, int fd) { + // NOTE: This function assumes buffer is at least 21 bytes long + if (value == 0) { + write_to_and_stderr(fd, "0", 1); + } else { + if (value < 0) write_to_and_stderr(fd, "-", 1); + char temp[20]; + char* p = temp; + while (value > 0) { + *p++ = static_cast(value % 10 + '0'); + value /= 10; + } + + while (p != temp) { + write_to_and_stderr(fd, --p, 1); + } + } + } + + void write_ulong(unsigned long long value, int fd) { + // NOTE: This function assumes buffer is at least 21 bytes long + if (value == 0) { + write_to_and_stderr(fd, "0", 1); + } else { + char temp[20]; + char* p = temp; + while (value > 0) { + *p++ = static_cast(value % 10 + '0'); + value /= 10; + } + + while (p != temp) { + write_to_and_stderr(fd, --p, 1); + } + } + } + + void write_hex_offset(size_t value, int fd) { + static const char* hexDigits = "0123456789abcdef"; + const int num_digits = sizeof(size_t) * 2; + write_to_and_stderr(fd, "0x", 2); + if (value == 0) { + write_to_and_stderr(fd, "0", 1); + } else { + char temp[num_digits]; + char* p = temp; + while (value > 0) { + *p++ = hexDigits[value & 0xF]; + value >>= 4; + } + + while (p != temp) { + write_to_and_stderr(fd, --p, 1); + } + } + } + + void write_to_and_stderr(int fd, const char* str, size_t n) { + if (fd != -1) write(fd, str, n ? n : strlen(str)); + write(STDERR_FILENO, str, n ? n : strlen(str)); + } +} \ No newline at end of file diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index 8b858d5a..e0999d46 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -18,14 +18,23 @@ #include #include #include +#include +#include + +namespace signal_safe { + void write_long(long long value, int fd); + void write_ulong(unsigned long long value, int fd); + void write_hex_offset(size_t value, int fd); + void write_to_and_stderr(int fd, const char* str, size_t len = 0); +} namespace fleece { class BacktraceSignalHandler { public: - static void setLogger(std::function logger) { sLogger = std::move(logger); } + static void setLogPath(const char* path); protected: - static std::function sLogger; + static volatile sig_atomic_t sLogFD; }; /** Captures a backtrace of the current thread, and can convert it to human-readable form. */ @@ -39,6 +48,8 @@ namespace fleece { [[nodiscard]] static std::shared_ptr capture(void* from, unsigned maxFrames = 50, void* context = nullptr); + static int raw_capture(void** buffer, int max, void* context = nullptr); + /// Captures a backtrace, unless maxFrames is zero. /// @param skipFrames Number of frames to skip at top of stack /// @param maxFrames Maximum number of frames to capture @@ -54,6 +65,8 @@ namespace fleece { /// Writes the human-readable backtrace to a stream. bool writeTo(std::ostream&) const; + static void writeTo(void** addresses, int size, int fd); + /// Returns the human-readable backtrace. std::string toString() const; @@ -75,6 +88,8 @@ namespace fleece { /// Returns info about a stack frame. 0 is the top. frameInfo getFrame(unsigned) const; + static frameInfo getFrame(const void* pc, bool stack_top); + frameInfo operator[](unsigned i) { return getFrame(i); } /// Installs a C++ terminate_handler that will log a backtrace and info about any uncaught diff --git a/cmake/platform_apple.cmake b/cmake/platform_apple.cmake index a94a633f..d65962bb 100644 --- a/cmake/platform_apple.cmake +++ b/cmake/platform_apple.cmake @@ -37,7 +37,6 @@ function(set_base_platform_files) set( ${APPLE_SSS_RESULT} - Fleece/Support/Backtrace+signals-posix.cc ObjC/slice+CoreFoundation.cc ObjC/slice+ObjC.mm PARENT_SCOPE From 404fd8631700e66b632e3e40d9fff2b9733cfa05 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Thu, 12 Mar 2026 12:08:58 +0900 Subject: [PATCH 06/13] Remove fragile code regarding ucontext --- Fleece/Support/Backtrace+signals-posix.cc | 20 ++- Fleece/Support/Backtrace.cc | 154 ++++++++-------------- Fleece/Support/Backtrace.hh | 3 +- 3 files changed, 66 insertions(+), 111 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc index 4351882e..c7b00f48 100644 --- a/Fleece/Support/Backtrace+signals-posix.cc +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -6,9 +6,9 @@ #include #include #ifdef __APPLE__ -#include +# include #else -#include +# include #endif namespace fleece { @@ -88,11 +88,11 @@ namespace fleece { } NOINLINE static void crash_handler_immediate(siginfo_t* info, void* context) { - void* buffer[50]; - int n = Backtrace::raw_capture(buffer, 50, context); + void* buffer[50]; + int n = Backtrace::raw_capture(buffer, 50, context); const char* name = strsignal(info->si_signo); write_to_and_stderr(sLogFD, "\n\n******************** Process Crash: ", 38); - if (name) { + if ( name ) { write_to_and_stderr(sLogFD, name); } else { write_to_and_stderr(sLogFD, "Signal: ", 8); @@ -105,9 +105,9 @@ namespace fleece { write_long(time(nullptr), sLogFD); write_to_and_stderr(sLogFD, " *******************\n", 21); - Backtrace::writeTo(buffer, n, sLogFD); + Backtrace::writeTo(buffer + 3, n - 3, sLogFD); write_to_and_stderr(sLogFD, "\n******************** Now terminating ********************\n", 59); - if (sLogFD != -1) { + if ( sLogFD != -1 ) { fsync(sLogFD); close(sLogFD); } @@ -130,9 +130,7 @@ namespace fleece { volatile sig_atomic_t BacktraceSignalHandler::sLogFD = -1; void BacktraceSignalHandler::setLogPath(const char* path) { - if (sLogFD != -1) { - close(sLogFD); - } + if ( sLogFD != -1 ) { close(sLogFD); } sLogFD = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); } @@ -141,4 +139,4 @@ namespace fleece { #if !TARGET_OS_IOS [[maybe_unused]] static fleece::BacktraceSignalHandlerPosix handler; -#endif \ No newline at end of file +#endif diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index f12d8a13..77b1bbca 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -11,7 +11,6 @@ // #include "Backtrace.hh" -#include #include #include #include @@ -30,6 +29,7 @@ # include # endif +// Generic _Unwind_Backtrace implementation: struct BacktraceState { void** current; void** end; @@ -45,7 +45,7 @@ static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* return _URC_NO_REASON; } -static int backtrace(void** buffer, int max, void* context = nullptr) { +int fleece::Backtrace::raw_capture(void** buffer, int max, void* context) { BacktraceState state = {buffer, buffer + max}; _Unwind_Backtrace(unwindCallback, &state); return int(state.current - buffer); @@ -96,36 +96,10 @@ static ResolvedInfo backtraceResolve(uintptr_t pc) { # elif defined(__APPLE__) # define HAVE_EXECINFO # include -# include int fleece::Backtrace::raw_capture(void** buffer, int max, void* context) { - if (max == 0) return 0; - if (context == nullptr) { - return backtrace(buffer, max); - } - - auto* uc = static_cast(context); -#if defined (__x86_64__) - buffer[0] = reinterpret_cast(uc->uc_mcontext->__ss.__rip); - void** fp = (void**)uc->uc_mcontext->__ss.__rbp; -#elif defined(__aarch64__) || defined(__arm64__) - buffer[0] = reinterpret_cast(uc->uc_mcontext->__ss.__pc); - void** fp = reinterpret_cast(uc->uc_mcontext->__ss.__fp); -#else - #error "Unsupported architecture" -#endif - - int i = 1; - while (fp && i < max) { - void* next_fp = *fp; - if ( reinterpret_cast(next_fp) <= reinterpret_cast(fp) ) break; // invalid frame pointer - void* ret_addr = *(fp + 1); - if ( !ret_addr ) break; - buffer[i++] = ret_addr; - fp = static_cast(next_fp); - } - - return i; + if ( max == 0 ) return 0; + return backtrace(buffer, max); } # else # define HAVE_EXECINFO @@ -148,16 +122,16 @@ namespace fleece { static const char* unmangle_safe(const char* function) { static char buffer[1024]; - int status; - size_t unmangledLen; - char* unmangled = abi::__cxa_demangle(function, buffer, &unmangledLen, &status); + int status; + size_t unmangledLen = 1024; + char* unmangled = abi::__cxa_demangle(function, buffer, &unmangledLen, &status); if ( unmangled && status == 0 ) return unmangled; return function; } - Backtrace::frameInfo Backtrace::getFrame(const void* pc, bool stack_top) { + Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { frameInfo frame = {}; - frame.pc = pc; + frame.pc = addr; // dladdr gives us the library name regardless of symbol visibility, // and a fallback saddr/sname for exported symbols. @@ -265,14 +239,14 @@ namespace fleece { if ( name.find(fn) != string::npos ) stop = true; for ( auto& abbrev : kAbbreviations ) replace(name, abbrev.old, abbrev.nuu); } -#ifndef __APPLE__ +# ifndef __APPLE__ if ( frame.file ) { const char* fileBase = strrchr(frame.file, '/'); fileBase = fileBase ? fileBase + 1 : frame.file; len = asprintf(&cstr, "%2u %-25s %s + %zu (%s:%d)", i, lib, name.empty() ? "?" : name.c_str(), frame.offset, fileBase, frame.line); } else -#endif +# endif { len = asprintf(&cstr, "%2u %-25s %s + %zu [0x%zx]", i, lib, name.empty() ? "?" : name.c_str(), frame.offset, frame.imageOffset); @@ -298,31 +272,36 @@ namespace fleece { if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); write_to_and_stderr(fd, "\t", 1); auto frame = getFrame(addresses[i], i == 0); - const char* lib = frame.library ? frame.library : "?"; - const char* name = frame.function ? unmangle(frame.function) : nullptr; -#ifndef __APPLE__ + const char* lib = frame.library ? frame.library : "?"; + const char* name = frame.function ? unmangle_safe(frame.function) : nullptr; + + if ( i < 10 ) write_to_and_stderr(fd, " ", 1); + write_ulong(i, fd); + const size_t lib_len = strlen(lib); + write_to_and_stderr(fd, " ", 1); + write_to_and_stderr(fd, lib, lib_len); + for ( size_t j = lib_len; j < 26; ++j ) write_to_and_stderr(fd, " ", 1); + if ( name ) { + write_to_and_stderr(fd, name, strlen(name)); + } else { + write_to_and_stderr(fd, "?", 1); + } + + write_to_and_stderr(fd, " + ", 3); + write_ulong(frame.offset, fd); + +# ifndef __APPLE__ if ( frame.file ) { const char* fileBase = strrchr(frame.file, '/'); fileBase = fileBase ? fileBase + 1 : frame.file; - len = snprintf(cstr, 1024, "%2u %-25s %s + %zu (%s:%d)", i, lib, !name ? "?" : name, - frame.offset, fileBase, frame.line); + write_to_and_stderr(fd, " (", 2); + write_to_and_stderr(fd, fileBase); + write_to_and_stderr(fd, ":", 1); + write_long(frame.line, fd); + write_to_and_stderr(fd, ")", 1); } else -#endif +# endif { - if (i < 10) write_to_and_stderr(fd, " ", 1); - write_ulong(i, fd); - const size_t lib_len = strlen(lib); - write_to_and_stderr(fd, " ", 1); - write_to_and_stderr(fd, lib, lib_len); - for (size_t j = lib_len; j < 26; ++j) write_to_and_stderr(fd, " ", 1); - if (name) { - write_to_and_stderr(fd, name, strlen(name)); - } else { - write_to_and_stderr(fd, "?", 1); - } - - write_to_and_stderr(fd, " + ", 3); - write_ulong(frame.offset, fd); write_to_and_stderr(fd, " [", 2); write_hex_offset(frame.imageOffset, fd); write_to_and_stderr(fd, "]", 1); @@ -521,35 +500,18 @@ namespace fleece { return bt; } - shared_ptr Backtrace::capture(void* from, unsigned maxFrames, void* context) { - auto bt = make_shared(0, 0); - bt->_capture(from, maxFrames, context); - return bt; - } - Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames, void* context) { if ( maxFrames > 0 ) _capture(skipFrames + 1, maxFrames, context); } void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames, void* context) { - _addrs.resize(++skipFrames + maxFrames); // skip this frame + skipFrames += 2; // skip this frame and its child + _addrs.resize(skipFrames + maxFrames); auto n = raw_capture(&_addrs[0], skipFrames + maxFrames, context); _addrs.resize(n); skip(skipFrames); } - void Backtrace::_capture(void* from, unsigned maxFrames, void* context) { - _addrs.resize(maxFrames + 8); - auto n = raw_capture(&_addrs[0], maxFrames + 8, context); - _addrs.resize(n); - for ( int i = 0; i < n; ++i ) { - if ( _addrs[i] == from ) { - skip(i); - break; - } - } - } - void Backtrace::skip(unsigned nFrames) { _addrs.erase(_addrs.begin(), _addrs.begin() + min(size_t(nFrames), _addrs.size())); } @@ -612,63 +574,57 @@ namespace fleece { namespace signal_safe { void write_long(long long value, int fd) { // NOTE: This function assumes buffer is at least 21 bytes long - if (value == 0) { + if ( value == 0 ) { write_to_and_stderr(fd, "0", 1); } else { - if (value < 0) write_to_and_stderr(fd, "-", 1); - char temp[20]; + if ( value < 0 ) write_to_and_stderr(fd, "-", 1); + char temp[20]; char* p = temp; - while (value > 0) { + while ( value > 0 ) { *p++ = static_cast(value % 10 + '0'); value /= 10; } - while (p != temp) { - write_to_and_stderr(fd, --p, 1); - } + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } } } void write_ulong(unsigned long long value, int fd) { // NOTE: This function assumes buffer is at least 21 bytes long - if (value == 0) { + if ( value == 0 ) { write_to_and_stderr(fd, "0", 1); } else { - char temp[20]; + char temp[20]; char* p = temp; - while (value > 0) { + while ( value > 0 ) { *p++ = static_cast(value % 10 + '0'); value /= 10; } - while (p != temp) { - write_to_and_stderr(fd, --p, 1); - } + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } } } void write_hex_offset(size_t value, int fd) { - static const char* hexDigits = "0123456789abcdef"; - const int num_digits = sizeof(size_t) * 2; + static const char* hexDigits = "0123456789abcdef"; + const int num_digits = sizeof(size_t) * 2; write_to_and_stderr(fd, "0x", 2); - if (value == 0) { + if ( value == 0 ) { write_to_and_stderr(fd, "0", 1); } else { - char temp[num_digits]; + char temp[num_digits]; char* p = temp; - while (value > 0) { + while ( value > 0 ) { *p++ = hexDigits[value & 0xF]; value >>= 4; } - while (p != temp) { - write_to_and_stderr(fd, --p, 1); - } + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } } } void write_to_and_stderr(int fd, const char* str, size_t n) { - if (fd != -1) write(fd, str, n ? n : strlen(str)); + if ( fd != -1 ) write(fd, str, n ? n : strlen(str)); write(STDERR_FILENO, str, n ? n : strlen(str)); } -} \ No newline at end of file +} // namespace signal_safe diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index e0999d46..1effd31f 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -20,13 +20,14 @@ #include #include #include +#include namespace signal_safe { void write_long(long long value, int fd); void write_ulong(unsigned long long value, int fd); void write_hex_offset(size_t value, int fd); void write_to_and_stderr(int fd, const char* str, size_t len = 0); -} +} // namespace signal_safe namespace fleece { class BacktraceSignalHandler { From 57b812e11c8843c22f7b30150ebd4a23d4f0b90b Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 13 Mar 2026 07:40:32 +0900 Subject: [PATCH 07/13] Window enhancements and remove std::terminate handler It now gets caught in the abort handler --- Fleece/Support/Backtrace+signals-win32.cc | 60 +++++++++++++++-------- Fleece/Support/Backtrace.cc | 31 +++--------- Fleece/Support/Backtrace.hh | 19 +++---- cmake/platform_linux.cmake | 12 +---- cmake/platform_win.cmake | 12 +---- 5 files changed, 57 insertions(+), 77 deletions(-) diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc index 7a6ae824..a570e310 100644 --- a/Fleece/Support/Backtrace+signals-win32.cc +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include namespace fleece { @@ -12,10 +12,10 @@ namespace fleece { public: BacktraceSignalHandlerWin32() { SetUnhandledExceptionFilter(crash_handler); - signal(SIGABRT, signal_handler); + previous_sig_handler() = signal(SIGABRT, signal_handler); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - _set_purecall_handler(&terminator); - _set_invalid_parameter_handler(&invalid_parameter_handler); + previous_purecall_handler() = _set_purecall_handler(&terminator); + previous_invalid_parameter_handler() = _set_invalid_parameter_handler(&invalid_parameter_handler); } BacktraceSignalHandlerWin32(const BacktraceSignalHandlerWin32&) = delete; @@ -24,6 +24,21 @@ namespace fleece { BacktraceSignalHandlerWin32& operator=(BacktraceSignalHandlerWin32&&) = delete; private: + static _invalid_parameter_handler& previous_invalid_parameter_handler() { + static _invalid_parameter_handler handler; + return handler; + } + + static _purecall_handler& previous_purecall_handler() { + static _purecall_handler handler; + return handler; + } + + static _crt_signal_t& previous_sig_handler() { + static _crt_signal_t handler; + return handler; + } + static string& violation_type() { static string type; return type; @@ -51,13 +66,16 @@ namespace fleece { static inline void signal_handler(int sig) { violation_type() = (sig == SIGABRT) ? "Abort Signal (SIGABRT)" : "Signal"; crash_handler_immediate(signal_skip_recs); + signal(sig, previous_sig_handler()); + raise(sig); abort(); } - static inline void __cdecl invalid_parameter_handler(const wchar_t*, const wchar_t*, const wchar_t*, - unsigned int, uintptr_t) { + static inline void __cdecl invalid_parameter_handler(const wchar_t* a, const wchar_t* b, const wchar_t* c, + unsigned int d, uintptr_t e) { violation_type() = "Invalid CRT Parameter"; crash_handler_immediate(signal_skip_recs); + previous_invalid_parameter_handler()(a, b, c, d, e); abort(); } @@ -78,22 +96,24 @@ namespace fleece { bt = Backtrace::capture(skip, 32 + skip); } - if ( sLogger ) { - stringstream out; - out << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - bt->writeTo(out); - out << "\n******************** Now terminating ********************\n"; - sLogger(out.str()); - } else { - cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; - bt->writeTo(cerr); - cerr << "\n******************** Now terminating ********************\n"; + if ( sCrashStream ) { + sCrashStream << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(sCrashStream); + sCrashStream << "\n******************** Now terminating ********************\n"; } + + cerr << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + bt->writeTo(cerr); + cerr << "\n******************** Now terminating ********************\n"; } }; + + // Awkwardly defined here to avoid FleeceBase getting its own copy since it also compiles Backtrace.cc + ofstream BacktraceSignalHandler::sCrashStream; + + void BacktraceSignalHandler::setLogPath(const char* path) { + sCrashStream = ofstream(path, ios::out | ios::trunc | ios::binary); + } } // namespace fleece -fleece::BacktraceSignalHandler& getSignalHandler() { - static fleece::BacktraceSignalHandlerWin32 handler; - return handler; -} +static fleece::BacktraceSignalHandlerWin32 handler; diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 77b1bbca..17ee830d 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -330,7 +330,7 @@ namespace fleece { # pragma comment(lib, "Dbghelp.lib") # include -# include +# include # include "asprintf.h" # include # include @@ -350,7 +350,7 @@ namespace fleece { } } - static int backtrace(void** buffer, int max, void* context) { + int Backtrace::raw_capture(void** buffer, int max, void* context) { CONTEXT localCtx; CONTEXT* myCtx; if ( context ) { @@ -370,8 +370,7 @@ namespace fleece { // we use GetCurrentThread() since we're walking while in a valid thread context. HANDLE thread = GetCurrentThread(); auto process = GetCurrentProcess(); - STACKFRAME64 s; - memset(&s, 0, sizeof(STACKFRAME64)); + STACKFRAME64 s{}; s.AddrStack.Mode = AddrModeFlat; s.AddrFrame.Mode = AddrModeFlat; @@ -400,7 +399,7 @@ namespace fleece { if ( s.AddrReturn.Offset == 0 ) { break; } - buffer[i] = (void*)s.AddrReturn.Offset; + buffer[i] = reinterpret_cast(s.AddrReturn.Offset); size++; } @@ -467,11 +466,10 @@ namespace fleece { exit: free(symbol); free(line); - SymCleanup(process); return success; } - static char* unmangle(const char* function) { return (char*)function; } + static char* unmangle(const char* function) { return const_cast(function); } } // namespace fleece @@ -552,25 +550,9 @@ namespace fleece { cerr << "\n******************** Now terminating ********************\n"; } } - - void Backtrace::installTerminateHandler(function logger) { - static once_flag sOnce; - call_once(sOnce, [&] { - static auto const sLogger = std::move(logger); - static terminate_handler const sOldHandler = set_terminate([] { - // ---- Code below gets called by C++ runtime on a call to terminate --- - handleTerminate(sLogger); - // Chain to old handler: - sOldHandler(); - // Just in case the old handler doesn't abort: - abort(); - // ---- End of handler ---- - }); - }); - } - } // namespace fleece +#ifndef _WIN32 namespace signal_safe { void write_long(long long value, int fd) { // NOTE: This function assumes buffer is at least 21 bytes long @@ -628,3 +610,4 @@ namespace signal_safe { write(STDERR_FILENO, str, n ? n : strlen(str)); } } // namespace signal_safe +#endif diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index 1effd31f..88bc39ff 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -18,9 +18,11 @@ #include #include #include +#include +#include +#ifndef _WIN32 #include #include -#include namespace signal_safe { void write_long(long long value, int fd); @@ -28,6 +30,7 @@ namespace signal_safe { void write_hex_offset(size_t value, int fd); void write_to_and_stderr(int fd, const char* str, size_t len = 0); } // namespace signal_safe +#endif namespace fleece { class BacktraceSignalHandler { @@ -35,7 +38,11 @@ namespace fleece { static void setLogPath(const char* path); protected: +#ifdef _WIN32 + static std::ofstream sCrashStream; +#else static volatile sig_atomic_t sLogFD; +#endif }; /** Captures a backtrace of the current thread, and can convert it to human-readable form. */ @@ -93,16 +100,6 @@ namespace fleece { frameInfo operator[](unsigned i) { return getFrame(i); } - /// Installs a C++ terminate_handler that will log a backtrace and info about any uncaught - /// exception. By default it then calls the preexisting terminate_handler, which usually - /// calls abort(). - /// - /// Since the OS will not usually generate a crash report upon a SIGABORT, you can set - /// `andRaise` to a different signal ID such as SIGILL to force a crash. - /// - /// Only the first call to this function has any effect; subsequent calls are ignored. - static void installTerminateHandler(std::function logger); - private: void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); diff --git a/cmake/platform_linux.cmake b/cmake/platform_linux.cmake index 72bf8f19..c1eceb28 100644 --- a/cmake/platform_linux.cmake +++ b/cmake/platform_linux.cmake @@ -49,17 +49,7 @@ function(set_source_files) endfunction() function(set_base_platform_files) - set(oneValueArgs RESULT) - cmake_parse_arguments(LINUX_SSS "" "${oneValueArgs}" "" ${ARGN}) - if(NOT DEFINED LINUX_SSS_RESULT) - message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") - endif() - - set( - ${LINUX_SSS_RESULT} - Fleece/Support/Backtrace+signals-posix.cc - PARENT_SCOPE - ) + # No-op endfunction() function(set_test_source_files) diff --git a/cmake/platform_win.cmake b/cmake/platform_win.cmake index 5223516c..cf38df29 100644 --- a/cmake/platform_win.cmake +++ b/cmake/platform_win.cmake @@ -19,17 +19,7 @@ function(set_source_files) endfunction() function(set_base_platform_files) - set(oneValueArgs RESULT) - cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) - if(NOT DEFINED WIN_SSS_RESULT) - message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") - endif() - - set( - ${WIN_SSS_RESULT} - Fleece/Support/Backtrace+signals-win32.cc - PARENT_SCOPE - ) + # No-op endfunction() function(set_test_source_files) From abdba6bbc1ffc90a7dab7fe4c9ee43b6bb301bbf Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 13 Mar 2026 09:00:50 +0900 Subject: [PATCH 08/13] Code refactor to make more readable --- Fleece/Support/Backtrace+capture-darwin.cc | 13 + Fleece/Support/Backtrace+capture-linux.cc | 130 +++++ Fleece/Support/Backtrace+capture-posix.cc | 189 ++++++++ Fleece/Support/Backtrace+capture-win32.cc | 129 +++++ Fleece/Support/Backtrace.cc | 538 +-------------------- cmake/platform_apple.cmake | 10 +- cmake/platform_linux.cmake | 15 +- cmake/platform_win.cmake | 13 +- 8 files changed, 495 insertions(+), 542 deletions(-) create mode 100644 Fleece/Support/Backtrace+capture-darwin.cc create mode 100644 Fleece/Support/Backtrace+capture-linux.cc create mode 100644 Fleece/Support/Backtrace+capture-posix.cc create mode 100644 Fleece/Support/Backtrace+capture-win32.cc diff --git a/Fleece/Support/Backtrace+capture-darwin.cc b/Fleece/Support/Backtrace+capture-darwin.cc new file mode 100644 index 00000000..0fd6f1eb --- /dev/null +++ b/Fleece/Support/Backtrace+capture-darwin.cc @@ -0,0 +1,13 @@ +#ifndef __APPLE__ +# error This file is only for Darwin (macOS/iOS) platforms +#endif + +#include "Backtrace.hh" +#include + +namespace fleece { + int Backtrace::raw_capture(void** buffer, int max, void* context) { + if ( max == 0 ) return 0; + return backtrace(buffer, max); + } +} // namespace fleece diff --git a/Fleece/Support/Backtrace+capture-linux.cc b/Fleece/Support/Backtrace+capture-linux.cc new file mode 100644 index 00000000..041c773a --- /dev/null +++ b/Fleece/Support/Backtrace+capture-linux.cc @@ -0,0 +1,130 @@ +#if !defined(__linux__) && !defined(__ANDROID__) +# error "This implementation is meant for Linux and Android only +#endif + +#include "Backtrace.hh" +#include // dladdr() +#include // part of compiler runtime, no extra link needed +#include +#include +#include + +namespace fleece { + using namespace std; + + struct BacktraceState { + void** current; + void** end; + }; + + static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { + BacktraceState* state = static_cast(arg); + uintptr_t pc = _Unwind_GetIP(context); + if ( pc ) { + if ( state->current == state->end ) return _URC_END_OF_STACK; + *state->current++ = reinterpret_cast(pc); + } + return _URC_NO_REASON; + } + + int Backtrace::raw_capture(void** buffer, int max, void* context) { + BacktraceState state = {buffer, buffer + max}; + _Unwind_Backtrace(unwindCallback, &state); + return int(state.current - buffer); + } + + static backtrace_state* getBacktraceState() { + static std::once_flag once; + static backtrace_state* state; + std::call_once(once, [] { state = backtrace_create_state(nullptr, /*threaded=*/1, nullptr, nullptr); }); + return state; + } + + struct ResolvedInfo { + const char* function = nullptr; + const char* file = nullptr; + int line = 0; + uintptr_t symval = 0; // function start address, for offset calculation + }; + + static ResolvedInfo backtraceResolve(uintptr_t pc) { + ResolvedInfo info; + backtrace_pcinfo( + getBacktraceState(), pc, + [](void* data, uintptr_t, const char* file, int line, const char* fn) -> int { + auto* r = static_cast(data); + if ( fn && !r->function ) r->function = fn; + if ( file && !r->file ) { + r->file = file; + r->line = line; + } + return 0; + }, + nullptr, &info); + + // Always call syminfo: provides symval (function start) for offset, and function name fallback + backtrace_syminfo( + getBacktraceState(), pc, + [](void* data, uintptr_t, const char* sym, uintptr_t symval, uintptr_t) { + auto* r = static_cast(data); + if ( !r->function && sym ) r->function = sym; + if ( symval ) r->symval = symval; + }, + nullptr, &info); + return info; + } + + Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { + frameInfo frame = {}; + frame.pc = addr; + + // dladdr gives us the library name regardless of symbol visibility, + // and a fallback saddr/sname for exported symbols. + Dl_info dlInfo = {}; + dladdr(frame.pc, &dlInfo); + if ( dlInfo.dli_fname ) { + frame.library = dlInfo.dli_fname; + if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; + } + + uintptr_t pc = reinterpret_cast(frame.pc); + if ( !stack_top ) pc -= 1; // return address → call site + if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); + auto resolved = backtraceResolve(pc); + frame.function = resolved.function; + frame.file = resolved.file; + frame.line = resolved.line; + // Prefer symval from libbacktrace (works for hidden-visibility symbols); + // fall back to dladdr's saddr for exported symbols. + if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; + else if ( dlInfo.dli_saddr ) + frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; + + return frame; + } + + const char* Backtrace::getSymbol(unsigned i) const { + // delegate to getFrame which already uses libbacktrace + static thread_local string tName; + auto frame = getFrame(i); + if ( frame.function ) { + tName = frame.function; + return tName.c_str(); + } + + return nullptr; + } + + string FunctionName(const void* pc) { +#if !defined(__ANDROID__) + uintptr_t addr = reinterpret_cast(pc) - 1; + if ( const char* name = backtraceResolve(addr).function ) return Unmangle(name); + return {}; +#else + Dl_info info = {}; + dladdr(pc, &info); + if ( info.dli_sname ) return Unmangle(info.dli_sname); + return {}; +#endif + } +} // namespace fleece diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc new file mode 100644 index 00000000..8d0ee349 --- /dev/null +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -0,0 +1,189 @@ +#ifdef _WIN32 +# error "This implementation is not meant to be used on Windows +#endif + +#include "Backtrace.hh" +#include // abi::__cxa_demangle() +#include + +namespace fleece { + using namespace std; + using namespace signal_safe; + + static constexpr struct { + const char *old, *nuu; + } kAbbreviations[] = { + {"(anonymous namespace)", "(anon)"}, + {"std::__1::", "std::"}, + {"std::__cxx11::", "std::"}, + {"std::basic_string, std::allocator >", "std::string"}, + }; + + // If any of these strings occur in a backtrace, suppress further frames. + static constexpr const char* kTerminalFunctions[] = { + "_C_A_T_C_H____T_E_S_T_", + "Catch::TestInvokerAsFunction::invoke() const", + "litecore::actor::Scheduler::task(unsigned)", + "litecore::actor::GCDMailbox::safelyCall", + }; + + static void replace(std::string& str, string_view oldStr, string_view newStr) { + string::size_type pos = 0; + while ( string::npos != (pos = str.find(oldStr, pos)) ) { + str.replace(pos, oldStr.size(), newStr); + pos += newStr.size(); + } + } + + char* unmangle(const char* function) { + int status; + size_t unmangledLen; + char* unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); + if ( unmangled && status == 0 ) return unmangled; + free(unmangled); + return (char*)function; + } + + bool Backtrace::writeTo(ostream& out) const { + for ( unsigned i = 0; i < unsigned(_addrs.size()); ++i ) { + if ( i > 0 ) out << '\n'; + out << '\t'; + char* cstr = nullptr; + auto frame = getFrame(i); + int len; + bool stop = false; + const char* lib = frame.library ? frame.library : "?"; + string name = frame.function ? Unmangle(frame.function) : string(); + if ( !name.empty() ) { + for ( auto fn : kTerminalFunctions ) + if ( name.find(fn) != string::npos ) stop = true; + for ( auto& abbrev : kAbbreviations ) replace(name, abbrev.old, abbrev.nuu); + } +#ifndef __APPLE__ + if ( frame.file ) { + const char* fileBase = strrchr(frame.file, '/'); + fileBase = fileBase ? fileBase + 1 : frame.file; + len = asprintf(&cstr, "%2u %-25s %s + %zu (%s:%d)", i, lib, name.empty() ? "?" : name.c_str(), + frame.offset, fileBase, frame.line); + } else +#endif + { + len = asprintf(&cstr, "%2u %-25s %s + %zu [0x%zx]", i, lib, name.empty() ? "?" : name.c_str(), + frame.offset, frame.imageOffset); + } + + if ( len < 0 ) return false; + + if ( len > 0 ) { + out.write(cstr, size_t(len)); + free(cstr); + } + + if ( stop ) { + out << "\n\t ... (" << (_addrs.size() - i - 1) << " more suppressed) ..."; + break; + } + } + return true; + } + + void Backtrace::writeTo(void** addresses, int size, int fd) { + for ( unsigned i = 0; i < unsigned(size); ++i ) { + if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); + write_to_and_stderr(fd, "\t", 1); + auto frame = getFrame(addresses[i], i == 0); + const char* lib = frame.library ? frame.library : "?"; + const char* name = frame.function; + + if ( i < 10 ) write_to_and_stderr(fd, " ", 1); + write_ulong(i, fd); + const size_t lib_len = strlen(lib); + write_to_and_stderr(fd, " ", 1); + write_to_and_stderr(fd, lib, lib_len); + for ( size_t j = lib_len; j < 26; ++j ) write_to_and_stderr(fd, " ", 1); + if ( name ) { + write_to_and_stderr(fd, name, strlen(name)); + } else { + write_to_and_stderr(fd, "?", 1); + } + + write_to_and_stderr(fd, " + ", 3); + write_ulong(frame.offset, fd); + +#ifndef __APPLE__ + if ( frame.file ) { + const char* fileBase = strrchr(frame.file, '/'); + fileBase = fileBase ? fileBase + 1 : frame.file; + write_to_and_stderr(fd, " (", 2); + write_to_and_stderr(fd, fileBase); + write_to_and_stderr(fd, ":", 1); + write_long(frame.line, fd); + write_to_and_stderr(fd, ")", 1); + } else +#endif + { + write_to_and_stderr(fd, " [", 2); + write_hex_offset(frame.imageOffset, fd); + write_to_and_stderr(fd, "]", 1); + } + } + } +} // namespace fleece + +namespace signal_safe { + void write_long(long long value, int fd) { + // NOTE: This function assumes buffer is at least 21 bytes long + if ( value == 0 ) { + write_to_and_stderr(fd, "0", 1); + } else { + if ( value < 0 ) write_to_and_stderr(fd, "-", 1); + char temp[20]; + char* p = temp; + while ( value > 0 ) { + *p++ = static_cast(value % 10 + '0'); + value /= 10; + } + + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } + } + } + + void write_ulong(unsigned long long value, int fd) { + // NOTE: This function assumes buffer is at least 21 bytes long + if ( value == 0 ) { + write_to_and_stderr(fd, "0", 1); + } else { + char temp[20]; + char* p = temp; + while ( value > 0 ) { + *p++ = static_cast(value % 10 + '0'); + value /= 10; + } + + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } + } + } + + void write_hex_offset(size_t value, int fd) { + static const char* hexDigits = "0123456789abcdef"; + const int num_digits = sizeof(size_t) * 2; + write_to_and_stderr(fd, "0x", 2); + if ( value == 0 ) { + write_to_and_stderr(fd, "0", 1); + } else { + char temp[num_digits]; + char* p = temp; + while ( value > 0 ) { + *p++ = hexDigits[value & 0xF]; + value >>= 4; + } + + while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } + } + } + + void write_to_and_stderr(int fd, const char* str, size_t n) { + if ( fd != -1 ) write(fd, str, n ? n : strlen(str)); + write(STDERR_FILENO, str, n ? n : strlen(str)); + } +} // namespace signal_safe diff --git a/Fleece/Support/Backtrace+capture-win32.cc b/Fleece/Support/Backtrace+capture-win32.cc new file mode 100644 index 00000000..8fb8479c --- /dev/null +++ b/Fleece/Support/Backtrace+capture-win32.cc @@ -0,0 +1,129 @@ +#ifndef _WIN32 +# error "This implementation is only for Windows" +#endif + +#include "Backtrace.hh" +#include + +namespace fleece { + char* unmangle(const char* function) { return const_cast(function); } + + int Backtrace::raw_capture(void** buffer, int max, void* context) { + CONTEXT localCtx; + CONTEXT* myCtx; + if ( context ) { + memcpy(&localCtx, context, sizeof(CONTEXT)); + myCtx = &localCtx; + } else { + RtlCaptureContext(&localCtx); + myCtx = &localCtx; + } + + if ( max == 0 ) return 0; + + // Initialize symbol handler for StackWalk64 + initialize_symbols(); + + // StackWalk64 needs a valid thread handle. If we have a captured context, + // we use GetCurrentThread() since we're walking while in a valid thread context. + HANDLE thread = GetCurrentThread(); + auto process = GetCurrentProcess(); + STACKFRAME64 s{}; + + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrPC.Mode = AddrModeFlat; +#if defined(_M_X64) + s.AddrPC.Offset = myCtx->Rip; + s.AddrStack.Offset = myCtx->Rsp; + s.AddrFrame.Offset = myCtx->Rbp; + auto machine_type = IMAGE_FILE_MACHINE_AMD64; +#elif defined(_M_ARM64) + s.AddrPC.Offset = myCtx->Pc; + s.AddrStack.Offset = myCtx->Sp; + s.AddrFrame.Offset = myCtx->R11; + auto machine_type = IMAGE_FILE_MACHINE_ARM64; +#else +# error Unsupported architecture +#endif + + auto size = 0; + for ( int i = 0; i < max; i++ ) { + SetLastError(0); + if ( !StackWalk64(machine_type, process, thread, &s, myCtx, NULL, SymFunctionTableAccess64, + SymGetModuleBase64, NULL) ) { + break; + } + + if ( s.AddrReturn.Offset == 0 ) { break; } + + buffer[i] = reinterpret_cast(s.AddrReturn.Offset); + size++; + } + + return size; + } + + bool Backtrace::writeTo(ostream& out) const { + const auto process = GetCurrentProcess(); + SYMBOL_INFO* symbol = nullptr; + IMAGEHLP_LINE64* line = nullptr; + bool success = false; + + // Initialize symbol handler for symbol resolution + initialize_symbols(); + + symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 1023 * sizeof(TCHAR)); + if ( !symbol ) goto exit; + symbol->MaxNameLen = 1024; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + DWORD displacement; + line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); + if ( !line ) goto exit; + line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for ( unsigned i = 0; i < _addrs.size(); i++ ) { + if ( i > 0 ) out << "\r\n"; + out << '\t'; + const auto address = (DWORD64)_addrs[i]; + + // Module basename + void* moduleBase = nullptr; + RtlPcToFileHeader(_addrs[i], &moduleBase); + DWORD64 rva = moduleBase ? (address - (DWORD64)moduleBase) : 0; + char modulePath[4096] = "?"; + if ( moduleBase ) { + int mlen = GetModuleFileNameA((HMODULE)moduleBase, modulePath, sizeof(modulePath)); + if ( mlen > 0 ) { + char* p = modulePath + mlen - 1; + while ( p > modulePath && p[-1] != '\\' ) --p; + memmove(modulePath, p, strlen(p) + 1); + } + } + + // Function name and offset from symbol start + DWORD64 fnOffset = 0; + BOOL symFound = SymFromAddr(process, address, &fnOffset, symbol); + + const char* fn = symFound ? symbol->Name : "?"; + char* cstr = nullptr; + if ( SymGetLineFromAddr64(process, address, &displacement, line) ) { + const char* fileBase = strrchr(line->FileName, '\\'); + fileBase = fileBase ? fileBase + 1 : line->FileName; + asprintf(&cstr, "%2u %-25s %s + %llu (%s:%lu)", i, modulePath, fn, fnOffset, fileBase, + line->LineNumber); + } else { + asprintf(&cstr, "%2u %-25s %s + %llu [0x%llx]", i, modulePath, fn, fnOffset, rva); + } + if ( !cstr ) goto exit; + out << cstr; + free(cstr); + } + success = true; + + exit: + free(symbol); + free(line); + return success; + } +} // namespace fleece diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 17ee830d..7a5dc987 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -19,466 +19,21 @@ #include #include "betterassert.hh" -#ifndef _MSC_VER -# include // dladdr() -# include // abi::__cxa_demangle() - -# if defined(__ANDROID__) || (defined(__linux__) && defined(HAVE_LIBBACKTRACE)) -# include // part of compiler runtime, no extra link needed -# ifdef HAVE_LIBBACKTRACE -# include -# endif - -// Generic _Unwind_Backtrace implementation: -struct BacktraceState { - void** current; - void** end; -}; - -static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { - BacktraceState* state = static_cast(arg); - uintptr_t pc = _Unwind_GetIP(context); - if ( pc ) { - if ( state->current == state->end ) return _URC_END_OF_STACK; - *state->current++ = reinterpret_cast(pc); - } - return _URC_NO_REASON; -} - -int fleece::Backtrace::raw_capture(void** buffer, int max, void* context) { - BacktraceState state = {buffer, buffer + max}; - _Unwind_Backtrace(unwindCallback, &state); - return int(state.current - buffer); -} - -# ifdef HAVE_LIBBACKTRACE -static backtrace_state* getBacktraceState() { - static std::once_flag once; - static backtrace_state* state; - std::call_once(once, [] { state = backtrace_create_state(nullptr, /*threaded=*/1, nullptr, nullptr); }); - return state; -} - -struct ResolvedInfo { - const char* function = nullptr; - const char* file = nullptr; - int line = 0; - uintptr_t symval = 0; // function start address, for offset calculation -}; - -static ResolvedInfo backtraceResolve(uintptr_t pc) { - ResolvedInfo info; - backtrace_pcinfo( - getBacktraceState(), pc, - [](void* data, uintptr_t, const char* file, int line, const char* fn) -> int { - auto* r = static_cast(data); - if ( fn && !r->function ) r->function = fn; - if ( file && !r->file ) { - r->file = file; - r->line = line; - } - return 0; - }, - nullptr, &info); - // Always call syminfo: provides symval (function start) for offset, and function name fallback - backtrace_syminfo( - getBacktraceState(), pc, - [](void* data, uintptr_t, const char* sym, uintptr_t symval, uintptr_t) { - auto* r = static_cast(data); - if ( !r->function && sym ) r->function = sym; - if ( symval ) r->symval = symval; - }, - nullptr, &info); - return info; -} -# endif // HAVE_LIBBACKTRACE - -# elif defined(__APPLE__) -# define HAVE_EXECINFO -# include - -int fleece::Backtrace::raw_capture(void** buffer, int max, void* context) { - if ( max == 0 ) return 0; - return backtrace(buffer, max); -} -# else -# define HAVE_EXECINFO -# include -# endif - - namespace fleece { using namespace std; using namespace signal_safe; - static char* unmangle(const char* function) { - int status; - size_t unmangledLen; - char* unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); - if ( unmangled && status == 0 ) return unmangled; - free(unmangled); - return (char*)function; - } - - static const char* unmangle_safe(const char* function) { - static char buffer[1024]; - int status; - size_t unmangledLen = 1024; - char* unmangled = abi::__cxa_demangle(function, buffer, &unmangledLen, &status); - if ( unmangled && status == 0 ) return unmangled; - return function; - } - - Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { - frameInfo frame = {}; - frame.pc = addr; - - // dladdr gives us the library name regardless of symbol visibility, - // and a fallback saddr/sname for exported symbols. - Dl_info dlInfo = {}; - dladdr(frame.pc, &dlInfo); - if ( dlInfo.dli_fname ) { - frame.library = dlInfo.dli_fname; - if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; - } -# if defined(HAVE_LIBBACKTRACE) - uintptr_t pc = reinterpret_cast(frame.pc); - if ( !stack_top ) pc -= 1; // return address → call site - if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); - auto resolved = backtraceResolve(pc); - frame.function = resolved.function; - frame.file = resolved.file; - frame.line = resolved.line; - // Prefer symval from libbacktrace (works for hidden-visibility symbols); - // fall back to dladdr's saddr for exported symbols. - if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; - else if ( dlInfo.dli_saddr ) - frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; -# else - frame.function = dlInfo.dli_sname; // exported symbols only - if ( dlInfo.dli_saddr ) frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; - if ( dlInfo.dli_fbase ) { - uintptr_t pc = reinterpret_cast(frame.pc); - if ( !stack_top ) pc -= 1; - frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); - } -# endif - return frame; - } - Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { precondition(i < _addrs.size()); return getFrame(_addrs[i], i == 0); } - const char* Backtrace::getSymbol(unsigned i) const { -# if defined(HAVE_LIBBACKTRACE) - // delegate to getFrame which already uses libbacktrace - static thread_local string tName; - auto frame = getFrame(i); - if ( frame.function ) { - tName = frame.function; - return tName.c_str(); - } - return nullptr; -# elif defined(HAVE_EXECINFO) - precondition(i < _addrs.size()); - if ( !_symbols ) _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); - if ( _symbols ) { - const char* s = _symbols[i]; -# if __APPLE__ - while ( *s && isdigit(*s) ) ++s; - while ( *s && isspace(*s) ) ++s; -# else - const char* slash = strrchr(s, '/'); - if ( slash ) s = slash + 1; -# endif - return s; - } -# endif - return nullptr; - } - - // If any of these strings occur in a backtrace, suppress further frames. - static constexpr const char* kTerminalFunctions[] = { - "_C_A_T_C_H____T_E_S_T_", - "Catch::TestInvokerAsFunction::invoke() const", - "litecore::actor::Scheduler::task(unsigned)", - "litecore::actor::GCDMailbox::safelyCall", - }; - - static constexpr struct { - const char *old, *nuu; - } kAbbreviations[] = { - {"(anonymous namespace)", "(anon)"}, - {"std::__1::", "std::"}, - {"std::__cxx11::", "std::"}, - {"std::basic_string, std::allocator >", "std::string"}, - }; - - static void replace(std::string& str, string_view oldStr, string_view newStr) { - string::size_type pos = 0; - while ( string::npos != (pos = str.find(oldStr, pos)) ) { - str.replace(pos, oldStr.size(), newStr); - pos += newStr.size(); - } - } - - bool Backtrace::writeTo(ostream& out) const { - for ( unsigned i = 0; i < unsigned(_addrs.size()); ++i ) { - if ( i > 0 ) out << '\n'; - out << '\t'; - char* cstr = nullptr; - auto frame = getFrame(i); - int len; - bool stop = false; - const char* lib = frame.library ? frame.library : "?"; - string name = frame.function ? Unmangle(frame.function) : string(); - if ( !name.empty() ) { - for ( auto fn : kTerminalFunctions ) - if ( name.find(fn) != string::npos ) stop = true; - for ( auto& abbrev : kAbbreviations ) replace(name, abbrev.old, abbrev.nuu); - } -# ifndef __APPLE__ - if ( frame.file ) { - const char* fileBase = strrchr(frame.file, '/'); - fileBase = fileBase ? fileBase + 1 : frame.file; - len = asprintf(&cstr, "%2u %-25s %s + %zu (%s:%d)", i, lib, name.empty() ? "?" : name.c_str(), - frame.offset, fileBase, frame.line); - } else -# endif - { - len = asprintf(&cstr, "%2u %-25s %s + %zu [0x%zx]", i, lib, name.empty() ? "?" : name.c_str(), - frame.offset, frame.imageOffset); - } - - if ( len < 0 ) return false; - - if ( len > 0 ) { - out.write(cstr, size_t(len)); - free(cstr); - } - - if ( stop ) { - out << "\n\t ... (" << (_addrs.size() - i - 1) << " more suppressed) ..."; - break; - } - } - return true; - } - - void Backtrace::writeTo(void** addresses, int size, int fd) { - for ( unsigned i = 0; i < unsigned(size); ++i ) { - if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); - write_to_and_stderr(fd, "\t", 1); - auto frame = getFrame(addresses[i], i == 0); - const char* lib = frame.library ? frame.library : "?"; - const char* name = frame.function ? unmangle_safe(frame.function) : nullptr; - - if ( i < 10 ) write_to_and_stderr(fd, " ", 1); - write_ulong(i, fd); - const size_t lib_len = strlen(lib); - write_to_and_stderr(fd, " ", 1); - write_to_and_stderr(fd, lib, lib_len); - for ( size_t j = lib_len; j < 26; ++j ) write_to_and_stderr(fd, " ", 1); - if ( name ) { - write_to_and_stderr(fd, name, strlen(name)); - } else { - write_to_and_stderr(fd, "?", 1); - } - - write_to_and_stderr(fd, " + ", 3); - write_ulong(frame.offset, fd); - -# ifndef __APPLE__ - if ( frame.file ) { - const char* fileBase = strrchr(frame.file, '/'); - fileBase = fileBase ? fileBase + 1 : frame.file; - write_to_and_stderr(fd, " (", 2); - write_to_and_stderr(fd, fileBase); - write_to_and_stderr(fd, ":", 1); - write_long(frame.line, fd); - write_to_and_stderr(fd, ")", 1); - } else -# endif - { - write_to_and_stderr(fd, " [", 2); - write_hex_offset(frame.imageOffset, fd); - write_to_and_stderr(fd, "]", 1); - } - } - } - - std::string FunctionName(const void* pc) { -# if defined(__linux__) && !defined(__ANDROID__) && defined(HAVE_LIBBACKTRACE) - uintptr_t addr = reinterpret_cast(pc) - 1; - if ( const char* name = backtraceResolve(addr).function ) return Unmangle(name); - return {}; -# else - Dl_info info = {}; - dladdr(pc, &info); - if ( info.dli_sname ) return Unmangle(info.dli_sname); - return {}; -# endif - } } // namespace fleece - -#else // _MSC_VER -# pragma mark - WINDOWS IMPLEMENTATION: - -# pragma comment(lib, "Dbghelp.lib") -# include -# include -# include "asprintf.h" -# include -# include -using namespace std; - namespace fleece { - static void initialize_symbols() { - static bool initialized = false; - if ( !initialized ) { - auto process = GetCurrentProcess(); - SymInitialize(process, nullptr, TRUE); - DWORD symOptions = SymGetOptions(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - SymSetOptions(symOptions); - initialized = true; - } - } - - int Backtrace::raw_capture(void** buffer, int max, void* context) { - CONTEXT localCtx; - CONTEXT* myCtx; - if ( context ) { - memcpy(&localCtx, context, sizeof(CONTEXT)); - myCtx = &localCtx; - } else { - RtlCaptureContext(&localCtx); - myCtx = &localCtx; - } - - if ( max == 0 ) return 0; - - // Initialize symbol handler for StackWalk64 - initialize_symbols(); - - // StackWalk64 needs a valid thread handle. If we have a captured context, - // we use GetCurrentThread() since we're walking while in a valid thread context. - HANDLE thread = GetCurrentThread(); - auto process = GetCurrentProcess(); - STACKFRAME64 s{}; - - s.AddrStack.Mode = AddrModeFlat; - s.AddrFrame.Mode = AddrModeFlat; - s.AddrPC.Mode = AddrModeFlat; -# if defined(_M_X64) - s.AddrPC.Offset = myCtx->Rip; - s.AddrStack.Offset = myCtx->Rsp; - s.AddrFrame.Offset = myCtx->Rbp; - auto machine_type = IMAGE_FILE_MACHINE_AMD64; -# elif defined(_M_ARM64) - s.AddrPC.Offset = myCtx->Pc; - s.AddrStack.Offset = myCtx->Sp; - s.AddrFrame.Offset = myCtx->R11; - auto machine_type = IMAGE_FILE_MACHINE_ARM64; -# else -# error Unsupported architecture -# endif - - auto size = 0; - for ( int i = 0; i < max; i++ ) { - SetLastError(0); - if ( !StackWalk64(machine_type, process, thread, &s, myCtx, NULL, SymFunctionTableAccess64, - SymGetModuleBase64, NULL) ) { - break; - } - - if ( s.AddrReturn.Offset == 0 ) { break; } - - buffer[i] = reinterpret_cast(s.AddrReturn.Offset); - size++; - } - - return size; - } - - bool Backtrace::writeTo(ostream& out) const { - const auto process = GetCurrentProcess(); - SYMBOL_INFO* symbol = nullptr; - IMAGEHLP_LINE64* line = nullptr; - bool success = false; - - // Initialize symbol handler for symbol resolution - initialize_symbols(); - - symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 1023 * sizeof(TCHAR)); - if ( !symbol ) goto exit; - symbol->MaxNameLen = 1024; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - DWORD displacement; - line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); - if ( !line ) goto exit; - line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - for ( unsigned i = 0; i < _addrs.size(); i++ ) { - if ( i > 0 ) out << "\r\n"; - out << '\t'; - const auto address = (DWORD64)_addrs[i]; - - // Module basename - void* moduleBase = nullptr; - RtlPcToFileHeader(_addrs[i], &moduleBase); - DWORD64 rva = moduleBase ? (address - (DWORD64)moduleBase) : 0; - char modulePath[4096] = "?"; - if ( moduleBase ) { - int mlen = GetModuleFileNameA((HMODULE)moduleBase, modulePath, sizeof(modulePath)); - if ( mlen > 0 ) { - char* p = modulePath + mlen - 1; - while ( p > modulePath && p[-1] != '\\' ) --p; - memmove(modulePath, p, strlen(p) + 1); - } - } - - // Function name and offset from symbol start - DWORD64 fnOffset = 0; - BOOL symFound = SymFromAddr(process, address, &fnOffset, symbol); - - const char* fn = symFound ? symbol->Name : "?"; - char* cstr = nullptr; - if ( SymGetLineFromAddr64(process, address, &displacement, line) ) { - const char* fileBase = strrchr(line->FileName, '\\'); - fileBase = fileBase ? fileBase + 1 : line->FileName; - asprintf(&cstr, "%2u %-25s %s + %llu (%s:%lu)", i, modulePath, fn, fnOffset, fileBase, - line->LineNumber); - } else { - asprintf(&cstr, "%2u %-25s %s + %llu [0x%llx]", i, modulePath, fn, fnOffset, rva); - } - if ( !cstr ) goto exit; - out << cstr; - free(cstr); - } - success = true; - - exit: - free(symbol); - free(line); - return success; - } - - static char* unmangle(const char* function) { return const_cast(function); } - -} // namespace fleece - -#endif // _MSC_VER - - -#pragma mark - COMMON CODE: - -namespace fleece { + char* unmangle(const char* function); Backtrace::~Backtrace() { free(_symbols); } @@ -519,95 +74,4 @@ namespace fleece { writeTo(out); return out.str(); } - - void Backtrace::writeCrashLog(ostream& out) { - Backtrace bt(4); - auto xp = current_exception(); - if ( xp ) { - out << "Uncaught exception:\n\t"; - try { - rethrow_exception(xp); - } catch ( const exception& x ) { - const char* name = typeid(x).name(); - char* unmangled = unmangle(name); - out << unmangled << ": " << x.what() << "\n"; - if ( unmangled != name ) free(unmangled); - } catch ( ... ) { out << "unknown exception type\n"; } - } - out << "Backtrace:"; - bt.writeTo(out); - } - - void Backtrace::handleTerminate(const function& logger) { - // ---- Code below gets called by C++ runtime on an uncaught exception --- - if ( logger ) { - stringstream out; - writeCrashLog(out); - logger(out.str()); - } else { - cerr << "\n\n******************** C++ fatal error ********************\n"; - writeCrashLog(cerr); - cerr << "\n******************** Now terminating ********************\n"; - } - } } // namespace fleece - -#ifndef _WIN32 -namespace signal_safe { - void write_long(long long value, int fd) { - // NOTE: This function assumes buffer is at least 21 bytes long - if ( value == 0 ) { - write_to_and_stderr(fd, "0", 1); - } else { - if ( value < 0 ) write_to_and_stderr(fd, "-", 1); - char temp[20]; - char* p = temp; - while ( value > 0 ) { - *p++ = static_cast(value % 10 + '0'); - value /= 10; - } - - while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } - } - } - - void write_ulong(unsigned long long value, int fd) { - // NOTE: This function assumes buffer is at least 21 bytes long - if ( value == 0 ) { - write_to_and_stderr(fd, "0", 1); - } else { - char temp[20]; - char* p = temp; - while ( value > 0 ) { - *p++ = static_cast(value % 10 + '0'); - value /= 10; - } - - while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } - } - } - - void write_hex_offset(size_t value, int fd) { - static const char* hexDigits = "0123456789abcdef"; - const int num_digits = sizeof(size_t) * 2; - write_to_and_stderr(fd, "0x", 2); - if ( value == 0 ) { - write_to_and_stderr(fd, "0", 1); - } else { - char temp[num_digits]; - char* p = temp; - while ( value > 0 ) { - *p++ = hexDigits[value & 0xF]; - value >>= 4; - } - - while ( p != temp ) { write_to_and_stderr(fd, --p, 1); } - } - } - - void write_to_and_stderr(int fd, const char* str, size_t n) { - if ( fd != -1 ) write(fd, str, n ? n : strlen(str)); - write(STDERR_FILENO, str, n ? n : strlen(str)); - } -} // namespace signal_safe -#endif diff --git a/cmake/platform_apple.cmake b/cmake/platform_apple.cmake index d65962bb..106cfad7 100644 --- a/cmake/platform_apple.cmake +++ b/cmake/platform_apple.cmake @@ -19,6 +19,8 @@ function(set_source_files) Fleece/Integration/ObjC/MDict+ObjC.mm Fleece/Integration/ObjC/MValue+ObjC.mm Fleece/Support/Backtrace+signals-posix.cc + Fleece/Support/Backtrace+capture-posix.cc + Fleece/Support/Backtrace+capture-darwin.cc ObjC/Encoder+ObjC.mm ObjC/Fleece+CoreFoundation.mm ObjC/slice+CoreFoundation.cc @@ -37,6 +39,8 @@ function(set_base_platform_files) set( ${APPLE_SSS_RESULT} + Fleece/Support/Backtrace+capture-posix.cc + Fleece/Support/Backtrace+capture-darwin.cc ObjC/slice+CoreFoundation.cc ObjC/slice+ObjC.mm PARENT_SCOPE @@ -45,14 +49,14 @@ endfunction() function(set_test_source_files) set(oneValueArgs RESULT) - cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) - if(NOT DEFINED WIN_SSS_RESULT) + cmake_parse_arguments(APPLE_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED APPLE_SSS_RESULT) message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") endif() set_test_source_files_base(RESULT BASE_SRC_FILES) set( - ${WIN_SSS_RESULT} + ${APPLE_SSS_RESULT} ${BASE_SRC_FILES} Tests/ObjCTests.mm Fleece/Integration/ObjC/MTests.mm diff --git a/cmake/platform_linux.cmake b/cmake/platform_linux.cmake index c1eceb28..6f885ec8 100644 --- a/cmake/platform_linux.cmake +++ b/cmake/platform_linux.cmake @@ -44,12 +44,25 @@ function(set_source_files) ${LINUX_SSS_RESULT} ${BASE_SRC_FILES} Fleece/Support/Backtrace+signals-posix.cc + Fleece/Support/Backtrace+capture-posix.cc + Fleece/Support/Backtrace+capture-linux.cc PARENT_SCOPE ) endfunction() function(set_base_platform_files) - # No-op + set(oneValueArgs RESULT) + cmake_parse_arguments(LINUX_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED LINUX_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set( + ${LINUX_SSS_RESULT} + Fleece/Support/Backtrace+capture-posix.cc + Fleece/Support/Backtrace+capture-linux.cc + PARENT_SCOPE + ) endfunction() function(set_test_source_files) diff --git a/cmake/platform_win.cmake b/cmake/platform_win.cmake index cf38df29..8286cbb1 100644 --- a/cmake/platform_win.cmake +++ b/cmake/platform_win.cmake @@ -14,12 +14,23 @@ function(set_source_files) MSVC/vasprintf-msvc.c MSVC/asprintf.c Fleece/Support/Backtrace+signals-win32.cc + Fleece/Support/Backtrace+capture-win32.cc PARENT_SCOPE ) endfunction() function(set_base_platform_files) - # No-op + set(oneValueArgs RESULT) + cmake_parse_arguments(WIN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED WIN_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set( + ${WIN_SSS_RESULT} + Fleece/Support/Backtrace+capture-win32.cc + PARENT_SCOPE + ) endfunction() function(set_test_source_files) From 5684a2793c9df1d6fea913cfa08603613e927e00 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 13 Mar 2026 09:21:09 +0900 Subject: [PATCH 09/13] Various fixups --- Fleece/Support/Backtrace+capture-darwin.cc | 15 ++++++ Fleece/Support/Backtrace+capture-linux.cc | 58 +++++++++++----------- Fleece/Support/Backtrace+capture-posix.cc | 7 +++ Fleece/Support/Backtrace+capture-win32.cc | 19 +++++++ Fleece/Support/Backtrace.cc | 11 ---- Fleece/Support/Backtrace.hh | 10 ++-- 6 files changed, 73 insertions(+), 47 deletions(-) diff --git a/Fleece/Support/Backtrace+capture-darwin.cc b/Fleece/Support/Backtrace+capture-darwin.cc index 0fd6f1eb..1b7c60a5 100644 --- a/Fleece/Support/Backtrace+capture-darwin.cc +++ b/Fleece/Support/Backtrace+capture-darwin.cc @@ -6,6 +6,21 @@ #include namespace fleece { + Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { + frameInfo frame = {}; + Dl_info info; + if ( dladdr(addr, &info) ) { + frame.pc = stack_top ? addr : addr - 1; + frame.offset = (size_t)frame.pc - (size_t)info.dli_saddr; + frame.function = info.dli_sname; + frame.library = info.dli_fname; + const char* slash = strrchr(frame.library, '/'); + if ( slash ) frame.library = slash + 1; + } + + return frame; + } + int Backtrace::raw_capture(void** buffer, int max, void* context) { if ( max == 0 ) return 0; return backtrace(buffer, max); diff --git a/Fleece/Support/Backtrace+capture-linux.cc b/Fleece/Support/Backtrace+capture-linux.cc index 041c773a..71d772b4 100644 --- a/Fleece/Support/Backtrace+capture-linux.cc +++ b/Fleece/Support/Backtrace+capture-linux.cc @@ -27,6 +27,35 @@ namespace fleece { return _URC_NO_REASON; } + Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { + frameInfo frame = {}; + frame.pc = addr; + + // dladdr gives us the library name regardless of symbol visibility, + // and a fallback saddr/sname for exported symbols. + Dl_info dlInfo = {}; + dladdr(frame.pc, &dlInfo); + if ( dlInfo.dli_fname ) { + frame.library = dlInfo.dli_fname; + if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; + } + + uintptr_t pc = reinterpret_cast(frame.pc); + if ( !stack_top ) pc -= 1; // return address → call site + if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); + auto resolved = backtraceResolve(pc); + frame.function = resolved.function; + frame.file = resolved.file; + frame.line = resolved.line; + // Prefer symval from libbacktrace (works for hidden-visibility symbols); + // fall back to dladdr's saddr for exported symbols. + if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; + else if ( dlInfo.dli_saddr ) + frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; + + return frame; + } + int Backtrace::raw_capture(void** buffer, int max, void* context) { BacktraceState state = {buffer, buffer + max}; _Unwind_Backtrace(unwindCallback, &state); @@ -74,35 +103,6 @@ namespace fleece { return info; } - Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { - frameInfo frame = {}; - frame.pc = addr; - - // dladdr gives us the library name regardless of symbol visibility, - // and a fallback saddr/sname for exported symbols. - Dl_info dlInfo = {}; - dladdr(frame.pc, &dlInfo); - if ( dlInfo.dli_fname ) { - frame.library = dlInfo.dli_fname; - if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; - } - - uintptr_t pc = reinterpret_cast(frame.pc); - if ( !stack_top ) pc -= 1; // return address → call site - if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); - auto resolved = backtraceResolve(pc); - frame.function = resolved.function; - frame.file = resolved.file; - frame.line = resolved.line; - // Prefer symval from libbacktrace (works for hidden-visibility symbols); - // fall back to dladdr's saddr for exported symbols. - if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; - else if ( dlInfo.dli_saddr ) - frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; - - return frame; - } - const char* Backtrace::getSymbol(unsigned i) const { // delegate to getFrame which already uses libbacktrace static thread_local string tName; diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc index 8d0ee349..9db8d7e5 100644 --- a/Fleece/Support/Backtrace+capture-posix.cc +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -10,6 +10,8 @@ namespace fleece { using namespace std; using namespace signal_safe; + Backtrace::frameInfo getFrame(void* addr, bool isTopFrame); + static constexpr struct { const char *old, *nuu; } kAbbreviations[] = { @@ -44,6 +46,11 @@ namespace fleece { return (char*)function; } + Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { + precondition(i < _addrs.size()); + return getFrame(_addrs[i], i == 0); + } + bool Backtrace::writeTo(ostream& out) const { for ( unsigned i = 0; i < unsigned(_addrs.size()); ++i ) { if ( i > 0 ) out << '\n'; diff --git a/Fleece/Support/Backtrace+capture-win32.cc b/Fleece/Support/Backtrace+capture-win32.cc index 8fb8479c..a0d8f1a5 100644 --- a/Fleece/Support/Backtrace+capture-win32.cc +++ b/Fleece/Support/Backtrace+capture-win32.cc @@ -3,9 +3,28 @@ #endif #include "Backtrace.hh" +#include "asprintf.h" #include +#include +#include + +#pragma comment(lib, "Dbghelp.lib") namespace fleece { + using namespace std; + + static void initialize_symbols() { + static bool initialized = false; + if ( !initialized ) { + auto process = GetCurrentProcess(); + SymInitialize(process, nullptr, TRUE); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + initialized = true; + } + } + char* unmangle(const char* function) { return const_cast(function); } int Backtrace::raw_capture(void** buffer, int max, void* context) { diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 7a5dc987..5a208f93 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -21,17 +21,6 @@ namespace fleece { using namespace std; - using namespace signal_safe; - - Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { - precondition(i < _addrs.size()); - return getFrame(_addrs[i], i == 0); - } - - -} // namespace fleece - -namespace fleece { char* unmangle(const char* function); diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index 88bc39ff..cf617a7b 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -93,19 +93,15 @@ namespace fleece { /// The number of stack frames captured. unsigned size() const { return (unsigned)_addrs.size(); } - /// Returns info about a stack frame. 0 is the top. - frameInfo getFrame(unsigned) const; - - static frameInfo getFrame(const void* pc, bool stack_top); - - frameInfo operator[](unsigned i) { return getFrame(i); } - private: void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); +#ifndef _WIN32 void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); const char* getSymbol(unsigned i) const; static void writeCrashLog(std::ostream&); static void handleTerminate(const std::function& logger); + frameInfo getFrame(unsigned) const; +#endif std::vector _addrs; // Array of PCs in backtrace, top first mutable char** _symbols{nullptr}; From 23703f1cf4a4c3132b7a71ce85c048132abc7ce7 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 13 Mar 2026 09:55:19 +0900 Subject: [PATCH 10/13] Bouncing between machines for checking that I didn't forget stuff --- Fleece/Support/Backtrace+capture-darwin.cc | 50 +++++++++++++++---- Fleece/Support/Backtrace+capture-linux.cc | 58 +++++++++++----------- Fleece/Support/Backtrace+capture-posix.cc | 7 +-- 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/Fleece/Support/Backtrace+capture-darwin.cc b/Fleece/Support/Backtrace+capture-darwin.cc index 1b7c60a5..dcacf6e6 100644 --- a/Fleece/Support/Backtrace+capture-darwin.cc +++ b/Fleece/Support/Backtrace+capture-darwin.cc @@ -4,18 +4,31 @@ #include "Backtrace.hh" #include +#include +#include "betterassert.hh" namespace fleece { Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { - frameInfo frame = {}; - Dl_info info; - if ( dladdr(addr, &info) ) { - frame.pc = stack_top ? addr : addr - 1; - frame.offset = (size_t)frame.pc - (size_t)info.dli_saddr; - frame.function = info.dli_sname; - frame.library = info.dli_fname; - const char* slash = strrchr(frame.library, '/'); - if ( slash ) frame.library = slash + 1; + Backtrace::frameInfo frame = {}; + frame.pc = addr; + + Dl_info info = {}; + if (!dladdr(addr, &info)) { + return frame; + } + + if (info.dli_fname) { + frame.library = info.dli_fname; + if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; + } + + frame.function = info.dli_sname; + if ( info.dli_saddr ) frame.offset = reinterpret_cast(frame.pc) - reinterpret_cast(info.dli_saddr); + + if ( info.dli_fbase ) { + auto pc = reinterpret_cast(frame.pc); + if ( !stack_top ) pc -= 1; + frame.imageOffset = pc - reinterpret_cast(info.dli_fbase); } return frame; @@ -25,4 +38,23 @@ namespace fleece { if ( max == 0 ) return 0; return backtrace(buffer, max); } + + const char* Backtrace::getSymbol(unsigned i) const { + precondition(i < _addrs.size()); + if (!_symbols) + _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); + + if (_symbols) { + const char* s = _symbols[i]; + + // Skip line number and whitespace: + while (*s && isdigit(*s)) + ++s; + while (*s && isspace(*s)) + ++s; + + return s; + } + return nullptr; + } } // namespace fleece diff --git a/Fleece/Support/Backtrace+capture-linux.cc b/Fleece/Support/Backtrace+capture-linux.cc index 71d772b4..7606478a 100644 --- a/Fleece/Support/Backtrace+capture-linux.cc +++ b/Fleece/Support/Backtrace+capture-linux.cc @@ -27,35 +27,6 @@ namespace fleece { return _URC_NO_REASON; } - Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { - frameInfo frame = {}; - frame.pc = addr; - - // dladdr gives us the library name regardless of symbol visibility, - // and a fallback saddr/sname for exported symbols. - Dl_info dlInfo = {}; - dladdr(frame.pc, &dlInfo); - if ( dlInfo.dli_fname ) { - frame.library = dlInfo.dli_fname; - if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; - } - - uintptr_t pc = reinterpret_cast(frame.pc); - if ( !stack_top ) pc -= 1; // return address → call site - if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); - auto resolved = backtraceResolve(pc); - frame.function = resolved.function; - frame.file = resolved.file; - frame.line = resolved.line; - // Prefer symval from libbacktrace (works for hidden-visibility symbols); - // fall back to dladdr's saddr for exported symbols. - if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; - else if ( dlInfo.dli_saddr ) - frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; - - return frame; - } - int Backtrace::raw_capture(void** buffer, int max, void* context) { BacktraceState state = {buffer, buffer + max}; _Unwind_Backtrace(unwindCallback, &state); @@ -103,6 +74,35 @@ namespace fleece { return info; } + Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { + Backtrace::frameInfo frame = {}; + frame.pc = addr; + + // dladdr gives us the library name regardless of symbol visibility, + // and a fallback saddr/sname for exported symbols. + Dl_info dlInfo = {}; + dladdr(frame.pc, &dlInfo); + if ( dlInfo.dli_fname ) { + frame.library = dlInfo.dli_fname; + if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; + } + + uintptr_t pc = reinterpret_cast(frame.pc); + if ( !stack_top ) pc -= 1; // return address → call site + if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); + auto resolved = backtraceResolve(pc); + frame.function = resolved.function; + frame.file = resolved.file; + frame.line = resolved.line; + // Prefer symval from libbacktrace (works for hidden-visibility symbols); + // fall back to dladdr's saddr for exported symbols. + if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; + else if ( dlInfo.dli_saddr ) + frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; + + return frame; + } + const char* Backtrace::getSymbol(unsigned i) const { // delegate to getFrame which already uses libbacktrace static thread_local string tName; diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc index 9db8d7e5..2e12f177 100644 --- a/Fleece/Support/Backtrace+capture-posix.cc +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -5,12 +5,13 @@ #include "Backtrace.hh" #include // abi::__cxa_demangle() #include +#include "betterassert.hh" namespace fleece { using namespace std; using namespace signal_safe; - Backtrace::frameInfo getFrame(void* addr, bool isTopFrame); + Backtrace::frameInfo getFrame(const void* addr, bool isTopFrame); static constexpr struct { const char *old, *nuu; @@ -48,7 +49,7 @@ namespace fleece { Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { precondition(i < _addrs.size()); - return getFrame(_addrs[i], i == 0); + return fleece::getFrame(_addrs[i], i == 0); } bool Backtrace::writeTo(ostream& out) const { @@ -98,7 +99,7 @@ namespace fleece { for ( unsigned i = 0; i < unsigned(size); ++i ) { if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); write_to_and_stderr(fd, "\t", 1); - auto frame = getFrame(addresses[i], i == 0); + auto frame = fleece::getFrame(addresses[i], i == 0); const char* lib = frame.library ? frame.library : "?"; const char* name = frame.function; From fbe300e1cff6ef37323c0eb142fd9a629175565b Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 13 Mar 2026 12:52:44 +0900 Subject: [PATCH 11/13] Suppress compiler warning --- Fleece/Support/Backtrace+capture-posix.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc index 2e12f177..25021552 100644 --- a/Fleece/Support/Backtrace+capture-posix.cc +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -191,7 +191,17 @@ namespace signal_safe { } void write_to_and_stderr(int fd, const char* str, size_t n) { +#ifdef __clang__ +// There is nothing we can do at this point anyway if the return fails +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#endif + if ( fd != -1 ) write(fd, str, n ? n : strlen(str)); write(STDERR_FILENO, str, n ? n : strlen(str)); + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif } } // namespace signal_safe From f404937aa778c6fcbab4d0203bdb7aca7fc96c44 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Tue, 24 Mar 2026 07:47:38 +0900 Subject: [PATCH 12/13] PR review feedback (mine and others) and formatting --- Fleece/Support/Backtrace+capture-darwin.cc | 26 +++++++--------- Fleece/Support/Backtrace+capture-linux.cc | 21 ++++++++++--- Fleece/Support/Backtrace+capture-posix.cc | 6 ++-- Fleece/Support/Backtrace+signals-posix.cc | 35 +++++++++++++++++----- Fleece/Support/Backtrace+signals-win32.cc | 5 ++-- Fleece/Support/Backtrace.hh | 18 ++++++----- 6 files changed, 70 insertions(+), 41 deletions(-) diff --git a/Fleece/Support/Backtrace+capture-darwin.cc b/Fleece/Support/Backtrace+capture-darwin.cc index dcacf6e6..95e1eff0 100644 --- a/Fleece/Support/Backtrace+capture-darwin.cc +++ b/Fleece/Support/Backtrace+capture-darwin.cc @@ -8,22 +8,21 @@ #include "betterassert.hh" namespace fleece { - Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { + Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { Backtrace::frameInfo frame = {}; - frame.pc = addr; + frame.pc = addr; - Dl_info info = {}; - if (!dladdr(addr, &info)) { - return frame; - } + Dl_info info = {}; + if ( !dladdr(addr, &info) ) { return frame; } - if (info.dli_fname) { + if ( info.dli_fname ) { frame.library = info.dli_fname; if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; } frame.function = info.dli_sname; - if ( info.dli_saddr ) frame.offset = reinterpret_cast(frame.pc) - reinterpret_cast(info.dli_saddr); + if ( info.dli_saddr ) + frame.offset = reinterpret_cast(frame.pc) - reinterpret_cast(info.dli_saddr); if ( info.dli_fbase ) { auto pc = reinterpret_cast(frame.pc); @@ -41,17 +40,14 @@ namespace fleece { const char* Backtrace::getSymbol(unsigned i) const { precondition(i < _addrs.size()); - if (!_symbols) - _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); + if ( !_symbols ) _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); - if (_symbols) { + if ( _symbols ) { const char* s = _symbols[i]; // Skip line number and whitespace: - while (*s && isdigit(*s)) - ++s; - while (*s && isspace(*s)) - ++s; + while ( *s && isdigit(*s) ) ++s; + while ( *s && isspace(*s) ) ++s; return s; } diff --git a/Fleece/Support/Backtrace+capture-linux.cc b/Fleece/Support/Backtrace+capture-linux.cc index 7606478a..bbbce4e8 100644 --- a/Fleece/Support/Backtrace+capture-linux.cc +++ b/Fleece/Support/Backtrace+capture-linux.cc @@ -1,5 +1,5 @@ #if !defined(__linux__) && !defined(__ANDROID__) -# error "This implementation is meant for Linux and Android only +# error "This implementation is meant for Linux and Android only" #endif #include "Backtrace.hh" @@ -74,19 +74,31 @@ namespace fleece { return info; } - Backtrace::frameInfo getFrame(const void* addr, bool stack_top) { + Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { Backtrace::frameInfo frame = {}; - frame.pc = addr; + frame.pc = addr; // dladdr gives us the library name regardless of symbol visibility, // and a fallback saddr/sname for exported symbols. Dl_info dlInfo = {}; - dladdr(frame.pc, &dlInfo); + if ( !dladdr(frame.pc, &dlInfo) ) { return frame; } + if ( dlInfo.dli_fname ) { frame.library = dlInfo.dli_fname; if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1; } +#ifdef __ANDROID__ + frame.function = info.dli_sname; + if ( info.dli_saddr ) + frame.offset = reinterpret_cast(frame.pc) - reinterpret_cast(info.dli_saddr); + + if ( info.dli_fbase ) { + auto pc = reinterpret_cast(frame.pc); + if ( !stack_top ) pc -= 1; + frame.imageOffset = pc - reinterpret_cast(info.dli_fbase); + } +#else uintptr_t pc = reinterpret_cast(frame.pc); if ( !stack_top ) pc -= 1; // return address → call site if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast(dlInfo.dli_fbase); @@ -99,6 +111,7 @@ namespace fleece { if ( resolved.symval ) frame.offset = reinterpret_cast(frame.pc) - resolved.symval; else if ( dlInfo.dli_saddr ) frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr; +#endif return frame; } diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc index 25021552..d2eea5dc 100644 --- a/Fleece/Support/Backtrace+capture-posix.cc +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -11,8 +11,6 @@ namespace fleece { using namespace std; using namespace signal_safe; - Backtrace::frameInfo getFrame(const void* addr, bool isTopFrame); - static constexpr struct { const char *old, *nuu; } kAbbreviations[] = { @@ -49,7 +47,7 @@ namespace fleece { Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { precondition(i < _addrs.size()); - return fleece::getFrame(_addrs[i], i == 0); + return getFrame(_addrs[i], i == 0); } bool Backtrace::writeTo(ostream& out) const { @@ -99,7 +97,7 @@ namespace fleece { for ( unsigned i = 0; i < unsigned(size); ++i ) { if ( i > 0 ) write_to_and_stderr(fd, "\n", 1); write_to_and_stderr(fd, "\t", 1); - auto frame = fleece::getFrame(addresses[i], i == 0); + auto frame = getFrame(addresses[i], i == 0); const char* lib = frame.library ? frame.library : "?"; const char* name = frame.function; diff --git a/Fleece/Support/Backtrace+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc index c7b00f48..cfb40178 100644 --- a/Fleece/Support/Backtrace+signals-posix.cc +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -1,10 +1,12 @@ #include "Backtrace.hh" #include +#include #include #include #include #include #include +#include #ifdef __APPLE__ # include #else @@ -26,12 +28,24 @@ namespace fleece { }; } + static struct sigaction defaultActionFor(int signal) { + if ( default_actions().find(signal) == default_actions().end() ) { + struct sigaction sa{}; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + return sa; + } else { + return default_actions()[signal]; + } + } + BacktraceSignalHandlerPosix() { constexpr size_t stack_size = 1024 * 1024 * 8; - _stack_content = malloc(stack_size); + _stack_content = make_unique(stack_size); if ( _stack_content ) { stack_t ss; - ss.ss_sp = _stack_content; + ss.ss_sp = _stack_content.get(); ss.ss_size = stack_size; ss.ss_flags = 0; sigaltstack(&ss, nullptr); @@ -53,7 +67,9 @@ namespace fleece { # pragma clang diagnostic pop #endif - sigaction(signal_vector[i], &action, nullptr); + struct sigaction old_action; + sigaction(signal_vector[i], &action, &old_action); + default_actions()[signal_vector[i]] = old_action; } } @@ -63,7 +79,12 @@ namespace fleece { BacktraceSignalHandlerPosix& operator=(BacktraceSignalHandlerPosix&&) = delete; private: - void* _stack_content; + unique_ptr _stack_content; + + static unordered_map& default_actions() { + static unordered_map da; + return da; + } static string& violation_type() { static string type; @@ -114,12 +135,10 @@ namespace fleece { } [[noreturn]] static void sig_handler(int signo, siginfo_t* info, void* context) { - const char* name = strsignal(signo); - violation_type() = name ? name : "Signal " + to_string(signo); - crash_handler_immediate(info, context); - signal(signo, SIG_DFL); + auto default_action = BacktraceSignalHandlerPosix::defaultActionFor(signo); + sigaction(signo, &default_action, nullptr); raise(signo); _exit(128 + signo); diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc index a570e310..087d0834 100644 --- a/Fleece/Support/Backtrace+signals-win32.cc +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -14,7 +14,7 @@ namespace fleece { SetUnhandledExceptionFilter(crash_handler); previous_sig_handler() = signal(SIGABRT, signal_handler); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - previous_purecall_handler() = _set_purecall_handler(&terminator); + previous_purecall_handler() = _set_purecall_handler(&terminator); previous_invalid_parameter_handler() = _set_invalid_parameter_handler(&invalid_parameter_handler); } @@ -97,7 +97,8 @@ namespace fleece { } if ( sCrashStream ) { - sCrashStream << "\n\n******************** Process Crash: " << violation_type() << " ********************\n"; + sCrashStream << "\n\n******************** Process Crash: " << violation_type() + << " ********************\n"; bt->writeTo(sCrashStream); sCrashStream << "\n******************** Now terminating ********************\n"; } diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index cf617a7b..ea946bb8 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -21,8 +21,8 @@ #include #include #ifndef _WIN32 -#include -#include +# include +# include namespace signal_safe { void write_long(long long value, int fd); @@ -94,13 +94,15 @@ namespace fleece { unsigned size() const { return (unsigned)_addrs.size(); } private: - void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); + void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); #ifndef _WIN32 - void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); - const char* getSymbol(unsigned i) const; - static void writeCrashLog(std::ostream&); - static void handleTerminate(const std::function& logger); - frameInfo getFrame(unsigned) const; + void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); + const char* getSymbol(unsigned i) const; + static void writeCrashLog(std::ostream&); + static void handleTerminate(const std::function& logger); + frameInfo getFrame(unsigned) const; + static frameInfo getFrame(const void* pc, bool stack_top); + static char* unmangle(const char* function NONNULL); #endif std::vector _addrs; // Array of PCs in backtrace, top first From c68f30f77341add18132240312cf293e4748e894 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Tue, 24 Mar 2026 07:50:27 +0900 Subject: [PATCH 13/13] Remove unused function --- Fleece/Support/Backtrace+capture-posix.cc | 2 +- Fleece/Support/Backtrace.hh | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Fleece/Support/Backtrace+capture-posix.cc b/Fleece/Support/Backtrace+capture-posix.cc index d2eea5dc..53ca5787 100644 --- a/Fleece/Support/Backtrace+capture-posix.cc +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -1,5 +1,5 @@ #ifdef _WIN32 -# error "This implementation is not meant to be used on Windows +# error "This implementation is not meant to be used on Windows" #endif #include "Backtrace.hh" diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index ea946bb8..47241ad5 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -52,10 +52,6 @@ namespace fleece { [[nodiscard]] static std::shared_ptr capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); - /// Captures a backtrace from the given starting point and returns a shared pointer to the instance. - [[nodiscard]] static std::shared_ptr capture(void* from, unsigned maxFrames = 50, - void* context = nullptr); - static int raw_capture(void** buffer, int max, void* context = nullptr); /// Captures a backtrace, unless maxFrames is zero. @@ -96,7 +92,6 @@ namespace fleece { private: void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); #ifndef _WIN32 - void _capture(void* from, unsigned maxFrames = 50, void* context = nullptr); const char* getSymbol(unsigned i) const; static void writeCrashLog(std::ostream&); static void handleTerminate(const std::function& logger);