diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index 4f6dc2b7..397d46c4 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -1,11 +1,11 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set (platform_sources plat/default/priority.cpp plat/posix/perms.cpp plat/darwin/thread_role.cpp) + set (platform_sources plat/default/priority.cpp plat/posix/perms.cpp plat/darwin/thread_role.cpp plat/default/debugging.cpp) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") - set (platform_sources plat/windows/priority.cpp plat/windows/perms.cpp plat/windows/registry.cpp plat/windows/thread_role.cpp) + set (platform_sources plat/windows/priority.cpp plat/windows/perms.cpp plat/windows/registry.cpp plat/windows/thread_role.cpp plat/default/debugging.cpp) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - set (platform_sources plat/linux/priority.cpp plat/posix/perms.cpp plat/linux/thread_role.cpp) + set (platform_sources plat/linux/priority.cpp plat/posix/perms.cpp plat/linux/thread_role.cpp plat/linux/debugging.cpp) elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") - set (platform_sources plat/default/priority.cpp plat/posix/perms.cpp plat/freebsd/thread_role.cpp) + set (platform_sources plat/default/priority.cpp plat/posix/perms.cpp plat/freebsd/thread_role.cpp plat/plat/default/debugging.cpp) else () error ("Unknown platform: ${CMAKE_SYSTEM_NAME}") endif () @@ -49,6 +49,7 @@ target_link_libraries (nano_lib crypto_lib blake2 ${CRYPTOPP_LIBRARY} + ${CMAKE_DL_LIBS} Boost::boost) target_compile_definitions(nano_lib diff --git a/nano/lib/plat/default/debugging.cpp b/nano/lib/plat/default/debugging.cpp new file mode 100644 index 00000000..1489fa80 --- /dev/null +++ b/nano/lib/plat/default/debugging.cpp @@ -0,0 +1,5 @@ +#include + +void nano::create_load_memory_address_files () +{ +} diff --git a/nano/lib/plat/linux/debugging.cpp b/nano/lib/plat/linux/debugging.cpp new file mode 100644 index 00000000..2cfeb4e1 --- /dev/null +++ b/nano/lib/plat/linux/debugging.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include + +namespace +{ +// This creates a file for the load address of an executable or shared library. +// Useful for debugging should the virtual addresses be randomized. +int create_load_memory_address_file (dl_phdr_info * info, size_t, void *) +{ + static int counter = 0; + assert (counter <= 99); + // Create filename + const char file_prefix[] = "nano_node_crash_load_address_dump_"; + // Holds the filename prefix, a unique (max 2 digits) number and extension (null terminator is included in file_prefix size) + char filename[sizeof (file_prefix) + 2 + 4]; + snprintf (filename, sizeof (filename), "%s%d.txt", file_prefix, counter); + + // Open file + const auto file_descriptor = ::open (filename, O_CREAT | O_WRONLY | O_TRUNC, +#if defined(S_IWRITE) && defined(S_IREAD) + S_IWRITE | S_IREAD +#else + 0 +#endif + ); + + // Write the name of shared library + ::write (file_descriptor, "Name: ", 6); + ::write (file_descriptor, info->dlpi_name, strlen (info->dlpi_name)); + ::write (file_descriptor, "\n", 1); + + // Write the first load address found + for (auto i = 0; i < info->dlpi_phnum; ++i) + { + if (info->dlpi_phdr[i].p_type == PT_LOAD) + { + auto load_address = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr; + + // Each byte of the pointer is two hexadecimal characters, plus the 0x prefix and null terminator + char load_address_as_hex_str[sizeof (load_address) * 2 + 2 + 1]; + snprintf (load_address_as_hex_str, sizeof (load_address_as_hex_str), "%p", (void *)load_address); + ::write (file_descriptor, load_address_as_hex_str, strlen (load_address_as_hex_str)); + break; + } + } + + ::close (file_descriptor); + ++counter; + return 0; +} +} + +void nano::create_load_memory_address_files () +{ + dl_iterate_phdr (create_load_memory_address_file, nullptr); +} diff --git a/nano/lib/rpcconfig.cpp b/nano/lib/rpcconfig.cpp index 48c8a8b1..00bfe447 100644 --- a/nano/lib/rpcconfig.cpp +++ b/nano/lib/rpcconfig.cpp @@ -117,7 +117,7 @@ std::string get_default_rpc_filepath () boost::system::error_code err; auto running_executable_filepath = boost::dll::program_location (err); - // Construct the nano_rpc excutable file path based on where the currently running executable is found. + // Construct the nano_rpc executable file path based on where the currently running executable is found. auto rpc_filepath = running_executable_filepath.parent_path () / "nano_rpc"; if (running_executable_filepath.has_extension ()) { diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index 637e4ef1..a8c394a0 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -1,7 +1,27 @@ #include +#include + #include +// Some builds (mac) fail due to "Boost.Stacktrace requires `_Unwind_Backtrace` function". +#ifndef _WIN32 +#ifndef _GNU_SOURCE +#define BEFORE_GNU_SOURCE 0 +#define _GNU_SOURCE +#else +#define BEFORE_GNU_SOURCE 1 +#endif +#endif +// On Windows this include defines min/max macros, so keep below other includes +// to reduce conflicts with other std functions +#include +#ifndef _WIN32 +#if !BEFORE_GNU_SOURCE +#undef _GNU_SOURCE +#endif +#endif + namespace nano { seq_con_info_composite::seq_con_info_composite (const std::string & name) : @@ -42,6 +62,11 @@ const seq_con_info & seq_con_info_leaf::get_info () const return info; } +void dump_crash_stacktrace () +{ + boost::stacktrace::safe_dump_to ("nano_node_backtrace.dump"); +} + namespace thread_role { /* @@ -210,6 +235,33 @@ void release_assert_internal (bool check, const char * check_expr, const char * return; } - std::cerr << "Assertion (" << check_expr << ") failed " << file << ":" << line << std::endl; + std::cerr << "Assertion (" << check_expr << ") failed " << file << ":" << line << "\n\n"; + + // Output stack trace to cerr + auto stacktrace = boost::stacktrace::stacktrace (); + std::stringstream ss; + ss << stacktrace; + auto backtrace_str = ss.str (); + std::cerr << backtrace_str << std::endl; + + // "abort" at the end of this function will go into any signal handlers (the daemon ones will generate a stack trace and load memory address files on non-Windows systems). + // As there is no async-signal-safe way to generate stacktraces on Windows so must be done before aborting +#ifdef _WIN32 + { + // Try construct the stacktrace dump in the same folder as the the running executable, otherwise use the current directory. + boost::system::error_code err; + auto running_executable_filepath = boost::dll::program_location (err); + std::string filename = "nano_node_backtrace_release_assert.txt"; + std::string filepath = filename; + if (!err) + { + filepath = (running_executable_filepath.parent_path () / filename).string (); + } + + std::ofstream file (filepath); + nano::set_secure_perm_file (filepath); + file << backtrace_str; + } +#endif abort (); } diff --git a/nano/lib/utility.hpp b/nano/lib/utility.hpp index 770929fd..6c3c140d 100644 --- a/nano/lib/utility.hpp +++ b/nano/lib/utility.hpp @@ -77,6 +77,16 @@ bool is_windows_elevated (); */ bool event_log_reg_entry_exists (); +/* + * Create the load memory addresses for the executable and shared libraries. + */ +void create_load_memory_address_files (); + +/* + * Dumps a stacktrace file which can be read using the --debug_output_last_backtrace_dump CLI command + */ +void dump_crash_stacktrace (); + /* * Functions for understanding the role of the current thread */ diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index 28afead8..b0f67dfa 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -22,89 +22,13 @@ #include #endif -// Some builds (mac) fail due to "Boost.Stacktrace requires `_Unwind_Backtrace` function". -#ifndef _WIN32 -#ifndef _GNU_SOURCE -#define BEFORE_GNU_SOURCE 0 -#define _GNU_SOURCE -#else -#define BEFORE_GNU_SOURCE 1 -#endif -#endif -// On Windows this include defines min/max macros, so keep below other includes -// to reduce conflicts with other std functions -#include -#ifndef _WIN32 -#if !BEFORE_GNU_SOURCE -#undef _GNU_SOURCE -#endif -#endif - namespace { -#ifdef __linux__ - -#include -#include -#include -#include - -// This outputs the load addresses for the executable and shared libraries. -// Useful for debugging should the virtual addresses be randomized. -int output_memory_load_address (dl_phdr_info * info, size_t, void *) -{ - static int counter = 0; - assert (counter <= 99); - // Create filename - const char file_prefix[] = "nano_node_crash_load_address_dump_"; - // Holds the filename prefix, a unique (max 2 digits) number and extension (null terminator is included in file_prefix size) - char filename[sizeof (file_prefix) + 2 + 4]; - snprintf (filename, sizeof (filename), "%s%d.txt", file_prefix, counter); - - // Open file - const auto file_descriptor = ::open (filename, O_CREAT | O_WRONLY | O_TRUNC, -#if defined(S_IWRITE) && defined(S_IREAD) - S_IWRITE | S_IREAD -#else - 0 -#endif - ); - - // Write the name of shared library - ::write (file_descriptor, "Name: ", 6); - ::write (file_descriptor, info->dlpi_name, strlen (info->dlpi_name)); - ::write (file_descriptor, "\n", 1); - - // Write the first load address found - for (auto i = 0; i < info->dlpi_phnum; ++i) - { - if (info->dlpi_phdr[i].p_type == PT_LOAD) - { - auto load_address = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr; - - // Each byte of the pointer is two hexadecimal characters, plus the 0x prefix and null terminator - char load_address_as_hex_str[sizeof (load_address) * 2 + 2 + 1]; - snprintf (load_address_as_hex_str, sizeof (load_address_as_hex_str), "%p", (void *)load_address); - ::write (file_descriptor, load_address_as_hex_str, strlen (load_address_as_hex_str)); - break; - } - } - - ::close (file_descriptor); - ++counter; - return 0; -} -#endif - -// Only async-signal-safe functions are allowed to be called here void my_abort_signal_handler (int signum) { std::signal (signum, SIG_DFL); - boost::stacktrace::safe_dump_to ("nano_node_backtrace.dump"); - -#ifdef __linux__ - dl_iterate_phdr (output_memory_load_address, nullptr); -#endif + nano::dump_crash_stacktrace (); + nano::create_load_memory_address_files (); } }