diff --git a/doc/stacktrace.qbk b/doc/stacktrace.qbk index e422671..32de8ea 100644 --- a/doc/stacktrace.qbk +++ b/doc/stacktrace.qbk @@ -23,6 +23,166 @@ Boost.Stacktrace library is a simple library that provides information about cal [section Getting Started] +[import ../example/getting_started.cpp] +[import ../example/throwing_st.cpp] + + +[section Better asserts] + +Pretty often assertions provide not enougth information to locate the problem. For example you can see the following message on out-of-range access: + +``` +../../../boost/array.hpp:123: T& boost::array::operator[](boost::array::size_type) [with T = int; long unsigned int N = 5ul]: Assertion '(i < N)&&("out of range")' failed. +Aborted (core dumped) +``` + +That's not enought to locate the problem without debugger. There may be thousends code lines in real world examples and hundrets places where that assertion could happen. Let's try to improve the assertions, and make them more informative: + +[getting_started_assert_handlers] + +We've defined the `BOOST_ENABLE_ASSERT_DEBUG_HANDLER` macro for the whole project. Now all the `BOOST_ASSERT` and `BOOST_ASSERT_MSG` will call our functions `assertion_failed` and `assertion_failed_msg` in case of failure. In `assertion_failed_msg` we output information that was provided by the assertion macro and [classref boost::stacktrace::stacktrace]: + +``` +Expression 'i < N' is false in function 'T& boost::array::operator[](boost::array::size_type) [with T = int; long unsigned int N = 5ul]': out of range. +Backtrace: + 0# boost::assertion_failed_msg(char const*, char const*, char const*, char const*, long) + 1# boost::array::operator[](unsigned long) + 2# oops(unsigned long) + 3# bar(int) + 4# foo(int) + 5# bar(int) + 6# foo(int) + 7# bar(int) + 8# foo(int) + 9# bar(int) +10# foo(int) +11# bar(int) +12# foo(int) +13# bar(int) +14# foo(int) +15# main +16# __libc_start_main +17# _start +18# ?? +``` + +Now we do know the steps that lead to the assertion and could find the error without debugger. + +[endsect] + +[section Handle Terminates and Segmentation Faults] + +Segmentation Faults and `std::terminate` calls sometimes happen in programs. Programmers usually wish to get as much information as possible on such incidents, so having a stacktrace will significantly improve debugging and fixing. + +To deal with Segmentation Faults and `std::terminate` calls we would need to write handlers: + +[getting_started_terminate_handlers] + +After that we can set them as a default handlers and get some more information on incidents: + +[getting_started_setup_handlers] + +Now we'll get the following output on `std::terminate` call: + +``` +Terminate called: + 0# my_terminate_handler() + 1# std::rethrow_exception(std::__exception_ptr::exception_ptr) + 2# std::terminate() + 3# bar(int) + 4# bar(int) + 5# foo(int) + 6# bar(int) + 7# foo(int) + 8# bar(int) + 9# foo(int) +10# bar(int) +11# foo(int) +12# bar(int) +13# foo(int) +14# bar(int) +15# foo(int) +16# main +17# __libc_start_main +18# _start +19# ?? +``` +And the following output on Segmentation Fault: + +``` +Signal 11, backtrace: + 0# my_signal_handler(int) + 1# killpg + 2# ?? + 3# bar(int) + 4# foo(int) + 5# bar(int) + 6# foo(int) + 7# bar(int) + 8# foo(int) + 9# bar(int) +10# foo(int) +11# bar(int) +12# foo(int) +13# bar(int) +14# foo(int) +15# main +16# __libc_start_main +17# _start +18# ?? +``` + +The output stacktrace may be corrupted by previous actions. But now at least some basic information is available to work with. + +[endsect] + +[section Exceptions with Stacktrace] + +You can provide more information along with exception by embedding stacktraces into the exception. For that we will need: + +* Basic class that holds the stacktrace: + +[getting_started_class_traced] + +* Helper class for appending class `traced` to any exception: + +[getting_started_class_with_trace] + +* Throw `with_trace` instead of just `Exception`: + +[getting_started_throwing_with_trace] + +* Catch exceptions by `traced`: + +[getting_started_catching_trace] + +Code from above will output: + +``` +'i' must be less than 4 in oops() +Backtrace: + 0# with_trace::with_trace(char const (&) [34]) + 1# oops(unsigned long) + 2# bar(int) + 3# foo(int) + 4# bar(int) + 5# foo(int) + 6# bar(int) + 7# foo(int) + 8# bar(int) + 9# foo(int) +10# bar(int) +11# foo(int) +12# bar(int) +13# foo(int) +14# main +15# __libc_start_main +16# _start +17# ?? +``` + +[endsect] + [endsect] diff --git a/example/getting_started.cpp b/example/getting_started.cpp index fd4b222..da2296a 100644 --- a/example/getting_started.cpp +++ b/example/getting_started.cpp @@ -10,6 +10,9 @@ BOOST_NOINLINE void foo(int i); BOOST_NOINLINE void bar(int i); BOOST_NOINLINE void oops(std::size_t i) { + // std::terminate(); + // void (*p)() = 0; p(); + boost::array a = {{0, 1, 2, 3, 4}}; foo(a[i]); } @@ -58,10 +61,11 @@ Backtrace: 18# ?? */ +//[getting_started_assert_handlers + // BOOST_ENABLE_ASSERT_DEBUG_HANDLER is defined for the whole project -#include -#include -#include +#include // std::logic_error +#include // std::cerr #include namespace boost { @@ -75,18 +79,20 @@ namespace boost { ::boost::assertion_failed_msg(expr, 0 /*nullptr*/, function, file, line); } } // namespace boost - +//] //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include // std::set_terminate +//[getting_started_terminate_handlers + +#include // std::set_terminate, std::abort #include // ::signal #include -#include +#include // std::cerr void my_terminate_handler() { - std::cerr << "Terminate called: " << boost::stacktrace::stacktrace() << '\n'; + std::cerr << "Terminate called:\n" << boost::stacktrace::stacktrace() << '\n'; std::abort(); } @@ -94,20 +100,17 @@ void my_signal_handler(int signum) { std::cerr << "Signal " << signum << ", backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; std::abort(); } - +//] void setup_handlers() { +//[getting_started_setup_handlers std::set_terminate(&my_terminate_handler); ::signal(SIGSEGV, &my_signal_handler); +//] } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include -typedef boost::error_info stacktrace_info; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #include int main() { setup_handlers(); diff --git a/example/throwing_st.cpp b/example/throwing_st.cpp new file mode 100644 index 0000000..3a41814 --- /dev/null +++ b/example/throwing_st.cpp @@ -0,0 +1,93 @@ +// Copyright Antony Polukhin, 2016. +// +// 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) + +#include + +#if defined(BOOST_NO_CXX11_NOEXCEPT) || defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_NO_CXX11_RVALUE_REFERENCES) + +int main(){} + +#else + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//[getting_started_class_traced +#include + +struct traced { + const boost::stacktrace::stacktrace trace; + + virtual const char* what() const noexcept = 0; + virtual ~traced(){} +}; +//] + +//[getting_started_class_with_trace +template +struct with_trace : public Exception, public traced { + template + with_trace(Args&&... args) + : Exception(std::forward(args)...) + {} + + const char* what() const noexcept { + return Exception::what(); + } +}; +//] + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +BOOST_NOINLINE void oops(std::size_t i); +BOOST_NOINLINE void foo(int i); +BOOST_NOINLINE void bar(int i); + +#include +BOOST_NOINLINE void oops(std::size_t i) { + //[getting_started_throwing_with_trace + if (i >= 4) + throw with_trace("'i' must be less than 4 in oops()"); + if (i == 0) + throw with_trace("'i' must not be zero in oops()"); + //] + foo(i); +} + +#include +BOOST_NOINLINE void bar(int i) { + boost::array a = {{0, 1, 2, 3, 4}}; + if (i < 5) { + if (i >= 0) { + foo(a[i]); + } else { + oops(i); + } + } +} + +BOOST_NOINLINE void foo(int i) { + bar(--i); +} + +#include +int main() { + + //[getting_started_catching_trace + try { + foo(5); // testing assert handler + } catch (const traced& e) { + std::cerr << e.what() << '\n'; + if (e.trace) { + std::cerr << "Backtrace:\n" << e.trace << '\n'; + } + } catch (const std::exception& e) { + std::cerr << e.what() << '\n'; + } + //] +} + +#endif + diff --git a/include/boost/stacktrace.hpp b/include/boost/stacktrace.hpp index d7779cd..142596b 100644 --- a/include/boost/stacktrace.hpp +++ b/include/boost/stacktrace.hpp @@ -13,6 +13,7 @@ #endif #include +#include #include #include @@ -74,6 +75,12 @@ public: /// /// @b Complexity: Amortized O(1), O(1) for noop backend. BOOST_STACKTRACE_FUNCTION std::string operator[](std::size_t frame) const; + + bool operator!() const BOOST_NOEXCEPT { + return !size(); + } + + BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT() }; diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 41cf128..9e1bd39 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -44,7 +44,7 @@ test-suite stacktrace_tests [ run test_noop.cpp test_impl.cpp : : : BOOST_STACKTRACE_USE_NOOP $(NOOP_DEPS) : noop_ho ] [ run test.cpp test_impl.cpp : : : $(AUTO_DEPS) : autodetect_ho ] - # Test wih shared linked backends + # Test with shared linked backends [ run test.cpp : : : .//test_impl_lib_libunwind $(LINKSHARED_UNW) : libunwind_lib ] [ run test.cpp : : : .//test_impl_lib_backtrace $(LINKSHARED_BT) : backtrace_lib ] [ run test.cpp : : : .//test_impl_lib_windbg $(LINKSHARED_WIND) : windbg_lib ] @@ -55,8 +55,13 @@ test-suite stacktrace_tests [ run ../example/getting_started.cpp : : : BOOST_ENABLE_ASSERT_DEBUG_HANDLER $(LINKSHARED_BT) : backtrace_getting_started ] [ run ../example/getting_started.cpp : : : BOOST_ENABLE_ASSERT_DEBUG_HANDLER $(LINKSHARED_WIND) : windbg_getting_started ] [ run ../example/getting_started.cpp : : : BOOST_ENABLE_ASSERT_DEBUG_HANDLER $(LINKSHARED_NOOP) : noop_getting_started ] - [ run ../example/getting_started.cpp : : : BOOST_ENABLE_ASSERT_DEBUG_HANDLER $(AUTO_DEPS) : autodetect_getting_started ] + + [ run ../example/throwing_st.cpp : : : $(LINKSHARED_UNW) : libunwind_throwing_st ] + [ run ../example/throwing_st.cpp : : : $(LINKSHARED_BT) : backtrace_throwing_st ] + [ run ../example/throwing_st.cpp : : : $(LINKSHARED_WIND) : windbg_throwing_st ] + [ run ../example/throwing_st.cpp : : : $(LINKSHARED_NOOP) : noop_throwing_st ] + [ run ../example/throwing_st.cpp : : : $(AUTO_DEPS) : autodetect_throwing_st ] ;