diff --git a/Fleece/Support/Backtrace+capture-darwin.cc b/Fleece/Support/Backtrace+capture-darwin.cc new file mode 100644 index 00000000..95e1eff0 --- /dev/null +++ b/Fleece/Support/Backtrace+capture-darwin.cc @@ -0,0 +1,56 @@ +#ifndef __APPLE__ +# error This file is only for Darwin (macOS/iOS) platforms +#endif + +#include "Backtrace.hh" +#include +#include +#include "betterassert.hh" + +namespace fleece { + Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) { + 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; + } + + int Backtrace::raw_capture(void** buffer, int max, void* context) { + 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 new file mode 100644 index 00000000..bbbce4e8 --- /dev/null +++ b/Fleece/Support/Backtrace+capture-linux.cc @@ -0,0 +1,143 @@ +#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) { + 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 = {}; + 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); + 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; +#endif + + 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..53ca5787 --- /dev/null +++ b/Fleece/Support/Backtrace+capture-posix.cc @@ -0,0 +1,205 @@ +#ifdef _WIN32 +# error "This implementation is not meant to be used on Windows" +#endif + +#include "Backtrace.hh" +#include // abi::__cxa_demangle() +#include +#include "betterassert.hh" + +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; + } + + 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'; + 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) { +#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 diff --git a/Fleece/Support/Backtrace+capture-win32.cc b/Fleece/Support/Backtrace+capture-win32.cc new file mode 100644 index 00000000..a0d8f1a5 --- /dev/null +++ b/Fleece/Support/Backtrace+capture-win32.cc @@ -0,0 +1,148 @@ +#ifndef _WIN32 +# error "This implementation is only for Windows" +#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) { + 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+signals-posix.cc b/Fleece/Support/Backtrace+signals-posix.cc new file mode 100644 index 00000000..cfb40178 --- /dev/null +++ b/Fleece/Support/Backtrace+signals-posix.cc @@ -0,0 +1,161 @@ +#include "Backtrace.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +# include +#else +# include +#endif + +namespace fleece { + using namespace std; + using namespace signal_safe; + + 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 + }; + } + + 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 = make_unique(stack_size); + if ( _stack_content ) { + stack_t ss; + ss.ss_sp = _stack_content.get(); + 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 + + struct sigaction old_action; + sigaction(signal_vector[i], &action, &old_action); + default_actions()[signal_vector[i]] = old_action; + } + } + + BacktraceSignalHandlerPosix(const BacktraceSignalHandlerPosix&) = delete; + BacktraceSignalHandlerPosix& operator=(const BacktraceSignalHandlerPosix&) = delete; + BacktraceSignalHandlerPosix(BacktraceSignalHandlerPosix&&) = delete; + BacktraceSignalHandlerPosix& operator=(BacktraceSignalHandlerPosix&&) = delete; + + private: + unique_ptr _stack_content; + + static unordered_map& default_actions() { + static unordered_map da; + return da; + } + + 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(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 { + write_to_and_stderr(sLogFD, "Signal: ", 8); + write_long(info->si_signo, sLogFD); + } + + 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 + 3, n - 3, sLogFD); + write_to_and_stderr(sLogFD, "\n******************** Now terminating ********************\n", 59); + if ( sLogFD != -1 ) { + fsync(sLogFD); + close(sLogFD); + } + } + + [[noreturn]] static void sig_handler(int signo, siginfo_t* info, void* context) { + crash_handler_immediate(info, context); + + auto default_action = BacktraceSignalHandlerPosix::defaultActionFor(signo); + sigaction(signo, &default_action, nullptr); + raise(signo); + + _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 + +#if !TARGET_OS_IOS +[[maybe_unused]] +static fleece::BacktraceSignalHandlerPosix handler; +#endif diff --git a/Fleece/Support/Backtrace+signals-win32.cc b/Fleece/Support/Backtrace+signals-win32.cc new file mode 100644 index 00000000..087d0834 --- /dev/null +++ b/Fleece/Support/Backtrace+signals-win32.cc @@ -0,0 +1,120 @@ +#include "Backtrace.hh" +#include +#include +#include +#include +#include + +namespace fleece { + using namespace std; + + class BacktraceSignalHandlerWin32 : public BacktraceSignalHandler { + public: + BacktraceSignalHandlerWin32() { + 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_invalid_parameter_handler() = _set_invalid_parameter_handler(&invalid_parameter_handler); + } + + BacktraceSignalHandlerWin32(const BacktraceSignalHandlerWin32&) = delete; + BacktraceSignalHandlerWin32& operator=(const BacktraceSignalHandlerWin32&) = delete; + BacktraceSignalHandlerWin32(BacktraceSignalHandlerWin32&&) = delete; + 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; + } + + 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); + signal(sig, previous_sig_handler()); + raise(sig); + abort(); + } + + 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(); + } + + NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) { + 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) { + shared_ptr bt; + if ( ct != nullptr ) { + bt = Backtrace::capture(0u, 32, ct); + } else { + bt = Backtrace::capture(skip, 32 + skip); + } + + 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 + +static fleece::BacktraceSignalHandlerWin32 handler; diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index 2c95f867..5a208f93 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -11,7 +11,6 @@ // #include "Backtrace.hh" -#include #include #include #include @@ -20,419 +19,48 @@ #include #include "betterassert.hh" - -#ifndef _MSC_VER -#pragma mark - UNIX IMPLEMENTATION: - -#include // dladdr() - -#ifndef __ANDROID__ - #define HAVE_EXECINFO - #include // backtrace(), backtrace_symbols() -#else - #include // free - #include // _Unwind_Backtrace(), etc. -#endif - -#ifndef _MSC_VER - #include // abi::__cxa_demangle() - #define HAVE_UNMANGLE -#endif - - namespace fleece { using namespace std; + char* unmangle(const char* function); -#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; - size_t unmangledLen; - 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; - } - - return frame; - } - - - const char* Backtrace::getSymbol(unsigned i) const { -#if !defined(__ANDROID__) - precondition(i < _addrs.size()); - 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 - return s; - } -#endif - return nullptr; - } + Backtrace::~Backtrace() { free(_symbols); } - - // 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::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; - 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); - } else { - len = asprintf(&cstr, "%2u %p", i, _addrs[i]); - } - - 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; - } - - - std::string FunctionName(const void *pc) { - Dl_info info = {}; - dladdr(pc, &info); - if (info.dli_sname) - return Unmangle(info.dli_sname); - else - return ""; - } - -} - - -#else // _MSC_VER -#pragma mark - WINDOWS IMPLEMENTATION: - -#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 inline int backtrace(void** buffer, int max) { - return (int)CaptureStackBackTrace(0, (DWORD)max, buffer, nullptr); - } - - - 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; - 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]; - 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); - } else { - asprintf(&cstr, "at %s, address 0x%0llX", - symbol->Name, symbol->Address); - } - if (!cstr) - goto exit; - out << cstr; - free(cstr); - } - success = true; - - exit: - free(symbol); - free(line); - SymCleanup(process); - 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; - } - -} - -#endif // _MSC_VER - - -#pragma mark - COMMON CODE: - - -namespace fleece { - - 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); + Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames, void* context) { + if ( maxFrames > 0 ) _capture(skipFrames + 1, maxFrames, context); } - - 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) { + 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::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) { - 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::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 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"; - } - // Chain to old handler: - sOldHandler(); - // Just in case the old handler doesn't abort: - abort(); - // ---- End of handler ---- - }); - }); - } - -} +} // namespace fleece diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index d2f6f1f8..47241ad5 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -18,19 +18,48 @@ #include #include #include +#include +#include +#ifndef _WIN32 +# 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 +#endif namespace fleece { + class BacktraceSignalHandler { + public: + 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. */ 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); + + 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 - 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(); @@ -40,54 +69,49 @@ 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; // 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. - 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);} - - /// 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); - const char* getSymbol(unsigned i) const; - [[nodiscard]] char* printFrame(unsigned i) const; - static void writeCrashLog(std::ostream&); - - std::vector _addrs; // Array of PCs in backtrace, top first - mutable char** _symbols { nullptr }; + unsigned size() const { return (unsigned)_addrs.size(); } + + private: + void _capture(unsigned skipFrames = 0, unsigned maxFrames = 50, void* context = nullptr); +#ifndef _WIN32 + 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 + 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..106cfad7 100644 --- a/cmake/platform_apple.cmake +++ b/cmake/platform_apple.cmake @@ -18,6 +18,9 @@ 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 + Fleece/Support/Backtrace+capture-posix.cc + Fleece/Support/Backtrace+capture-darwin.cc ObjC/Encoder+ObjC.mm ObjC/Fleece+CoreFoundation.mm ObjC/slice+CoreFoundation.cc @@ -36,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 @@ -44,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 d6da2d60..6f885ec8 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) @@ -8,11 +40,29 @@ 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 + 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) @@ -36,6 +86,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 diff --git a/cmake/platform_win.cmake b/cmake/platform_win.cmake index 4646c100..8286cbb1 100644 --- a/cmake/platform_win.cmake +++ b/cmake/platform_win.cmake @@ -13,12 +13,24 @@ function(set_source_files) ${BASE_SRC_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)