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 diff --git a/configure.ac b/configure.ac index 0b06fab6d34b7..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,8 +218,34 @@ 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 +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 @@ -1209,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/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 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") 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..818b4de4caeed 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" @@ -350,7 +351,7 @@ int CommandLineRPC(int argc, char *argv[]) nRet = EXIT_FAILURE; } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRPC()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRPC()"); throw; } @@ -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"); @@ -372,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 6b9f49ade0ff4..5c3348e706c84 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; @@ -772,7 +774,7 @@ static int CommandLineRawTx(int argc, char* argv[]) nRet = EXIT_FAILURE; } catch (...) { - PrintExceptionContinue(NULL, "CommandLineRawTx()"); + PrintExceptionContinue(std::current_exception(), "CommandLineRawTx()"); throw; } @@ -784,6 +786,9 @@ static int CommandLineRawTx(int argc, char* argv[]) int main(int argc, char* argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); try { @@ -792,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 69de2b8ebd74f..048c4749597d1 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 @@ -172,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) @@ -195,6 +193,9 @@ bool AppInit(int argc, char* argv[]) int main(int argc, char* argv[]) { + RegisterPrettyTerminateHander(); + RegisterPrettySignalHandlers(); + SetupEnvironment(); // Connect dashd signal handlers 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 cd177d83ba00a..d2ef828471070 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" @@ -191,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 */ @@ -263,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"))); @@ -291,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()); } } @@ -317,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()); } } } @@ -335,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()); } } @@ -568,6 +563,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. @@ -731,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/stacktraces.cpp b/src/stacktraces.cpp new file mode 100644 index 0000000000000..c0a52f84123bf --- /dev/null +++ b/src/stacktraces.cpp @@ -0,0 +1,683 @@ +// 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 +} diff --git a/src/stacktraces.h b/src/stacktraces.h new file mode 100644 index 0000000000000..d2e05e7e9d944 --- /dev/null +++ b/src/stacktraces.h @@ -0,0 +1,44 @@ +// 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(); + +#endif//DASH_STACKTRACES_H 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 ); 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; } }