From d26dc67be6976f1218ed4056a2d10caa6ec671d8 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Thu, 26 Jan 2017 22:21:24 +0300 Subject: [PATCH] Implemented safe dumping and loading --- example/terminate_handler.cpp | 48 ++++-- .../boost/stacktrace/detail/frame_msvc.ipp | 61 ++++---- .../boost/stacktrace/detail/frame_noop.ipp | 38 ++--- .../boost/stacktrace/detail/frame_unwind.ipp | 87 +++++------ include/boost/stacktrace/detail/from_dump.ipp | 35 ----- include/boost/stacktrace/frame.hpp | 30 ++-- include/boost/stacktrace/stacktrace.hpp | 146 ++++++++++++++++-- src/addr2line.cpp | 1 - src/backtrace.cpp | 1 - src/basic.cpp | 1 - src/windbg.cpp | 1 - 11 files changed, 270 insertions(+), 179 deletions(-) delete mode 100644 include/boost/stacktrace/detail/from_dump.ipp diff --git a/example/terminate_handler.cpp b/example/terminate_handler.cpp index d5c841c..8060ed2 100644 --- a/example/terminate_handler.cpp +++ b/example/terminate_handler.cpp @@ -34,7 +34,7 @@ BOOST_NOINLINE void foo(int i) { void my_signal_handler(int signum) { ::signal(signum, SIG_DFL); - boost::stacktrace::this_thread_frames::dump("./backtrace.dump"); + boost::stacktrace::safe_dump_to("./backtrace.dump"); std::_Exit(-1); } //] @@ -48,7 +48,9 @@ void setup_handlers() { #include // std::cerr -#include +#include // std::ifstream +#include +#include int main(int argc, const char* argv[]) { if (argc < 2) { @@ -59,7 +61,7 @@ int main(int argc, const char* argv[]) { boost::filesystem::copy_file(argv[0], command_1, boost::filesystem::copy_option::overwrite_if_exists); command_1 += " 1"; if (std::system(command_1.string().c_str())) { - std::exit(-1); + std::exit(1); } } @@ -69,24 +71,48 @@ int main(int argc, const char* argv[]) { boost::filesystem::copy_file(argv[0], command_2, boost::filesystem::copy_option::overwrite_if_exists); command_2 += " 2"; if (std::system(command_2.string().c_str())) { - std::exit(-2); + std::exit(2); } } return 0; } - switch(argv[1][0]) { - case '1': + if (argv[1][0] == '1') { setup_handlers(); foo(5); - return -11; - case '2': - boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump("./backtrace.dump"); - std::cout << st << std::endl; + return 3; + } + + if (argv[1][0] != '2') { + return 4; + } + + if (!boost::filesystem::exists("./backtrace.dump")) { + if (std::string(argv[0]).find("noop") == std::string::npos) { + return 5; + } + return 0; } - return 3; + + if (boost::filesystem::exists("./backtrace.dump")) { + // there is a backtrace + std::ifstream ifs("./backtrace.dump"); + + boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(ifs); + std::cout << "Previous run crashed: " << st << std::endl; /*<-*/ + + if (!st) { + return 6; + } /*->*/ + // cleaning up + boost::filesystem::remove("./backtrace.dump"); + } + + + return 0; + } diff --git a/include/boost/stacktrace/detail/frame_msvc.ipp b/include/boost/stacktrace/detail/frame_msvc.ipp index 50cb724..e79a411 100644 --- a/include/boost/stacktrace/detail/frame_msvc.ipp +++ b/include/boost/stacktrace/detail/frame_msvc.ipp @@ -236,6 +236,35 @@ std::string to_string(const frame* frames, std::size_t size) { return res; } + +std::size_t this_thread_frames::collect(void** memory, std::size_t size) BOOST_NOEXCEPT { + return ::CaptureStackBackTrace( + 0, + static_cast(size), + memory, + 0 + ); +} + +std::size_t dump(void* fd, void** memory, std::size_t mem_size) BOOST_NOEXCEPT { + if (!::WriteFile(fd, memory, sizeof(void*) * mem_size, 0, 0)) { + return 0; + } + + return mem_size; +} + +std::size_t dump(const char* file, void** memory, std::size_t mem_size) BOOST_NOEXCEPT { + void* const fd = ::CreateFile(file, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (fd == INVALID_HANDLE_VALUE) { + return 0; + } + + const std::size_t size = boost::stacktrace::detail::dump(fd, memory, mem_size); + ::CloseHandle(fd); + return size; +} + } // namespace detail std::string frame::name() const { @@ -291,38 +320,6 @@ std::string to_string(const frame& f) { return res; } - -std::size_t this_thread_frames::collect(void** memory, std::size_t size) BOOST_NOEXCEPT { - return ::CaptureStackBackTrace( - 0, - static_cast(size), - memory, - 0 - ); -} - -std::size_t this_thread_frames::dump(void* fd) BOOST_NOEXCEPT { - BOOST_CONSTEXPR_OR_CONST std::size_t buf_size = boost::stacktrace::detail::max_frames_dump; - BOOST_CONSTEXPR_OR_CONST std::size_t frames_to_skip = 1; - void* buf[buf_size]; - const std::size_t size = boost::stacktrace::this_thread_frames::collect(buf, buf_size); - if (!::WriteFile(fd, buf + frames_to_skip, sizeof(void*) * (size - frames_to_skip), 0, 0)) { - return 0; - } - - return size; -} - -std::size_t this_thread_frames::dump(const char* file) BOOST_NOEXCEPT { - void* const fd = ::CreateFile(file, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (fd == INVALID_HANDLE_VALUE) { - return 0; - } - const std::size_t size = boost::stacktrace::this_thread_frames::dump(fd); - ::CloseHandle(fd); - return size; -} - }} // namespace boost::stacktrace #endif // BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP diff --git a/include/boost/stacktrace/detail/frame_noop.ipp b/include/boost/stacktrace/detail/frame_noop.ipp index ec9546e..c35d304 100644 --- a/include/boost/stacktrace/detail/frame_noop.ipp +++ b/include/boost/stacktrace/detail/frame_noop.ipp @@ -24,6 +24,25 @@ std::size_t from_dump(const char* /*filename*/, void** /*frames*/) { return 0; } +std::size_t this_thread_frames::collect(void** /*memory*/, std::size_t /*size*/) BOOST_NOEXCEPT { + return 0; +} + +#if defined(BOOST_WINDOWS) +std::size_t dump(void* /*fd*/, void** /*memory*/, std::size_t /*size*/) BOOST_NOEXCEPT { + return 0; +} +#else +std::size_t dump(int /*fd*/, void** /*memory*/, std::size_t /*size*/) BOOST_NOEXCEPT { + return 0; +} +#endif + + +std::size_t dump(const char* /*file*/, void** /*memory*/, std::size_t /*size*/) BOOST_NOEXCEPT { + return 0; +} + } // namespace detail std::string frame::name() const { @@ -42,25 +61,6 @@ std::string to_string(const frame& /*f*/) { return std::string(); } -std::size_t this_thread_frames::collect(void** /*memory*/, std::size_t /*size*/) BOOST_NOEXCEPT { - return 0; -} - -#if defined(BOOST_WINDOWS) -std::size_t this_thread_frames::dump(void* /*fd*/) BOOST_NOEXCEPT { - return 0; -} -#else -std::size_t this_thread_frames::dump(int /*fd*/) BOOST_NOEXCEPT { - return 0; -} -#endif - - - -std::size_t this_thread_frames::dump(const char* /*file*/) BOOST_NOEXCEPT { - return 0; -} }} // namespace boost::stacktrace diff --git a/include/boost/stacktrace/detail/frame_unwind.ipp b/include/boost/stacktrace/detail/frame_unwind.ipp index efa4c65..011c645 100644 --- a/include/boost/stacktrace/detail/frame_unwind.ipp +++ b/include/boost/stacktrace/detail/frame_unwind.ipp @@ -100,6 +100,47 @@ std::string to_string(const frame* frames, std::size_t size) { return res; } +std::size_t this_thread_frames::collect(void** memory, std::size_t size) BOOST_NOEXCEPT { + std::size_t frames_count = 0; + if (!size) { + return frames_count; + } + + boost::stacktrace::detail::unwind_state state = { memory, memory + size }; + ::_Unwind_Backtrace(&boost::stacktrace::detail::unwind_callback, &state); + frames_count = state.current - memory; + + if (memory[frames_count - 1] == 0) { + -- frames_count; + } + + return frames_count; +} + + +std::size_t dump(int fd, void** memory, std::size_t size) BOOST_NOEXCEPT { + // We do not retry, because this function must be typically called from signal handler so it's: + // * to scary to continue in case of EINTR + // * EAGAIN or EWOULDBLOCK may occur only in case of O_NONBLOCK is set for fd, + // so it seems that user does not want to block + if (::write(fd, memory, sizeof(void*) * size) == -1) { + return 0; + } + + return size; +} + +std::size_t dump(const char* file, void** memory, std::size_t mem_size) BOOST_NOEXCEPT { + const int fd = ::open(file, O_CREAT | O_WRONLY | O_TRUNC, S_IFREG | S_IWUSR | S_IRUSR); + if (fd == -1) { + return 0; + } + + const std::size_t size = boost::stacktrace::detail::dump(fd, memory, mem_size); + ::close(fd); + return size; +} + } // namespace detail @@ -119,52 +160,6 @@ std::string to_string(const frame& f) { } -std::size_t this_thread_frames::collect(void** memory, std::size_t size) BOOST_NOEXCEPT { - std::size_t frames_count = 0; - if (!size) { - return frames_count; - } - - boost::stacktrace::detail::unwind_state state = { memory, memory + size }; - ::_Unwind_Backtrace(&boost::stacktrace::detail::unwind_callback, &state); - frames_count = state.current - memory; - - if (memory[frames_count - 1] == 0) { - -- frames_count; - } - - return frames_count; -} - - -std::size_t this_thread_frames::dump(int fd) BOOST_NOEXCEPT { - BOOST_CONSTEXPR_OR_CONST std::size_t buf_size = boost::stacktrace::detail::max_frames_dump; - BOOST_CONSTEXPR_OR_CONST std::size_t frames_to_skip = 1; - void* buf[buf_size]; - const std::size_t size = boost::stacktrace::this_thread_frames::collect(buf, buf_size); - - // We do not retry, becase this function must be typically called from signal handler so it's: - // * to scary to continue in case of EINTR - // * EAGAIN or EWOULDBLOCK may occur only in case of O_NONBLOCK is set for fd, - // so it seems that user does not want to block - if (::write(fd, buf + frames_to_skip, sizeof(void*) * (size - frames_to_skip)) == -1) { - return 0; - } - - return size; -} - -std::size_t this_thread_frames::dump(const char* file) BOOST_NOEXCEPT { - const int fd = ::open(file, O_CREAT | O_WRONLY | O_TRUNC, S_IFREG | S_IWUSR | S_IRUSR); - if (fd == -1) { - return 0; - } - - const std::size_t size = boost::stacktrace::this_thread_frames::dump(fd); - ::close(fd); - return size; -} - }} // namespace boost::stacktrace #endif // BOOST_STACKTRACE_DETAIL_FRAME_UNWIND_IPP diff --git a/include/boost/stacktrace/detail/from_dump.ipp b/include/boost/stacktrace/detail/from_dump.ipp deleted file mode 100644 index f9cd77a..0000000 --- a/include/boost/stacktrace/detail/from_dump.ipp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright Antony Polukhin, 2016-2017. -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_STACKTRACE_DETAIL_FROM_DUMP_IPP -#define BOOST_STACKTRACE_DETAIL_FROM_DUMP_IPP - -#include -#ifdef BOOST_HAS_PRAGMA_ONCE -# pragma once -#endif - -#include -#include - -namespace boost { namespace stacktrace { namespace detail { - -std::size_t from_dump(const char* filename, void** frames) { - std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); - std::size_t size = static_cast(ifs.tellg()) / sizeof(void*); - if (size > boost::stacktrace::detail::max_frames_dump) { - size = boost::stacktrace::detail::max_frames_dump; - } - - ifs.seekg(0); - ifs.read(reinterpret_cast(frames), size * sizeof(void*)); - - return size; -} - -}}} // namespace boost::stacktrace::detail - -#endif // BOOST_STACKTRACE_DETAIL_FROM_DUMP_IPP diff --git a/include/boost/stacktrace/frame.hpp b/include/boost/stacktrace/frame.hpp index 41792b5..e946ec8 100644 --- a/include/boost/stacktrace/frame.hpp +++ b/include/boost/stacktrace/frame.hpp @@ -50,6 +50,19 @@ namespace detail { enum helper{ max_frames_dump = 128 }; BOOST_STACKTRACE_FUNCTION std::size_t from_dump(const char* filename, void** frames); + BOOST_STACKTRACE_FUNCTION std::size_t dump(const char* file, void** memory, std::size_t size) BOOST_NOEXCEPT; +#if defined(BOOST_WINDOWS) + BOOST_STACKTRACE_FUNCTION std::size_t dump(void* fd, void** memory, std::size_t size) BOOST_NOEXCEPT; +#else + // POSIX + BOOST_STACKTRACE_FUNCTION std::size_t dump(int fd, void** memory, std::size_t size) BOOST_NOEXCEPT; +#endif + + +struct this_thread_frames { // struct is required to avoid warning about usage of inline+BOOST_NOINLINE + BOOST_NOINLINE BOOST_STACKTRACE_FUNCTION static std::size_t collect(void** memory, std::size_t size) BOOST_NOEXCEPT; +}; + } // namespace detail /// Non-owning class that references the frame information stored inside the boost::stacktrace::stacktrace class. @@ -169,21 +182,6 @@ std::basic_ostream& operator<<(std::basic_ostream # elif defined(BOOST_MSVC) # include -# include # else # include -# include # endif #endif /// @endcond diff --git a/include/boost/stacktrace/stacktrace.hpp b/include/boost/stacktrace/stacktrace.hpp index 91e19c7..77a4a7d 100644 --- a/include/boost/stacktrace/stacktrace.hpp +++ b/include/boost/stacktrace/stacktrace.hpp @@ -49,6 +49,18 @@ class basic_stacktrace { ); } } + + template + static void try_reserve(Vector& v, InIt, InIt, Categ) { + v.reserve(boost::stacktrace::detail::max_frames_dump); + } + + template + static void try_reserve(Vector& v, InIt first, InIt last, std::random_access_iterator_tag) { + const size_type size = static_cast(last - first); + v.reserve(size > 1024 ? 1024 : size); // Dealing with suspiciously big sizes + } + /// @endcond public: @@ -71,9 +83,9 @@ public: /// /// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. /// - /// @param max_depth max stack depth + /// @param max_depth Max call sequence depth to collect. /// - /// @throws Nothing. Note that default construction of allocator may throw, hovewer it is + /// @throws Nothing. Note that default construction of allocator may throw, however it is /// performed outside the constructor and exception in `allocator_type()` would not result in calling `std::terminate`. BOOST_NOINLINE explicit basic_stacktrace(std::size_t max_depth = static_cast(-1), const allocator_type& a = allocator_type()) BOOST_NOEXCEPT : impl_(a) @@ -90,7 +102,7 @@ public: try { { // Fast path without additional allocations void* buffer[buffer_size]; - const std::size_t frames_count = boost::stacktrace::this_thread_frames::collect(buffer, buffer_size); + const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(buffer, buffer_size); if (buffer_size > frames_count || frames_count >= max_depth) { const std::size_t size = (max_depth < frames_count ? max_depth : frames_count); fill(buffer, size); @@ -102,7 +114,7 @@ public: typedef typename Allocator::template rebind::other allocator_void_t; boost::container::vector buf(buffer_size * 2, 0, impl_.get_allocator()); do { - const std::size_t frames_count = boost::stacktrace::this_thread_frames::collect(buf.data(), buf.size()); + const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(buf.data(), buf.size()); if (buf.size() > frames_count || frames_count >= max_depth) { const std::size_t size = (max_depth < frames_count ? max_depth : frames_count); fill(buf.data(), size); @@ -230,23 +242,127 @@ public: return impl_; } - static basic_stacktrace from_dump(const char* file, const allocator_type& a = allocator_type()) { - basic_stacktrace st(0, a); - void* buf[boost::stacktrace::detail::max_frames_dump]; - const std::size_t size = boost::stacktrace::detail::from_dump(file, buf); - st.impl_.reserve(size); - for (std::size_t i = 0; i < size; ++i) { - st.impl_.push_back( - boost::stacktrace::frame(buf[i]) - ); + /// Constructs stacktrace from basic_istreamable that references the dumped stacktrace. + /// + /// @b Complexity: O(N) + template + static basic_stacktrace from_dump(std::basic_istream& in, const allocator_type& a = allocator_type()) { + typedef typename std::basic_istream::pos_type pos_type; + basic_stacktrace ret(0, a); + + // reserving space + const pos_type pos = in.tellg(); + in.seekg(0, in.end); + ret.impl_.reserve(in.tellg() / sizeof(void*)); + in.seekg(pos); + + void* ptr = 0; + while (in.read(reinterpret_cast(&ptr), sizeof(ptr))) { + if (!ptr) { + break; + } + + ret.impl_.emplace_back(ptr); } - return st; + return ret; + } + + /// Constructs stacktrace from data pointed by iterator range. Iterators must have either `void*`, `const void*` or `boost::stacktrace::frame` value_type. + /// + /// @b Complexity: std::distance(first, last) + template + static basic_stacktrace from_dump(InIt first, InIt last, const allocator_type& a = allocator_type()) { + basic_stacktrace ret(0, a); + + try_reserve( + ret.impl_, + first, + last, + typename boost::container::iterator_traits::iterator_category() + ); + + for (; first != last; ++first) { + if (!*first) { + break; + } + + ret.impl_.emplace_back(*first); + } + + return ret; } }; -/// @brief Compares stacktraces for less, order is platform dependant. +/// @brief Stores current function call sequence into the memory. +/// +/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. +/// +/// @b Async-Handler-Safety: Safe. +/// +/// @returns Stored call sequence depth. +/// +/// @param memory Preallocated buffer to store current function call sequence into. Must at least as big as max_frames * sizeof(void*). +/// +/// @param max_frames Max call sequence depth to store. +BOOST_FORCEINLINE std::size_t safe_dump_to(void* memory[], std::size_t max_frames) BOOST_NOEXCEPT { + return boost::stacktrace::detail::this_thread_frames::collect(memory, max_frames); +} + +namespace detail { + template + BOOST_FORCEINLINE std::size_t safe_dump_to_impl(T file) BOOST_NOEXCEPT { + void* buffer[boost::stacktrace::detail::max_frames_dump + 1]; + const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(buffer, boost::stacktrace::detail::max_frames_dump); + buffer[frames_count] = 0; + return boost::stacktrace::detail::dump(file, buffer, frames_count + 1); + } +} + +/// @brief Opens a file and rewrites its content with current function call sequence. +/// +/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. +/// +/// @b Async-Handler-Safety: Safe. +/// +/// @returns Stored call sequence depth. +/// +/// @param file File to store current function call sequence. +BOOST_FORCEINLINE std::size_t safe_dump_to(const char* file) BOOST_NOEXCEPT { + return boost::stacktrace::detail::safe_dump_to_impl(file); +} + +#ifdef BOOST_STACKTRACE_DOXYGEN_INVOKED + +/// @brief Writes into the provided file descriptor the current function call sequence. +/// +/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. +/// +/// @b Async-Handler-Safety: Safe. +/// +/// @returns Stored call sequence depth. +/// +/// @param file File to store current function call sequence. +BOOST_FORCEINLINE std::size_t safe_dump_to(platform_specific fd) BOOST_NOEXCEPT; + +#elif defined(BOOST_WINDOWS) + +BOOST_FORCEINLINE std::size_t safe_dump_to(void* fd) BOOST_NOEXCEPT { + return boost::stacktrace::detail::safe_dump_to_impl(fd); +} + +#else + +// POSIX +BOOST_FORCEINLINE std::size_t safe_dump_to(int fd) BOOST_NOEXCEPT { + return boost::stacktrace::detail::safe_dump_to_impl(fd); +} + +#endif + + +/// @brief Compares stacktraces for less, order is platform dependent. /// /// @b Complexity: Amortized O(1); worst case O(size()) /// diff --git a/src/addr2line.cpp b/src/addr2line.cpp index 558edc2..7360d41 100644 --- a/src/addr2line.cpp +++ b/src/addr2line.cpp @@ -8,4 +8,3 @@ #define BOOST_STACKTRACE_USE_ADDR2LINE #define BOOST_STACKTRACE_LINK #include -#include diff --git a/src/backtrace.cpp b/src/backtrace.cpp index a90db49..b9e9dc8 100644 --- a/src/backtrace.cpp +++ b/src/backtrace.cpp @@ -8,4 +8,3 @@ #define BOOST_STACKTRACE_USE_BACKTRACE #define BOOST_STACKTRACE_LINK #include -#include diff --git a/src/basic.cpp b/src/basic.cpp index 221f312..4d6d928 100644 --- a/src/basic.cpp +++ b/src/basic.cpp @@ -7,4 +7,3 @@ #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS #define BOOST_STACKTRACE_LINK #include -#include diff --git a/src/windbg.cpp b/src/windbg.cpp index d9d9e84..a6be578 100644 --- a/src/windbg.cpp +++ b/src/windbg.cpp @@ -7,4 +7,3 @@ #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS #define BOOST_STACKTRACE_LINK #include -#include