From 7dd05659a20d03f1c570b618a387fc1480d417fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Nov 2018 13:03:42 +0100 Subject: [PATCH 1/8] Add libbacktrace to depends This is currently only useful to extract symbols. It fails to gather stacktraces when compiled with MinGW, so we can only use it to get symbol information from a stack trace which we gathered outside of libbacktrace. --- depends/packages/backtrace.mk | 21 +++++++++++++++++++++ depends/packages/packages.mk | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 depends/packages/backtrace.mk diff --git a/depends/packages/backtrace.mk b/depends/packages/backtrace.mk new file mode 100644 index 0000000000000..d84391c0fef83 --- /dev/null +++ b/depends/packages/backtrace.mk @@ -0,0 +1,21 @@ +package=backtrace +$(package)_version=rust-snapshot-2018-05-22 +$(package)_download_path=https://github.com/rust-lang-nursery/libbacktrace/archive +$(package)_file_name=$($(package)_version).tar.gz +$(package)_sha256_hash=8da6daa0a582c9bbd1f2933501168b4c43664700f604f43e922e85b99e5049bc + +define $(package)_set_vars +$(package)_config_opts=--disable-shared --prefix=$(host_prefix) +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 9eabe53f3b149..8fa5ce43227f8 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,4 +1,4 @@ -packages:=boost openssl libevent zeromq gmp chia_bls +packages:=boost openssl libevent zeromq gmp chia_bls backtrace native_packages := native_ccache qt_native_packages = native_protobuf From 29759d3c4862de940f09c614e0e9db1a727fe3cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 25 Jan 2019 07:53:52 +0100 Subject: [PATCH 2/8] Add -mbig-obj to CXXFLAGS for MinGW builds --- configure.ac | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configure.ac b/configure.ac index 0b06fab6d34b7..b20d72949cf6a 100644 --- a/configure.ac +++ b/configure.ac @@ -213,6 +213,9 @@ if test "x$enable_debug" = xyes; then fi fi +# Needed for MinGW targets when debug symbols are enabled as compiled objects get very large +AX_CHECK_COMPILE_FLAG([-Wa,-mbig-obj], [CXXFLAGS="$CXXFLAGS -Wa,-mbig-obj"],,,) + ERROR_CXXFLAGS= if test "x$enable_werror" = "xyes"; then if test "x$CXXFLAG_WERROR" = "x"; then From cff1f96a89468b0c100d8b2844562c36ca8c587f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Nov 2018 17:06:27 +0100 Subject: [PATCH 3/8] Implement stacktraces for C++ exceptions This is a hack and should only be used for debugging. It works by wrapping the C++ ABI __wrap___cxa_allocate_exception. The wrapper records a backtrace and stores it in a global map. Later the stacktrace can be retrieved with GetExceptionStacktraceStr. This commit also adds handlers to pretty print uncaught exceptions and signals. --- configure.ac | 31 ++ src/Makefile.am | 37 +- src/Makefile.bench.include | 4 +- src/Makefile.qt.include | 4 +- src/Makefile.qttest.include | 4 +- src/Makefile.test.include | 4 +- src/bench/bench_dash.cpp | 4 + src/dash-cli.cpp | 4 + src/dash-tx.cpp | 5 + src/dashd.cpp | 4 + src/qt/dash.cpp | 4 + src/stacktraces.cpp | 759 ++++++++++++++++++++++++++++++++++++ src/stacktraces.h | 46 +++ 13 files changed, 896 insertions(+), 14 deletions(-) create mode 100644 src/stacktraces.cpp create mode 100644 src/stacktraces.h diff --git a/configure.ac b/configure.ac index b20d72949cf6a..f0d25d9401baf 100644 --- a/configure.ac +++ b/configure.ac @@ -192,6 +192,13 @@ AC_ARG_ENABLE([debug], [enable_debug=$enableval], [enable_debug=no]) +# Enable exception stacktraces +AC_ARG_ENABLE([stacktraces], + [AS_HELP_STRING([--enable-stacktraces], + [gather and print exception stack traces (default is no)])], + [enable_stacktraces=$enableval], + [enable_stacktraces=no]) + # Turn warnings into errors AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], @@ -211,6 +218,29 @@ if test "x$enable_debug" = xyes; then if test "x$GXX" = xyes; then CXXFLAGS="$CXXFLAGS -g3 -O0" fi +elif test "x$enable_stacktraces" = xyes; then + # Enable debug information but don't turn off optimization + # (stacktraces will be suboptimal, but better than nothing) + if test "x$GCC" = xyes; then + CFLAGS="$CFLAGS -g1 -fno-omit-frame-pointer" + fi + + if test "x$GXX" = xyes; then + CXXFLAGS="$CXXFLAGS -g1 -fno-omit-frame-pointer" + fi +fi + +AM_CONDITIONAL([ENABLE_STACKTRACES], [test x$enable_stacktraces = xyes]) +if test "x$enable_stacktraces" = xyes; then + AC_DEFINE(ENABLE_STACKTRACES, 1, [Define this symbol if stacktraces should be enables]) +fi +AX_CHECK_LINK_FLAG([-Wl,-wrap=__cxa_allocate_exception], [LINK_WRAP_SUPPORTED=yes],,,) +AX_CHECK_COMPILE_FLAG([-rdynamic], [RDYNAMIC_SUPPORTED=yes],,,) +AM_CONDITIONAL([STACKTRACE_WRAPPED_CXX_ABI],[test x$LINK_WRAP_SUPPORTED = xyes]) +AM_CONDITIONAL([RDYNAMIC_SUPPORTED],[test x$RDYNAMIC_SUPPORTED = xyes]) + +if test x$LINK_WRAP_SUPPORTED = "xyes"; then + AC_DEFINE(STACKTRACE_WRAPPED_CXX_ABI, 1, [Define this symbol to use wrapped CXX ABIs for exception stacktraces])], fi # Needed for MinGW targets when debug symbols are enabled as compiled objects get very large @@ -1212,6 +1242,7 @@ echo " with test = $use_tests" echo " with bench = $use_bench" echo " with upnp = $use_upnp" echo " debug enabled = $enable_debug" +echo " stacktraces enabled = $enable_stacktraces" echo " werror = $enable_werror" echo echo " target os = $TARGET_OS" diff --git a/src/Makefile.am b/src/Makefile.am index f948d77a34d28..41dfe632707f8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,6 +10,29 @@ AM_CXXFLAGS = $(HARDENED_CXXFLAGS) $(ERROR_CXXFLAGS) AM_CPPFLAGS = $(HARDENED_CPPFLAGS) EXTRA_LIBRARIES = +if ENABLE_STACKTRACES +if STACKTRACE_WRAPPED_CXX_ABI +# Wrap internal C++ ABI's so that we can attach stacktraces to exceptions +LDFLAGS_WRAP_EXCEPTIONS = -Wl,-wrap,__cxa_allocate_exception -Wl,-wrap,__cxa_free_exception +if TARGET_WINDOWS +LDFLAGS_WRAP_EXCEPTIONS += -Wl,-wrap,_assert -Wl,-wrap,_wassert +else +LDFLAGS_WRAP_EXCEPTIONS += -Wl,-wrap,__assert_fail +endif +endif + +if RDYNAMIC_SUPPORTED +# This gives better stacktraces +AM_CXXFLAGS += -rdynamic +endif +endif + +if TARGET_WINDOWS +BACKTRACE_LIB = -ldbghelp -lbacktrace +else +BACKTRACE_LIB = -lbacktrace +endif + if EMBEDDED_UNIVALUE LIBUNIVALUE = univalue/libunivalue.la @@ -184,6 +207,7 @@ BITCOIN_CORE_H = \ script/standard.h \ script/ismine.h \ spork.h \ + stacktraces.h \ streams.h \ support/allocators/mt_pooled_secure.h \ support/allocators/pooled_secure.h \ @@ -464,6 +488,7 @@ libdash_util_a_SOURCES = \ compat/strnlen.cpp \ random.cpp \ rpc/protocol.cpp \ + stacktraces.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ @@ -492,7 +517,7 @@ nodist_libdash_util_a_SOURCES = $(srcdir)/obj/build.h dashd_SOURCES = dashd.cpp dashd_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) dashd_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -dashd_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +dashd_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS dashd_SOURCES += dashd-res.rc @@ -511,13 +536,13 @@ dashd_LDADD = \ $(LIBMEMENV) \ $(LIBSECP256K1) -dashd_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(BLS_LIBS) +dashd_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(BLS_LIBS) # dash-cli binary # dash_cli_SOURCES = dash-cli.cpp dash_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) dash_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -dash_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +dash_cli_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS dash_cli_SOURCES += dash-cli-res.rc @@ -528,14 +553,14 @@ dash_cli_LDADD = \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) -dash_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS) $(BLS_LIBS) +dash_cli_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS) $(BLS_LIBS) # # dash-tx binary # dash_tx_SOURCES = dash-tx.cpp dash_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) dash_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -dash_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +dash_tx_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS dash_tx_SOURCES += dash-tx-res.rc @@ -549,7 +574,7 @@ dash_tx_LDADD = \ $(LIBBITCOIN_CRYPTO) \ $(LIBSECP256K1) -dash_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) $(BLS_LIBS) +dash_tx_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(CRYPTO_LIBS) $(BLS_LIBS) # # dashconsensus library # diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 96170ff4e9698..60bbbadc79277 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -55,8 +55,8 @@ bench_bench_dash_SOURCES += bench/coin_selection.cpp bench_bench_dash_LDADD += $(LIBBITCOIN_WALLET) $(LIBBITCOIN_CRYPTO) endif -bench_bench_dash_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) -bench_bench_dash_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +bench_bench_dash_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) +bench_bench_dash_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_TEST_FILES) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index a31cad006b5ac..a50bf431334eb 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -643,9 +643,9 @@ if ENABLE_ZMQ qt_dash_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_dash_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) -qt_dash_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +qt_dash_qt_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_dash_qt_LIBTOOLFLAGS = --tag CXX #locale/foo.ts -> locale/foo.qm diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 01243392a5213..efca183cb6e62 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -49,10 +49,10 @@ if ENABLE_ZMQ qt_test_test_dash_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_test_test_dash_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ - $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ + $(LIBMEMENV) $(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) -qt_test_test_dash_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +qt_test_test_dash_qt_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_test_test_dash_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) CLEAN_BITCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 1daa5862eb4c0..b7285e91a899a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -152,14 +152,14 @@ endif test_test_dash_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_dash_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) test_test_dash_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ - $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) + $(BACKTRACE_LIB) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) test_test_dash_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) if ENABLE_WALLET test_test_dash_LDADD += $(LIBBITCOIN_WALLET) endif test_test_dash_LDADD += $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) -test_test_dash_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static +test_test_dash_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static if ENABLE_ZMQ test_test_dash_LDADD += $(ZMQ_LIBS) diff --git a/src/bench/bench_dash.cpp b/src/bench/bench_dash.cpp index a1d2085024707..7d30af90c92ed 100644 --- a/src/bench/bench_dash.cpp +++ b/src/bench/bench_dash.cpp @@ -5,6 +5,7 @@ #include "bench.h" #include "key.h" +#include "stacktraces.h" #include "validation.h" #include "util.h" @@ -16,6 +17,9 @@ void CleanupBLSDkgTests(); int main(int argc, char** argv) { + RegisterPrettySignalHandlers(); + RegisterPrettyTerminateHander(); + ECC_Start(); ECCVerifyHandle verifyHandle; diff --git a/src/dash-cli.cpp b/src/dash-cli.cpp index b657bb6c81eb2..a179cb6d6c0cf 100644 --- a/src/dash-cli.cpp +++ b/src/dash-cli.cpp @@ -12,6 +12,7 @@ #include "clientversion.h" #include "rpc/client.h" #include "rpc/protocol.h" +#include "stacktraces.h" #include "util.h" #include "utilstrencodings.h" @@ -362,6 +363,9 @@ int CommandLineRPC(int argc, char *argv[]) int main(int argc, char* argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); if (!SetupNetworking()) { fprintf(stderr, "Error: Initializing networking failed\n"); diff --git a/src/dash-tx.cpp b/src/dash-tx.cpp index 6b9f49ade0ff4..48e0486701362 100644 --- a/src/dash-tx.cpp +++ b/src/dash-tx.cpp @@ -26,6 +26,8 @@ #include #include +#include "stacktraces.h" + static bool fCreateBlank; static std::map registers; static const int CONTINUE_EXECUTION=-1; @@ -784,6 +786,9 @@ static int CommandLineRawTx(int argc, char* argv[]) int main(int argc, char* argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); try { diff --git a/src/dashd.cpp b/src/dashd.cpp index 69de2b8ebd74f..658c8e3c1a4f6 100644 --- a/src/dashd.cpp +++ b/src/dashd.cpp @@ -19,6 +19,7 @@ #include "httpserver.h" #include "httprpc.h" #include "utilstrencodings.h" +#include "stacktraces.h" #include #include @@ -195,6 +196,9 @@ bool AppInit(int argc, char* argv[]) int main(int argc, char* argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); // Connect dashd signal handlers diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index cd177d83ba00a..8c6490d4670de 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -30,6 +30,7 @@ #include "init.h" #include "rpc/server.h" #include "scheduler.h" +#include "stacktraces.h" #include "ui_interface.h" #include "util.h" #include "warnings.h" @@ -568,6 +569,9 @@ WId BitcoinApplication::getMainWinId() const #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); /// 1. Parse command-line options. These take precedence over anything else. diff --git a/src/stacktraces.cpp b/src/stacktraces.cpp new file mode 100644 index 0000000000000..d29639b9b4555 --- /dev/null +++ b/src/stacktraces.cpp @@ -0,0 +1,759 @@ +// Copyright (c) 2014-2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "stacktraces.h" +#include "tinyformat.h" +#include "random.h" +#include "util.h" + +#include "dash-config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if WIN32 +#include +#include +#else +#include +#include +#include +#if !__APPLE__ +#include // for PATH_MAX +#endif +#endif + +#if !WIN32 +#include +#endif + +#if __APPLE__ +#include +#endif + +#include +#include // for basename() +#include + +static void PrintCrashInfo(const std::string& s) +{ + LogPrintf("%s", s); + fprintf(stderr, "%s", s.c_str()); + fflush(stderr); +} + +// set to true when the abort signal should not handled +// this is the case when the terminate handler or an assert already handled the exception +static std::atomic skipAbortSignal(false); + +ssize_t GetExeFileNameImpl(char* buf, size_t bufSize) +{ +#if WIN32 + std::vector tmp(bufSize); + DWORD len = GetModuleFileName(NULL, tmp.data(), bufSize); + if (len >= bufSize) { + return len; + } + for (size_t i = 0; i < len; i++) { + buf[i] = (char)tmp[i]; + } + return len; +#elif __APPLE__ + uint32_t bufSize2 = (uint32_t)bufSize; + if (_NSGetExecutablePath(buf, &bufSize2) != 0) { + // it's not entirely clear if the value returned by _NSGetExecutablePath includes the null character + return bufSize2 + 1; + } + // TODO do we have to call realpath()? The path returned by _NSGetExecutablePath may include ., .. symbolic links + return strlen(buf); +#else + ssize_t len = readlink("/proc/self/exe", buf, bufSize - 1); + if (len == -1) { + return -1; + } + return len; +#endif +} + +std::string GetExeFileName() +{ + std::vector buf(1024); + while (true) { + ssize_t len = GetExeFileNameImpl(buf.data(), buf.size()); + if (len < 0) { + return ""; + } + if (len < buf.size()) { + return std::string(buf.begin(), buf.begin() + len); + } + buf.resize(buf.size() * 2); + } +} + +std::string DemangleSymbol(const std::string& name) +{ +#if __GNUC__ || __clang__ + int status = -4; // some arbitrary value to eliminate the compiler warning + char* str = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); + if (status != 0) { + free(str); + return name; + } + std::string ret = str; + free(str); + return ret; +#else + // TODO other platforms/compilers + return name; +#endif +} + +static void my_backtrace_error_callback (void *data, const char *msg, + int errnum) +{ + PrintCrashInfo(strprintf("libbacktrace error: %d - %s\n", errnum, msg)); +} + +static backtrace_state* GetLibBacktraceState() +{ + static std::string exeFileName = GetExeFileName(); + static const char* exeFileNamePtr = exeFileName.empty() ? nullptr : exeFileName.c_str(); + static backtrace_state* st = backtrace_create_state(exeFileNamePtr, 1, my_backtrace_error_callback, NULL); + return st; +} + +#if WIN32 +// PC addresses returned by StackWalk64 are in the real mapped space, while libbacktrace expects them to be in the +// default mapped space starting at 0x400000. This method converts the address. +// TODO this is probably the same reason libbacktrace is not able to gather the stacktrace on Windows (returns pointers like 0x1 or 0xfffffff) +// If they ever fix this problem, we might end up converting to invalid addresses here +static uintptr_t ConvertAddress(uintptr_t addr) +{ + MEMORY_BASIC_INFORMATION mbi; + + if (!VirtualQuery((PVOID)addr, &mbi, sizeof(mbi))) + return 0; + + uintptr_t hMod = (uintptr_t)mbi.AllocationBase; + uintptr_t offset = addr - hMod; + return 0x400000 + offset; +} + +static __attribute__((noinline)) std::vector GetStackFrames(size_t skip, size_t max_frames, const CONTEXT* pContext = nullptr) +{ + // We can't use libbacktrace for stack unwinding on Windows as it returns invalid addresses (like 0x1 or 0xffffffff) + static BOOL symInitialized = SymInitialize(GetCurrentProcess(), NULL, TRUE); + + // dbghelp is not thread safe + static std::mutex m; + std::lock_guard l(m); + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + CONTEXT context; + if (!pContext) { + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + } else { + memcpy(&context, pContext, sizeof(CONTEXT)); + } + + DWORD image; + STACKFRAME64 stackframe; + ZeroMemory(&stackframe, sizeof(STACKFRAME64)); + +#ifdef __i386__ + image = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context.Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Esp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif __x86_64__ + image = IMAGE_FILE_MACHINE_AMD64; + stackframe.AddrPC.Offset = context.Rip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Rbp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Rsp; + stackframe.AddrStack.Mode = AddrModeFlat; + if (!pContext) { + skip++; // skip this method + } +#else +#error unsupported architecture +#endif + + std::vector ret; + + size_t i = 0; + while (ret.size() < max_frames) { + BOOL result = StackWalk64( + image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL); + + if (!result) { + break; + } + if (i >= skip) { + uintptr_t pc = ConvertAddress(stackframe.AddrPC.Offset); + if (pc == 0) { + pc = stackframe.AddrPC.Offset; + } + ret.emplace_back(pc); + } + i++; + } + + return ret; +} +#else +static int my_backtrace_simple_callback(void *data, uintptr_t pc) +{ + auto v = (std::vector*)data; + v->emplace_back(pc); + if (v->size() >= 128) { + // abort + return 1; + } + return 0; +} + +static __attribute__((noinline)) std::vector GetStackFrames(size_t skip, size_t max_frames) +{ + // FYI, this is not using libbacktrace, but "backtrace()" from + std::vector buf(max_frames); + int count = backtrace(buf.data(), (int)buf.size()); + if (count == 0) { + return {}; + } + buf.resize((size_t)count); + + std::vector ret; + ret.reserve(count); + for (size_t i = skip + 1; i < buf.size(); i++) { + ret.emplace_back((uintptr_t) buf[i]); + } + return ret; +} +#endif + +struct stackframe_info { + uintptr_t pc; + std::string filename; + int lineno; + std::string function; +}; + +static int my_backtrace_full_callback (void *data, uintptr_t pc, const char *filename, int lineno, const char *function) +{ + auto sis = (std::vector*)data; + stackframe_info si; + si.pc = pc; + si.lineno = lineno; + if (filename) { + si.filename = filename; + } + if (function) { + si.function = DemangleSymbol(function); + } + sis->emplace_back(si); + if (sis->size() >= 128) { + // abort + return 1; + } + if (si.function == "mainCRTStartup" || + si.function == "pthread_create_wrapper" || + si.function == "__tmainCRTStartup") { + // on Windows, stack frames are unwinded into invalid PCs after entry points + // this doesn't catch all cases unfortunately + return 1; + } + return 0; +} + +static std::vector GetStackFrameInfos(const std::vector& stackframes) +{ + std::vector infos; + infos.reserve(stackframes.size()); + + for (size_t i = 0; i < stackframes.size(); i++) { + if (backtrace_pcinfo(GetLibBacktraceState(), stackframes[i], my_backtrace_full_callback, my_backtrace_error_callback, &infos)) { + break; + } + } + + return infos; +} + +static std::string GetStackFrameInfosStr(const std::vector& sis, size_t spaces = 2) +{ + if (sis.empty()) { + return "\n"; + } + + std::string sp; + for (size_t i = 0; i < spaces; i++) { + sp += " "; + } + + std::vector lstrs; + lstrs.reserve(sis.size()); + + for (size_t i = 0; i < sis.size(); i++) { + auto& si = sis[i]; + + std::string lstr; + if (!si.filename.empty()) { + std::vector vecFilename(si.filename.size() + 1, '\0'); + strcpy(vecFilename.data(), si.filename.c_str()); + lstr += basename(vecFilename.data()); + } else { + lstr += ""; + } + if (si.lineno != 0) { + lstr += strprintf(":%d", si.lineno); + } + + lstrs.emplace_back(lstr); + } + + // get max "filename:line" length so we can better format it + size_t lstrlen = std::max_element(lstrs.begin(), lstrs.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); })->size(); + + std::string fmtStr = strprintf("%%2d#: (0x%%08X) %%-%ds - %%s\n", lstrlen); + + std::string s; + for (size_t i = 0; i < sis.size(); i++) { + auto& si = sis[i]; + + auto& lstr = lstrs[i]; + + std::string fstr; + if (!si.function.empty()) { + fstr = si.function; + } else { + fstr = "???"; + } + + std::string s2 = strprintf(fmtStr, i, si.pc, lstr, fstr); + + s += sp; + s += s2; + } + return s; +} + +#if ENABLE_STACKTRACES +static std::mutex g_stacktraces_mutex; +static std::map>> g_stacktraces; + +#if STACKTRACE_WRAPPED_CXX_ABI +// These come in through -Wl,-wrap +// It only works on GCC +extern "C" void* __real___cxa_allocate_exception(size_t thrown_size); +extern "C" void __real___cxa_free_exception(void * thrown_exception); +#if __clang__ +#error not supported on WIN32 (no dlsym support) +#elif WIN32 +extern "C" void __real__assert(const char *assertion, const char *file, unsigned int line); +extern "C" void __real__wassert(const wchar_t *assertion, const wchar_t *file, unsigned int line); +#else +extern "C" void __real___assert_fail(const char *assertion, const char *file, unsigned int line, const char *function); +#endif +#else +// Clang does not support -Wl,-wrap, so we must use dlsym +// This is ok because at the same time Clang only supports dynamic linking to libc/libc++ +extern "C" void* __real___cxa_allocate_exception(size_t thrown_size) +{ + static auto f = (void*(*)(size_t))dlsym(RTLD_NEXT, "__cxa_allocate_exception"); + return f(thrown_size); +} +extern "C" void __real___cxa_free_exception(void * thrown_exception) +{ + static auto f = (void(*)(void*))dlsym(RTLD_NEXT, "__cxa_free_exception"); + return f(thrown_exception); +} +#if __clang__ +extern "C" void __attribute__((noreturn)) __real___assert_rtn(const char *function, const char *file, int line, const char *assertion) +{ + static auto f = (void(__attribute__((noreturn)) *) (const char*, const char*, int, const char*))dlsym(RTLD_NEXT, "__assert_rtn"); + f(function, file, line, assertion); +} +#elif WIN32 +#error not supported on WIN32 (no dlsym support) +#else +extern "C" void __real___assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) +{ + static auto f = (void(*)(const char*, const char*, unsigned int, const char*))dlsym(RTLD_NEXT, "__assert_fail"); + f(assertion, file, line, function); +} +#endif +#endif + +#if STACKTRACE_WRAPPED_CXX_ABI +#define WRAPPED_NAME(x) __wrap_##x +#else +#define WRAPPED_NAME(x) x +#endif + +extern "C" void* __attribute__((noinline)) WRAPPED_NAME(__cxa_allocate_exception)(size_t thrown_size) +{ + // grab the current stack trace and store it in the global exception->stacktrace map + auto localSt = GetStackFrames(1, 16); + + // WARNING keep this as it is and don't try to optimize it (no std::move, no std::make_shared, ...) + // trying to optimize this will cause the optimizer to move the GetStackFrames() call deep into the stl libs + std::shared_ptr> st(new std::vector(localSt)); + + void* p = __real___cxa_allocate_exception(thrown_size); + + std::lock_guard l(g_stacktraces_mutex); + g_stacktraces.emplace(p, st); + return p; +} + +extern "C" void __attribute__((noinline)) WRAPPED_NAME(__cxa_free_exception)(void * thrown_exception) +{ + __real___cxa_free_exception(thrown_exception); + + std::lock_guard l(g_stacktraces_mutex); + g_stacktraces.erase(thrown_exception); +} + +#if __clang__ +extern "C" void __attribute__((noinline)) WRAPPED_NAME(__assert_rtn)(const char *function, const char *file, int line, const char *assertion) +{ + auto st = GetCurrentStacktraceStr(1); + PrintCrashInfo(strprintf("#### assertion failed: %s ####\n%s", assertion, st)); + skipAbortSignal = true; + __real___assert_rtn(function, file, line, assertion); +} +#elif WIN32 +extern "C" void __attribute__((noinline)) WRAPPED_NAME(_assert)(const char *assertion, const char *file, unsigned int line) +{ + auto st = GetCurrentStacktraceStr(1); + PrintCrashInfo(strprintf("#### assertion failed: %s ####\n%s", assertion, st)); + skipAbortSignal = true; + __real__assert(assertion, file, line); +} +extern "C" void __attribute__((noinline)) WRAPPED_NAME(_wassert)(const wchar_t *assertion, const wchar_t *file, unsigned int line) +{ + auto st = GetCurrentStacktraceStr(1); + PrintCrashInfo(strprintf("#### assertion failed: %s ####\n%s", std::string(assertion, assertion + wcslen(assertion)), st)); + skipAbortSignal = true; + __real__wassert(assertion, file, line); +} +#else +extern "C" void __attribute__((noinline)) WRAPPED_NAME(__assert_fail)(const char *assertion, const char *file, unsigned int line, const char *function) +{ + auto st = GetCurrentStacktraceStr(1); + PrintCrashInfo(strprintf("#### assertion failed: %s ####\n%s", assertion, st)); + skipAbortSignal = true; + __real___assert_fail(assertion, file, line, function); +} +#endif + +static std::shared_ptr> GetExceptionStacktrace(const std::exception_ptr& e) +{ + void* p = *(void**)&e; + + std::lock_guard l(g_stacktraces_mutex); + auto it = g_stacktraces.find(p); + if (it == g_stacktraces.end()) { + return nullptr; + } + return it->second; +} +#endif + +std::string GetExceptionStacktraceStr(const std::exception_ptr& e) +{ +#ifdef ENABLE_STACKTRACES + auto stackframes = GetExceptionStacktrace(e); + if (stackframes && !stackframes->empty()) { + auto infos = GetStackFrameInfos(*stackframes); + return GetStackFrameInfosStr(infos); + } +#endif + return "\n"; +} + +std::string __attribute__((noinline)) GetCurrentStacktraceStr(size_t skip, size_t max_depth) +{ +#ifdef ENABLE_STACKTRACES + auto stackframes = GetStackFrames(skip + 1, max_depth); // +1 to skip current method + auto infos = GetStackFrameInfos(stackframes); + return GetStackFrameInfosStr(infos); +#else + return "\n"; +#endif +} + +std::string GetPrettyExceptionStr(const std::exception_ptr& e) +{ + if (!e) { + return "\n"; + } + + std::string type; + std::string what; + + try { + // rethrow and catch the exception as there is no other way to reliably cast to the real type (not possible with RTTI) + std::rethrow_exception(e); + } catch (const std::exception& e) { + type = abi::__cxa_current_exception_type()->name(); + what = GetExceptionWhat(e); + } catch (const std::string& e) { + type = abi::__cxa_current_exception_type()->name(); + what = GetExceptionWhat(e); + } catch (const char* e) { + type = abi::__cxa_current_exception_type()->name(); + what = GetExceptionWhat(e); + } catch (int e) { + type = abi::__cxa_current_exception_type()->name(); + what = GetExceptionWhat(e); + } catch (...) { + type = abi::__cxa_current_exception_type()->name(); + what = ""; + } + + if (type.empty()) { + type = ""; + } else { + type = DemangleSymbol(type); + } + + std::string s = strprintf("Exception: type=%s, what=\"%s\"\n", type, what); + +#if ENABLE_STACKTRACES + s += GetExceptionStacktraceStr(e); +#endif + + return s; +} + +static void terminate_handler() +{ + auto exc = std::current_exception(); + + std::string s, s2; + s += "#### std::terminate() called, aborting ####\n"; + + if (exc) { + s += "#### UNCAUGHT EXCEPTION ####\n"; + s2 = GetPrettyExceptionStr(exc); + } else { + s += "#### UNKNOWN REASON ####\n"; +#ifdef ENABLE_STACKTRACES + s2 = GetCurrentStacktraceStr(0); +#else + s2 = "\n"; +#endif + } + + PrintCrashInfo(strprintf("%s%s", s, s2)); + + skipAbortSignal = true; + std::abort(); +} + +void RegisterPrettyTerminateHander() +{ + std::set_terminate(terminate_handler); +} + +#if ENABLE_STACKTRACES && !WIN32 +static void HandlePosixSignal(int s) +{ + if (s == SIGABRT && skipAbortSignal) { + return; + } + std::string st = GetCurrentStacktraceStr(0); + const char* name = strsignal(s); + if (!name) { + name = "UNKNOWN"; + } + PrintCrashInfo(strprintf("#### signal %s ####\n%s", name, st)); + + // avoid a signal loop + skipAbortSignal = true; + std::abort(); +} +#endif + +#if WIN32 +static void DoHandleWindowsException(EXCEPTION_POINTERS * ExceptionInfo) +{ + std::string excType; + switch(ExceptionInfo->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION: excType = "EXCEPTION_ACCESS_VIOLATION"; break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: excType = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; break; + case EXCEPTION_BREAKPOINT: excType = "EXCEPTION_BREAKPOINT"; break; + case EXCEPTION_DATATYPE_MISALIGNMENT: excType = "EXCEPTION_DATATYPE_MISALIGNMENT"; break; + case EXCEPTION_FLT_DENORMAL_OPERAND: excType = "EXCEPTION_FLT_DENORMAL_OPERAND"; break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: excType = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; break; + case EXCEPTION_FLT_INEXACT_RESULT: excType = "EXCEPTION_FLT_INEXACT_RESULT"; break; + case EXCEPTION_FLT_INVALID_OPERATION: excType = "EXCEPTION_FLT_INVALID_OPERATION"; break; + case EXCEPTION_FLT_OVERFLOW: excType = "EXCEPTION_FLT_OVERFLOW"; break; + case EXCEPTION_FLT_STACK_CHECK: excType = "EXCEPTION_FLT_STACK_CHECK"; break; + case EXCEPTION_FLT_UNDERFLOW: excType = "EXCEPTION_FLT_UNDERFLOW"; break; + case EXCEPTION_ILLEGAL_INSTRUCTION: excType = "EXCEPTION_ILLEGAL_INSTRUCTION"; break; + case EXCEPTION_IN_PAGE_ERROR: excType = "EXCEPTION_IN_PAGE_ERROR"; break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: excType = "EXCEPTION_INT_DIVIDE_BY_ZERO"; break; + case EXCEPTION_INT_OVERFLOW: excType = "EXCEPTION_INT_OVERFLOW"; break; + case EXCEPTION_INVALID_DISPOSITION: excType = "EXCEPTION_INVALID_DISPOSITION"; break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: excType = "EXCEPTION_NONCONTINUABLE_EXCEPTION"; break; + case EXCEPTION_PRIV_INSTRUCTION: excType = "EXCEPTION_PRIV_INSTRUCTION"; break; + case EXCEPTION_SINGLE_STEP: excType = "EXCEPTION_SINGLE_STEP"; break; + case EXCEPTION_STACK_OVERFLOW: excType = "EXCEPTION_STACK_OVERFLOW"; break; + default: excType = "UNKNOWN"; break; + } + + auto stackframes = GetStackFrames(0, 16, ExceptionInfo->ContextRecord); + auto infos = GetStackFrameInfos(stackframes); + auto infosStr = GetStackFrameInfosStr(infos); + + PrintCrashInfo(strprintf("#### Windows Exception %s ####\n%s", excType, infosStr)); +} + +LONG WINAPI HandleWindowsException(EXCEPTION_POINTERS * ExceptionInfo) +{ + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { + // We can't directly do the exception handling in this thread anymore as we need stack space for this + // So let's do the handling in another thread + // On Wine, the exception handler is not called at all + std::thread([&]() { + DoHandleWindowsException(ExceptionInfo); + }).join(); + } else { + DoHandleWindowsException(ExceptionInfo); + } + return EXCEPTION_CONTINUE_SEARCH; +} +#endif + +void RegisterPrettySignalHandlers() +{ +#if ENABLE_STACKTRACES +#if WIN32 + SetUnhandledExceptionFilter(HandleWindowsException); +#else + const std::vector posix_signals = { + // Signals for which the default action is "Core". + SIGABRT, // Abort signal from abort(3) + SIGBUS, // Bus error (bad memory access) + SIGFPE, // Floating point exception + SIGILL, // Illegal Instruction + SIGIOT, // IOT trap. A synonym for SIGABRT + SIGQUIT, // Quit from keyboard + SIGSEGV, // Invalid memory reference + SIGSYS, // Bad argument to routine (SVr4) + SIGTRAP, // Trace/breakpoint trap + SIGXCPU, // CPU time limit exceeded (4.2BSD) + SIGXFSZ, // File size limit exceeded (4.2BSD) +#if __APPLE__ + SIGEMT, // emulation instruction executed +#endif + }; + + for (auto s : posix_signals) { + struct sigaction sa_segv; + sa_segv.sa_handler = HandlePosixSignal; + sigemptyset(&sa_segv.sa_mask); + sa_segv.sa_flags = 0; + sigaction(s, &sa_segv, NULL); + } +#endif +#endif +} + +static __attribute__((noinline)) int GetCrashType() +{ + // exclude crash type 5 (stack overflow) as we can't handle it propery in linux (including Windows on Wine) + // If you want to test it, just change this line to return the fixed number 5 + return GetRandInt(5); +} + +static __attribute__((noinline)) int DoCrashRecursiveLoop() +{ + std::string a("1"), b("2"), c("3"), d, e, f; + a += b + c + d + e + f; + if (a.empty()) { + // will never happen + LogPrintf( "%s", a); + return 0; + } + return DoCrashRecursiveLoop(); +} + +static __attribute__((noinline)) void DoCrash2() +{ + static int zero = 0; + static int test = 1; + + int rnd = GetCrashType(); + switch (rnd) { + case 0: + PrintCrashInfo(strprintf("%s: throwing exception!\n", __func__)); + throw std::runtime_error("whoops"); + case 1: + PrintCrashInfo(strprintf("%s: asserting %d==2!\n", __func__, rnd)); + assert(rnd == 2); + break; + case 2: { + PrintCrashInfo(strprintf("%s: accessing invalid memory!\n", __func__)); + int* p = (int*) 0x1; + *p = 1; + break; + } + case 3: { + PrintCrashInfo(strprintf("%s: dividing by zero!\n", __func__)); + test = test / zero; + break; + } + case 4: { + PrintCrashInfo(strprintf("%s: calling std::terminate()!\n", __func__)); + std::terminate(); + break; + } + case 5: { + PrintCrashInfo(strprintf("%s: causing stack overflow!\n", __func__)); + DoCrashRecursiveLoop(); + break; + } + } +}; + +static __attribute__((noinline)) void DoCrash() +{ + DoCrash2(); +} + +void __attribute__((noinline)) TestCrash(bool allowOtherThread) +{ + bool otherThread = allowOtherThread && (GetRandInt(2)) == 0; + + if (otherThread) { + std::thread t(DoCrash); + if (t.joinable()) { + t.join(); + } + } else { + DoCrash(); + } +} diff --git a/src/stacktraces.h b/src/stacktraces.h new file mode 100644 index 0000000000000..1d34210cbccfa --- /dev/null +++ b/src/stacktraces.h @@ -0,0 +1,46 @@ +// Copyright (c) 2014-2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_STACKTRACES_H +#define DASH_STACKTRACES_H + +#include +#include +#include + +#include + +#include "tinyformat.h" + +std::string DemangleSymbol(const std::string& name); + +std::string GetCurrentStacktraceStr(size_t skip = 0, size_t max_depth = 16); + +std::string GetExceptionStacktraceStr(const std::exception_ptr& e); +std::string GetPrettyExceptionStr(const std::exception_ptr& e); + +template +std::string GetExceptionWhat(const T& e); + +template<> +inline std::string GetExceptionWhat(const std::exception& e) +{ + return e.what(); +} + +// Default implementation +template +inline std::string GetExceptionWhat(const T& e) +{ + std::ostringstream s; + s << e; + return s.str(); +} + +void RegisterPrettyTerminateHander(); +void RegisterPrettySignalHandlers(); + +void TestCrash(bool allowOtherThread = true); + +#endif//DASH_STACKTRACES_H From 0dbdf00bc8ad95cdc8da5d48d241892b40e2d070 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Nov 2018 13:02:15 +0100 Subject: [PATCH 4/8] Register exception translators to pretty print exceptions in unit tests --- src/test/test_dash.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/test_dash.cpp b/src/test/test_dash.cpp index e191a67d130b5..eed85d7d46929 100644 --- a/src/test/test_dash.cpp +++ b/src/test/test_dash.cpp @@ -21,6 +21,7 @@ #include "rpc/server.h" #include "rpc/register.h" #include "script/sigcache.h" +#include "stacktraces.h" #include "test/testutil.h" @@ -33,6 +34,7 @@ #include #include +#include #include std::unique_ptr g_connman; @@ -225,3 +227,33 @@ bool ShutdownRequested() { return false; } + +template +void translate_exception(const T &e) +{ + std::cerr << GetPrettyExceptionStr(std::current_exception()) << std::endl; + throw; +} + +template +void register_exception_translator() +{ + boost::unit_test::unit_test_monitor.register_exception_translator(&translate_exception); +} + +struct ExceptionInitializer { + ExceptionInitializer() + { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + + register_exception_translator(); + register_exception_translator(); + register_exception_translator(); + } + ~ExceptionInitializer() + { + } +}; + +BOOST_GLOBAL_FIXTURE( ExceptionInitializer ); From ffc6bd52236fb166a8314d39eb43079f0c2a85e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Nov 2018 17:27:33 +0100 Subject: [PATCH 5/8] Use GetPrettyExceptionStr for all unhandled exceptions --- src/dash-cli.cpp | 13 +++---------- src/dash-tx.cpp | 12 +++--------- src/dashd.cpp | 5 +---- src/net_processing.cpp | 7 ++----- src/qt/dash.cpp | 21 ++++++--------------- src/util.cpp | 18 ++++-------------- src/util.h | 8 ++------ 7 files changed, 21 insertions(+), 63 deletions(-) diff --git a/src/dash-cli.cpp b/src/dash-cli.cpp index a179cb6d6c0cf..818b4de4caeed 100644 --- a/src/dash-cli.cpp +++ b/src/dash-cli.cpp @@ -351,7 +351,7 @@ int CommandLineRPC(int argc, char *argv[]) nRet = EXIT_FAILURE; } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRPC()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRPC()"); throw; } @@ -376,23 +376,16 @@ int main(int argc, char* argv[]) int ret = AppInitRPC(argc, argv); if (ret != CONTINUE_EXECUTION) return ret; - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "AppInitRPC()"); - return EXIT_FAILURE; } catch (...) { - PrintExceptionContinue(NULL, "AppInitRPC()"); + PrintExceptionContinue(std::current_exception(), "AppInitRPC()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRPC(argc, argv); - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "CommandLineRPC()"); } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRPC()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRPC()"); } return ret; } diff --git a/src/dash-tx.cpp b/src/dash-tx.cpp index 48e0486701362..5c3348e706c84 100644 --- a/src/dash-tx.cpp +++ b/src/dash-tx.cpp @@ -774,7 +774,7 @@ static int CommandLineRawTx(int argc, char* argv[]) nRet = EXIT_FAILURE; } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRawTx()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRawTx()"); throw; } @@ -797,21 +797,15 @@ int main(int argc, char* argv[]) return ret; } catch (const std::exception& e) { - PrintExceptionContinue(&e, "AppInitRawTx()"); - return EXIT_FAILURE; - } catch (...) { - PrintExceptionContinue(NULL, "AppInitRawTx()"); + PrintExceptionContinue(std::current_exception(), "AppInitRawTx()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRawTx(argc, argv); - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "CommandLineRawTx()"); } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRawTx()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRawTx()"); } return ret; } diff --git a/src/dashd.cpp b/src/dashd.cpp index 658c8e3c1a4f6..048c4749597d1 100644 --- a/src/dashd.cpp +++ b/src/dashd.cpp @@ -173,11 +173,8 @@ bool AppInit(int argc, char* argv[]) } fRet = AppInitMain(threadGroup, scheduler); - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "AppInit()"); } catch (...) { - PrintExceptionContinue(NULL, "AppInit()"); + PrintExceptionContinue(std::current_exception(), "AppInit()"); } if (!fRet) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 7ab7c8863abe1..b68c0bcf03c0b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3085,13 +3085,10 @@ bool ProcessMessages(CNode* pfrom, CConnman& connman, const std::atomic& i } else { - PrintExceptionContinue(&e, "ProcessMessages()"); + PrintExceptionContinue(std::current_exception(), "ProcessMessages()"); } - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "ProcessMessages()"); } catch (...) { - PrintExceptionContinue(NULL, "ProcessMessages()"); + PrintExceptionContinue(std::current_exception(), "ProcessMessages()"); } if (!fRet) { diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 8c6490d4670de..d2ef828471070 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -192,7 +192,7 @@ public Q_SLOTS: CScheduler scheduler; /// Pass fatal exception message to UI thread - void handleRunawayException(const std::exception *e); + void handleRunawayException(const std::exception_ptr e); }; /** Main Dash application object */ @@ -264,7 +264,7 @@ BitcoinCore::BitcoinCore(): { } -void BitcoinCore::handleRunawayException(const std::exception *e) +void BitcoinCore::handleRunawayException(const std::exception_ptr e) { PrintExceptionContinue(e, "Runaway exception"); Q_EMIT runawayException(QString::fromStdString(GetWarnings("gui"))); @@ -292,10 +292,8 @@ void BitcoinCore::initialize() } bool rv = AppInitMain(threadGroup, scheduler); Q_EMIT initializeResult(rv); - } catch (const std::exception& e) { - handleRunawayException(&e); } catch (...) { - handleRunawayException(NULL); + handleRunawayException(std::current_exception()); } } @@ -318,10 +316,8 @@ void BitcoinCore::restart(QStringList args) QProcess::startDetached(QApplication::applicationFilePath(), args); qDebug() << __func__ << ": Restart initiated..."; QApplication::quit(); - } catch (std::exception& e) { - handleRunawayException(&e); } catch (...) { - handleRunawayException(NULL); + handleRunawayException(std::current_exception()); } } } @@ -336,10 +332,8 @@ void BitcoinCore::shutdown() Shutdown(); qDebug() << __func__ << ": Shutdown finished"; Q_EMIT shutdownResult(); - } catch (const std::exception& e) { - handleRunawayException(&e); } catch (...) { - handleRunawayException(NULL); + handleRunawayException(std::current_exception()); } } @@ -735,11 +729,8 @@ int main(int argc, char *argv[]) app.exec(); app.requestShutdown(); app.exec(); - } catch (const std::exception& e) { - PrintExceptionContinue(&e, "Runaway exception"); - app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } catch (...) { - PrintExceptionContinue(NULL, "Runaway exception"); + PrintExceptionContinue(std::current_exception(), "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } return app.getReturnValue(); diff --git a/src/util.cpp b/src/util.cpp index 68e42ab3272f6..f4800678ecb8c 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -15,6 +15,7 @@ #include "ctpl.h" #include "random.h" #include "serialize.h" +#include "stacktraces.h" #include "sync.h" #include "utilstrencodings.h" #include "utiltime.h" @@ -534,23 +535,12 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message std::string("\n\n"); } -static std::string FormatException(const std::exception* pex, const char* pszThread) +static std::string FormatException(const std::exception_ptr pex, const char* pszThread) { -#ifdef WIN32 - char pszModule[MAX_PATH] = ""; - GetModuleFileNameA(NULL, pszModule, sizeof(pszModule)); -#else - const char* pszModule = "dash"; -#endif - if (pex) - return strprintf( - "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); - else - return strprintf( - "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); + return strprintf("EXCEPTION: %s", GetPrettyExceptionStr(pex)); } -void PrintExceptionContinue(const std::exception* pex, const char* pszThread) +void PrintExceptionContinue(const std::exception_ptr pex, const char* pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); diff --git a/src/util.h b/src/util.h index b0e8cba65dcef..7c7978ff9c758 100644 --- a/src/util.h +++ b/src/util.h @@ -124,7 +124,7 @@ bool error(const char* fmt, const Args&... args) return false; } -void PrintExceptionContinue(const std::exception *pex, const char* pszThread); +void PrintExceptionContinue(const std::exception_ptr pex, const char* pszThread); void ParseParameters(int argc, const char*const argv[]); void FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); @@ -266,12 +266,8 @@ template void TraceThread(const char* name, Callable func) LogPrintf("%s thread interrupt\n", name); throw; } - catch (const std::exception& e) { - PrintExceptionContinue(&e, name); - throw; - } catch (...) { - PrintExceptionContinue(NULL, name); + PrintExceptionContinue(std::current_exception(), name); throw; } } From a99d259afc0ac2dc3ef3657722db2840f4ec0beb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Nov 2018 11:59:32 +0100 Subject: [PATCH 6/8] Use --enable-stacktraces in CI for linux32/linux64 --- ci/matrix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/matrix.sh b/ci/matrix.sh index 15e918bc53e4f..38b2dc3c5f12d 100755 --- a/ci/matrix.sh +++ b/ci/matrix.sh @@ -56,7 +56,7 @@ elif [ "$BUILD_TARGET" = "linux32" ]; then export HOST=i686-pc-linux-gnu export PACKAGES="g++-multilib bc python3-zmq" export DEP_OPTS="NO_QT=1" - export BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" + export BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports --enable-stacktraces LDFLAGS=-static-libstdc++" export USE_SHELL="/bin/dash" export PYZMQ=true export RUN_TESTS=true @@ -64,7 +64,7 @@ elif [ "$BUILD_TARGET" = "linux64" ]; then export HOST=x86_64-unknown-linux-gnu export PACKAGES="bc python3-zmq" export DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1" - export BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports" + export BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports --enable-stacktraces" export CPPFLAGS="-DDEBUG_LOCKORDER -DENABLE_DASH_DEBUG" export PYZMQ=true export RUN_TESTS=true From 1a4cac0c43ce931e84a07ec3909e3e512e8256a6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Feb 2019 06:58:04 +0100 Subject: [PATCH 7/8] Catch and print python exceptions when stopping nodes Otherwise the code at the bottom is never executed when nodes crash, leading to no output of debug.log files on Travis. --- qa/rpc-tests/test_framework/test_framework.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 5984b858263e0..b6f354f1387b4 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -175,7 +175,12 @@ def main(self): if not self.options.noshutdown: print("Stopping nodes") - stop_nodes(self.nodes) + try: + stop_nodes(self.nodes) + except BaseException as e: + success = False + print("Unexpected exception caught during shutdown: " + repr(e)) + traceback.print_tb(sys.exc_info()[2]) else: print("Note: dashds were not stopped and may still be running") From ad0b5224c713d94ab59d6a1a8f7ffa197e7d96ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Feb 2019 12:35:13 +0100 Subject: [PATCH 8/8] Remove now unneeded/unused TestCrash methods --- src/stacktraces.cpp | 76 --------------------------------------------- src/stacktraces.h | 2 -- 2 files changed, 78 deletions(-) diff --git a/src/stacktraces.cpp b/src/stacktraces.cpp index d29639b9b4555..c0a52f84123bf 100644 --- a/src/stacktraces.cpp +++ b/src/stacktraces.cpp @@ -681,79 +681,3 @@ void RegisterPrettySignalHandlers() #endif #endif } - -static __attribute__((noinline)) int GetCrashType() -{ - // exclude crash type 5 (stack overflow) as we can't handle it propery in linux (including Windows on Wine) - // If you want to test it, just change this line to return the fixed number 5 - return GetRandInt(5); -} - -static __attribute__((noinline)) int DoCrashRecursiveLoop() -{ - std::string a("1"), b("2"), c("3"), d, e, f; - a += b + c + d + e + f; - if (a.empty()) { - // will never happen - LogPrintf( "%s", a); - return 0; - } - return DoCrashRecursiveLoop(); -} - -static __attribute__((noinline)) void DoCrash2() -{ - static int zero = 0; - static int test = 1; - - int rnd = GetCrashType(); - switch (rnd) { - case 0: - PrintCrashInfo(strprintf("%s: throwing exception!\n", __func__)); - throw std::runtime_error("whoops"); - case 1: - PrintCrashInfo(strprintf("%s: asserting %d==2!\n", __func__, rnd)); - assert(rnd == 2); - break; - case 2: { - PrintCrashInfo(strprintf("%s: accessing invalid memory!\n", __func__)); - int* p = (int*) 0x1; - *p = 1; - break; - } - case 3: { - PrintCrashInfo(strprintf("%s: dividing by zero!\n", __func__)); - test = test / zero; - break; - } - case 4: { - PrintCrashInfo(strprintf("%s: calling std::terminate()!\n", __func__)); - std::terminate(); - break; - } - case 5: { - PrintCrashInfo(strprintf("%s: causing stack overflow!\n", __func__)); - DoCrashRecursiveLoop(); - break; - } - } -}; - -static __attribute__((noinline)) void DoCrash() -{ - DoCrash2(); -} - -void __attribute__((noinline)) TestCrash(bool allowOtherThread) -{ - bool otherThread = allowOtherThread && (GetRandInt(2)) == 0; - - if (otherThread) { - std::thread t(DoCrash); - if (t.joinable()) { - t.join(); - } - } else { - DoCrash(); - } -} diff --git a/src/stacktraces.h b/src/stacktraces.h index 1d34210cbccfa..d2e05e7e9d944 100644 --- a/src/stacktraces.h +++ b/src/stacktraces.h @@ -41,6 +41,4 @@ inline std::string GetExceptionWhat(const T& e) void RegisterPrettyTerminateHander(); void RegisterPrettySignalHandlers(); -void TestCrash(bool allowOtherThread = true); - #endif//DASH_STACKTRACES_H