From ad1691acd11c7544bc40e653cfe506f6bc51b5e8 Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Thu, 2 Jan 2020 14:17:16 +0000 Subject: [PATCH] CLI command to automate a crash report (#2455) --- nano/lib/utility.cpp | 2 +- nano/nano_node/entry.cpp | 238 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 238 insertions(+), 2 deletions(-) diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index 83bae3e4..1d59f9c1 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -124,7 +124,7 @@ void release_assert_internal (bool check, const char * check_expr, const char * 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 + // As there is no async-signal-safe way to generate stacktraces on Windows it 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. diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 116f78cb..f3ce58af 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -7,10 +7,12 @@ #include #include +#include #include #include #include #include +#include #include @@ -35,6 +37,27 @@ #endif #endif +namespace +{ +class uint64_from_hex // For use with boost::lexical_cast to read hexadecimal strings +{ +public: + uint64_t value; +}; +std::istream & operator>> (std::istream & in, uint64_from_hex & out_val); + +class address_library_pair +{ +public: + uint64_t address; + std::string library; + + address_library_pair (uint64_t address, std::string library); + bool operator< (const address_library_pair & other) const; + bool operator== (const address_library_pair & other) const; +}; +} + int main (int argc, char * const * argv) { nano::set_umask (); @@ -58,6 +81,7 @@ int main (int argc, char * const * argv) ("debug_opencl", "OpenCL work generation") ("debug_profile_kdf", "Profile kdf function") ("debug_output_last_backtrace_dump", "Displays the contents of the latest backtrace in the event of a nano_node crash") + ("debug_generate_crash_report", "Consolidates the nano_node_backtrace.dump file. Requires addr2line installed on Linux") ("debug_sys_logging", "Test the system logger") ("debug_verify_profile", "Profile signature verification") ("debug_verify_profile_batch", "Profile batch signature verification") @@ -76,7 +100,8 @@ int main (int argc, char * const * argv) ("device", boost::program_options::value (), "Defines for OpenCL command") ("threads", boost::program_options::value (), "Defines count for OpenCL command") ("difficulty", boost::program_options::value (), "Defines for OpenCL command, HEX") - ("pow_sleep_interval", boost::program_options::value (), "Defines the amount to sleep inbetween each pow calculation attempt"); + ("pow_sleep_interval", boost::program_options::value (), "Defines the amount to sleep inbetween each pow calculation attempt") + ("address_column", boost::program_options::value (), "Defines which column the addresses are located, 0 indexed (check --debug_output_last_backtrace_dump output)"); // clang-format on nano::add_node_options (description); nano::add_node_flag_options (description); @@ -442,6 +467,193 @@ int main (int argc, char * const * argv) << st << std::endl; } } + else if (vm.count ("debug_generate_crash_report")) + { + if (boost::filesystem::exists ("nano_node_backtrace.dump")) + { + // There is a backtrace, so output the contents + std::ifstream ifs ("nano_node_backtrace.dump"); + boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump (ifs); + + std::string crash_report_filename = "nano_node_crash_report.txt"; + +#if defined(_WIN32) || defined(__APPLE__) + // Only linux has load addresses, so just write the dump to a readable file. + // It's the best we can do to keep consistency. + std::ofstream ofs (crash_report_filename); + ofs << st; +#else + // Read all the nano node files + boost::system::error_code err; + auto running_executable_filepath = boost::dll::program_location (err); + if (!err) + { + auto num = 0; + auto format = boost::format ("nano_node_crash_load_address_dump_%1%.txt"); + std::vector base_addresses; + + // The first one only has the load address + uint64_from_hex base_address; + std::string line; + if (boost::filesystem::exists (boost::str (format % num))) + { + std::getline (std::ifstream (boost::str (format % num)), line); + if (boost::conversion::try_lexical_convert (line, base_address)) + { + base_addresses.emplace_back (base_address.value, running_executable_filepath.string ()); + } + } + ++num; + + // Now do the rest of the files + while (boost::filesystem::exists (boost::str (format % num))) + { + std::ifstream ifs_dump_filename (boost::str (format % num)); + + // 2 lines, the path to the dynamic library followed by the load address + std::string dynamic_lib_path; + std::getline (ifs_dump_filename, dynamic_lib_path); + std::getline (ifs_dump_filename, line); + + if (boost::conversion::try_lexical_convert (line, base_address)) + { + base_addresses.emplace_back (base_address.value, dynamic_lib_path); + } + + ++num; + } + + std::sort (base_addresses.begin (), base_addresses.end ()); + + auto address_column_it = vm.find ("address_column"); + auto column = -1; + if (address_column_it != vm.end ()) + { + if (!boost::conversion::try_lexical_convert (address_column_it->second.as (), column)) + { + std::cerr << "Error: Invalid address column\n"; + result = -1; + } + } + + // Extract the addresses from the dump file. + std::stringstream stacktrace_ss; + stacktrace_ss << st; + std::vector backtrace_addresses; + while (std::getline (stacktrace_ss, line)) + { + std::istringstream iss (line); + std::vector results (std::istream_iterator{ iss }, std::istream_iterator ()); + + if (column != -1) + { + if (column < results.size ()) + { + uint64_from_hex address_hex; + if (boost::conversion::try_lexical_convert (results[column], address_hex)) + { + backtrace_addresses.push_back (address_hex.value); + } + else + { + std::cerr << "Error: Address column does not point to valid addresses\n"; + result = -1; + } + } + else + { + std::cerr << "Error: Address column too high\n"; + result = -1; + } + } + else + { + for (const auto & text : results) + { + uint64_from_hex address_hex; + if (boost::conversion::try_lexical_convert (text, address_hex)) + { + backtrace_addresses.push_back (address_hex.value); + break; + } + } + } + } + + // Recreate the crash report with an empty file + boost::filesystem::remove (crash_report_filename); + { + std::ofstream ofs (crash_report_filename); + nano::set_secure_perm_file (crash_report_filename); + } + + // Hold the results from all addr2line calls, if all fail we can assume that addr2line is not installed, + // and inform the user that it needs installing + std::vector system_codes; + + auto run_addr2line = [&backtrace_addresses, &base_addresses, &system_codes, &crash_report_filename](bool use_relative_addresses) { + for (auto backtrace_address : backtrace_addresses) + { + // Find the closest address to it + for (auto base_address : boost::adaptors::reverse (base_addresses)) + { + if (backtrace_address > base_address.address) + { + // Addresses need to be in hex for addr2line to work + auto address = use_relative_addresses ? backtrace_address - base_address.address : backtrace_address; + std::stringstream ss; + ss << std::uppercase << std::hex << address; + + // Call addr2line to convert the address into something readable. + auto res = std::system (boost::str (boost::format ("addr2line -fCi %1% -e %2% >> %3%") % ss.str () % base_address.library % crash_report_filename).c_str ()); + system_codes.push_back (res); + break; + } + } + } + }; + + // First run addr2line using absolute addresses + run_addr2line (false); + { + std::ofstream ofs (crash_report_filename, std::ios_base::out | std::ios_base::app); + ofs << std::endl << "Using relative addresses:" << std::endl; // Add an empty line to separate the absolute & relative output + } + + // Now run using relative addresses. This will give actual results for other dlls, the results from the nano_node executable. + run_addr2line (true); + + if (std::find (system_codes.begin (), system_codes.end (), 0) == system_codes.end ()) + { + std::cerr << "Error: Check that addr2line is installed and that nano_node_crash_load_address_dump_*.txt files exist." << std::endl; + result = -1; + } + else + { + // Delete the crash dump files. The user won't care about them after this. + num = 0; + while (boost::filesystem::exists (boost::str (format % num))) + { + boost::filesystem::remove (boost::str (format % num)); + ++num; + } + + boost::filesystem::remove ("nano_node_backtrace.dump"); + } + } + else + { + std::cerr << "Error: Could not determine running executable path" << std::endl; + result = -1; + } +#endif + } + else + { + std::cerr << "Error: nano_node_backtrace.dump could not be found"; + result = -1; + } + } else if (vm.count ("debug_verify_profile")) { nano::keypair key; @@ -1129,3 +1341,27 @@ int main (int argc, char * const * argv) } return result; } + +namespace +{ +std::istream & operator>> (std::istream & in, uint64_from_hex & out_val) +{ + in >> std::hex >> out_val.value; + return in; +} + +address_library_pair::address_library_pair (uint64_t address, std::string library) : +address (address), library (library) +{ +} + +bool address_library_pair::operator< (const address_library_pair & other) const +{ + return address < other.address; +} + +bool address_library_pair::operator== (const address_library_pair & other) const +{ + return address == other.address; +} +} \ No newline at end of file