From 5a546c1e1a87eacec65aaca150d1bfe1b0ba263b Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 24 Oct 2024 14:34:07 -0700 Subject: [PATCH] Use libunwind for stack unwinding The backtrace() interface is not available on Alpine. ([#245]) --- .github/workflows/release.yml | 2 ++ Makefile.am | 4 ++-- README.md | 4 ++++ Util/BacktraceException.hpp | 33 +++++++++++++++++++++++++++------ configure.ac | 16 ++++++++++++++++ flake.nix | 1 + 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4606385b8..2c0d99b17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,8 @@ jobs: automake \ curl \ libcurl4-openssl-dev \ + libunwind \ + libunwind-dev \ git \ libev-dev \ libtool \ diff --git a/Makefile.am b/Makefile.am index 2d7878e7a..7f31dff66 100644 --- a/Makefile.am +++ b/Makefile.am @@ -571,8 +571,8 @@ EXTRA_DIST = \ generate_commit_hash.sh \ commit_hash.h -AM_CXXFLAGS = -Wall -Werror $(PTHREAD_CFLAGS) $(libev_CFLAGS) $(SQLITE3_CFLAGS) $(CURL_CFLAGS) $(CLBOSS_CXXFLAGS) -LDADD = libclboss.la $(PTHREAD_LIBS) $(libev_LIBS) $(SQLITE3_LIBS) $(CURL_LIBS) +AM_CXXFLAGS = -Wall -Werror $(PTHREAD_CFLAGS) $(libev_CFLAGS) $(SQLITE3_CFLAGS) $(CURL_CFLAGS) $(CLBOSS_CXXFLAGS) $(LIBUNWIND_CFLAGS) +LDADD = libclboss.la $(PTHREAD_LIBS) $(libev_LIBS) $(SQLITE3_LIBS) $(CURL_LIBS) $(LIBUNWIND_LIBS) ACLOCAL_AMFLAGS = -I m4 diff --git a/README.md b/README.md index 60c58cd6b..9fbb3ea7a 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Debian-derived systems: * `libev-dev` * `libcurl4-gnutls-dev` * `libsqlite3-dev` +* `libunwind-dev` RPM-dervied : * `groupinstall "Development Tools"` @@ -51,6 +52,7 @@ RPM-dervied : * `libev-devel` * `libcurl-devel` * `libsqlite3x-devel` +* `libunwind-devel` Alpine: * `build-base` @@ -58,6 +60,7 @@ Alpine: * `libev-dev` * `curl-dev` * `sqlite-dev` +* `libunwind-dev` Equivalent packages have a good probability of existing in non-Debian-derived distributions as well. @@ -132,6 +135,7 @@ release: pkg install libev pkg install pkgconf pkg install sqlite3 + pkg install libunwind In addition, you have to use `gmake` for building, not the system `make`, as the included `libsecp256k1` requires diff --git a/Util/BacktraceException.hpp b/Util/BacktraceException.hpp index 6989df147..a923fb4ca 100644 --- a/Util/BacktraceException.hpp +++ b/Util/BacktraceException.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H # include"config.h" @@ -16,6 +17,9 @@ extern std::string g_argv0; +#define UNW_LOCAL_ONLY +#include + namespace Util { /** class Util::BacktraceException @@ -54,23 +58,40 @@ class BacktraceException : public T { static constexpr size_t MAX_FRAMES = 100; mutable bool formatted_ = false; mutable std::string full_message_; - mutable void* backtrace_addresses_[MAX_FRAMES]; + mutable std::vector backtrace_addresses_; mutable size_t stack_depth; void capture_backtrace() { - memset(backtrace_addresses_, 0, sizeof(backtrace_addresses_)); - stack_depth = backtrace(backtrace_addresses_, sizeof(backtrace_addresses_) / sizeof(void*)); + unw_cursor_t cursor; + unw_context_t context; + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + while (unw_step(&cursor) > 0 && backtrace_addresses_.size() < MAX_FRAMES) { + unw_word_t ip; + unw_get_reg(&cursor, UNW_REG_IP, &ip); + backtrace_addresses_.push_back(ip); + } + + stack_depth = backtrace_addresses_.size(); } std::string format_backtrace() const { - char** symbols = backtrace_symbols(backtrace_addresses_, stack_depth); + // Create an array of pointers compatible with `backtrace_symbols`. + std::vector pointers(backtrace_addresses_.size()); + for (size_t i = 0; i < backtrace_addresses_.size(); ++i) { + pointers[i] = reinterpret_cast(backtrace_addresses_[i]); + } + + // Use `backtrace_symbols` with the adapted pointers. + char** symbols = backtrace_symbols(pointers.data(), stack_depth); std::ostringstream oss; for (size_t i = 0; i < stack_depth; ++i) { oss << '#' << std::left << std::setfill(' ') << std::setw(2) << i << ' '; - auto line = addr2line(backtrace_addresses_[i]); + auto line = addr2line(pointers[i]); if (line.find("??") != std::string::npos) { // If addr2line doesn't find a good - // answer use basic information + // answer, use basic information // instead. oss << symbols[i] << std::endl; } else { diff --git a/configure.ac b/configure.ac index f9538a09b..aca0489c3 100644 --- a/configure.ac +++ b/configure.ac @@ -50,6 +50,22 @@ AX_LIB_CURL(,[:],[ AC_MSG_ERROR([Need libcurl.]) ]) +# Check for libunwind +PKG_CHECK_MODULES([LIBUNWIND], [libunwind], [ + AC_DEFINE([HAVE_LIBUNWIND], [1], [Define if libunwind is available]) +], [ + AC_MSG_ERROR([libunwind is required but not found]) +]) + +# https://github.com/ZmnSCPxj/clboss/issues/245 +case "$host_os" in + *alpine*) + LDFLAGS="$LDFLAGS -lexecinfo" + ;; +esac + +AC_SUBST(LDFLAGS) + # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. diff --git a/flake.nix b/flake.nix index 5bd1f5510..9aaa72f04 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,7 @@ libev libevdev curl + libunwind sqlite bind autoconf