348 lines
16 KiB
Plaintext
348 lines
16 KiB
Plaintext
[library Boost.Stacktrace
|
|
[quickbook 1.6]
|
|
[version 1.0]
|
|
[id stacktrace]
|
|
[copyright 2016-2019 Antony Polukhin]
|
|
[category Language Features Emulation]
|
|
[license
|
|
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])
|
|
]
|
|
]
|
|
|
|
[section Motivation]
|
|
How can one display the call sequence in C++? What function called the current function? What call sequence led to an exception?
|
|
|
|
Boost.Stacktrace library is a simple C++03 library that provides information about call sequence in a human-readable form.
|
|
|
|
[endsect]
|
|
|
|
[section Getting Started]
|
|
|
|
[import ../example/assert_handler.cpp]
|
|
[import ../example/terminate_handler.cpp]
|
|
[import ../example/throwing_st.cpp]
|
|
[import ../example/trace_addresses.cpp]
|
|
[import ../example/debug_function.cpp]
|
|
[import ../example/user_config.hpp]
|
|
|
|
[section How to print current call stack]
|
|
|
|
[classref boost::stacktrace::stacktrace] contains methods for working with call-stack/backtraces/stacktraces. Here's a small example:
|
|
```
|
|
#include <boost/stacktrace.hpp>
|
|
|
|
// ... somewhere inside the `bar(int)` function that is called recursively:
|
|
std::cout << boost::stacktrace::stacktrace();
|
|
```
|
|
|
|
In that example:
|
|
|
|
* `boost::stacktrace::` is the namespace that has all the classes and functions to work with stacktraces
|
|
* [classref boost::stacktrace::stacktrace stacktrace()] is the default constructor call; constructor stores the current function call sequence inside the stacktrace class.
|
|
|
|
Code from above will output something like this:
|
|
|
|
```
|
|
0# bar(int) at /path/to/source/file.cpp:70
|
|
1# bar(int) at /path/to/source/file.cpp:70
|
|
2# bar(int) at /path/to/source/file.cpp:70
|
|
3# bar(int) at /path/to/source/file.cpp:70
|
|
4# main at /path/to/main.cpp:93
|
|
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
|
|
6# _start
|
|
```
|
|
|
|
[note By default the Stacktrace library is very conservative in methods to decode stacktrace. If your output does not look as fancy as in example from above, see [link stacktrace.configuration_and_build section "Configuration and Build"] for allowing advanced features of the library. ]
|
|
|
|
|
|
[endsect]
|
|
|
|
[section Handle terminates, aborts 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.
|
|
|
|
`std::terminate` calls `std::abort`, so we need to capture stack traces on Segmentation Faults and Abort signals.
|
|
|
|
[warning Writing a signal handler requires high attention! Only a few system calls allowed in signal handlers, so there's no cross platform way to print a stacktrace without a risk of deadlocking. The only way to deal with the problem - [*dump raw stacktrace into file/socket and parse it on program restart].]
|
|
|
|
[warning Not all the platforms provide means for even getting stacktrace in async signal safe way. No stack trace will be saved on such platforms. ]
|
|
|
|
Let's write a handler to safely dump stacktrace:
|
|
|
|
[getting_started_terminate_handlers]
|
|
|
|
Registering our handler:
|
|
|
|
[getting_started_setup_handlers]
|
|
|
|
At program start we check for a file with stacktrace and if it exist - we're writing it in human readable format:
|
|
|
|
[getting_started_on_program_restart]
|
|
|
|
Now we'll get the following output on `std::terminate` call after the program restarts:
|
|
|
|
```
|
|
Previous run crashed:
|
|
0# 0x00007F2EC0A6A8EF
|
|
1# my_signal_handler(int) at ../example/terminate_handler.cpp:37
|
|
2# 0x00007F2EBFD84CB0
|
|
3# 0x00007F2EBFD84C37
|
|
4# 0x00007F2EBFD88028
|
|
5# 0x00007F2EC0395BBD
|
|
6# 0x00007F2EC0393B96
|
|
7# 0x00007F2EC0393BE1
|
|
8# bar(int) at ../example/terminate_handler.cpp:18
|
|
9# foo(int) at ../example/terminate_handler.cpp:22
|
|
10# bar(int) at ../example/terminate_handler.cpp:14
|
|
11# foo(int) at ../example/terminate_handler.cpp:22
|
|
12# main at ../example/terminate_handler.cpp:84
|
|
13# 0x00007F2EBFD6FF45
|
|
14# 0x0000000000402209
|
|
```
|
|
|
|
[note Function names from shared libraries may not be decoded due to address space layout randomization. Still better than nothing.]
|
|
|
|
[endsect]
|
|
|
|
[section Better asserts]
|
|
|
|
Pretty often assertions provide not enough 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<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul]: Assertion '(i < N)&&("out of range")' failed.
|
|
Aborted (core dumped)
|
|
```
|
|
|
|
That's not enough to locate the problem without debugger. There may be thousand code lines in real world examples and hundred 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<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul; boost::array<T, N>::reference = int&; boost::array<T, N>::size_type = long unsigned int]': out of range.
|
|
Backtrace:
|
|
0# boost::assertion_failed_msg(char const*, char const*, char const*, char const*, long) at ../example/assert_handler.cpp:39
|
|
1# boost::array<int, 5ul>::operator[](unsigned long) at ../../../boost/array.hpp:124
|
|
2# bar(int) at ../example/assert_handler.cpp:17
|
|
3# foo(int) at ../example/assert_handler.cpp:25
|
|
4# bar(int) at ../example/assert_handler.cpp:17
|
|
5# foo(int) at ../example/assert_handler.cpp:25
|
|
6# main at ../example/assert_handler.cpp:54
|
|
7# 0x00007F991FD69F45 in /lib/x86_64-linux-gnu/libc.so.6
|
|
8# 0x0000000000401139
|
|
```
|
|
|
|
Now we do know the steps that led to the assertion and can find the error without debugger.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Exceptions with stacktrace]
|
|
|
|
You can provide more information along with exception by embedding stacktraces into the exception. There are many ways to do that, here's how to doe that using Boost.Exception:
|
|
|
|
* Declare a `boost::error_info` typedef that holds the stacktrace:
|
|
|
|
[getting_started_class_traced]
|
|
|
|
* Write a helper class for throwing any exception with stacktrace:
|
|
|
|
[getting_started_class_with_trace]
|
|
|
|
* Use `throw_with_trace(E);` instead of just `throw E;`:
|
|
|
|
[getting_started_throwing_with_trace]
|
|
|
|
* Process exceptions:
|
|
|
|
[getting_started_catching_trace]
|
|
|
|
Code from above will output:
|
|
|
|
```
|
|
'i' must not be greater than zero in oops()
|
|
0# void throw_with_trace<std::logic_error>(std::logic_error const&) at ../example/throwing_st.cpp:22
|
|
1# oops(int) at ../example/throwing_st.cpp:38
|
|
2# bar(int) at ../example/throwing_st.cpp:54
|
|
3# foo(int) at ../example/throwing_st.cpp:59
|
|
4# bar(int) at ../example/throwing_st.cpp:49
|
|
5# foo(int) at ../example/throwing_st.cpp:59
|
|
6# main at ../example/throwing_st.cpp:76
|
|
7# 0x00007FAC113BEF45 in /lib/x86_64-linux-gnu/libc.so.6
|
|
8# 0x0000000000402ED9
|
|
```
|
|
|
|
[endsect]
|
|
|
|
[section Enabling and disabling stacktraces]
|
|
|
|
At some point arises a requirement to easily enable/disable stacktraces for a whole project. That could be easily achieved.
|
|
|
|
Just define *BOOST_STACKTRACE_LINK* for a whole project. Now you can enable/disable stacktraces by just linking with different libraries:
|
|
|
|
* link with `boost_stacktrace_noop` to disable backtracing
|
|
* link with other `boost_stacktrace_*` libraries
|
|
|
|
|
|
See [link stacktrace.configuration_and_build section "Configuration and Build"] for more info.
|
|
|
|
[endsect]
|
|
|
|
[section Saving stacktraces by specified format]
|
|
|
|
[classref boost::stacktrace::stacktrace] provides access to individual [classref boost::stacktrace::frame frames] of the stacktrace,
|
|
so that you could save stacktrace information in your own format. Consider the example, that saves only function addresses of each frame:
|
|
|
|
[getting_started_trace_addresses]
|
|
|
|
Code from above will output:
|
|
|
|
```
|
|
0x7fbcfd17f6b5,0x400d4a,0x400d61,0x400d61,0x400d61,0x400d61,0x400d77,0x400cbf,0x400dc0,0x7fbcfc82d830,0x400a79,
|
|
```
|
|
|
|
[endsect]
|
|
|
|
[section Getting function information from pointer]
|
|
|
|
[classref boost::stacktrace::frame] provides information about functions. You may construct that class from function pointer and get the function name at runtime:
|
|
|
|
[getting_started_debug_function]
|
|
|
|
Code from above will output:
|
|
|
|
```
|
|
my_signal_handler(int) at boost/libs/stacktrace/example/debug_function.cpp:21
|
|
```
|
|
|
|
[endsect]
|
|
|
|
[section Global control over stacktrace output format]
|
|
|
|
You may override the behavior of default stacktrace output operator by defining the macro from Boost.Config [macroref BOOST_USER_CONFIG] to point to a file like following:
|
|
|
|
[getting_started_user_config]
|
|
|
|
Implementation of `do_stream_st` may be the following:
|
|
|
|
[getting_started_user_config_impl]
|
|
|
|
Code from above will output:
|
|
|
|
```
|
|
Terminate called:
|
|
0# bar(int)
|
|
1# foo(int)
|
|
2# bar(int)
|
|
3# foo(int)
|
|
```
|
|
|
|
[endsect]
|
|
|
|
[/
|
|
[section Store stacktraces into shared memory]
|
|
|
|
There's a way to serialize stacktrace in async safe manner and share that serialized representation with another process. Here's another example with signal handlers.
|
|
|
|
This example is very close to the [link stacktrace.getting_started.handle_terminates_aborts_and_seg "Handle terminates, aborts and Segmentation Faults"], but this time we are dumping stacktrace into shared memory:
|
|
|
|
[getting_started_terminate_handlers_shmem]
|
|
|
|
After registering signal handlers and catching a signal, we may print stacktrace dumps on program restart:
|
|
|
|
[getting_started_on_program_restart_shmem]
|
|
|
|
The program output will be the following:
|
|
|
|
```
|
|
Previous run crashed and left trace in shared memory:
|
|
0# 0x00007FD51C7218EF
|
|
1# my_signal_handler2(int) at ../example/terminate_handler.cpp:68
|
|
2# 0x00007FD51B833CB0
|
|
3# 0x00007FD51B833C37
|
|
4# 0x00007FD51B837028
|
|
5# 0x00007FD51BE44BBD
|
|
6# 0x00007FD51BE42B96
|
|
7# 0x00007FD51BE42BE1
|
|
8# bar(int) at ../example/terminate_handler.cpp:18
|
|
9# foo(int) at ../example/terminate_handler.cpp:22
|
|
10# bar(int) at ../example/terminate_handler.cpp:14
|
|
11# foo(int) at ../example/terminate_handler.cpp:22
|
|
12# run_3(char const**) at ../example/terminate_handler.cpp:152
|
|
13# main at ../example/terminate_handler.cpp:207
|
|
14# 0x00007FD51B81EF45
|
|
15# 0x0000000000402999
|
|
```
|
|
|
|
[endsect]
|
|
]
|
|
|
|
[endsect]
|
|
|
|
[section Configuration and Build]
|
|
|
|
By default Boost.Stacktrace is a header-only library, but you may change that and use the following macros to improve build times or to be able to tune library without recompiling your project:
|
|
[table:linkmacro Link macros
|
|
[[Macro name] [Effect]]
|
|
[[*BOOST_STACKTRACE_LINK*] [Disable header-only build and require linking with shared or static library that contains the tracing implementation. If *BOOST_ALL_DYN_LINK* is defined, then link with shared library.]]
|
|
[[*BOOST_STACKTRACE_DYN_LINK*] [Disable header-only build and require linking with shared library that contains tracing implementation.]]
|
|
]
|
|
|
|
|
|
In header only mode library could be tuned by macro. If one of the link macro from above is defined, you have to manually link with one of the libraries:
|
|
[table:libconfig Config
|
|
[[Macro name or default] [Library] [Effect] [Platforms] [Uses debug information [footnote This will provide more readable backtraces with *source code locations* if the binary is built with debug information.]] [Uses dynamic exports information [footnote This will provide readable function names in backtrace for functions that are exported by the binary. Compiling with `-rdynamic` flag, without `-fisibility=hidden` or marking functions as exported produce a better stacktraces.]] ]
|
|
[[['default for MSVC, Intel on Windows, MinGW-w64] / *BOOST_STACKTRACE_USE_WINDBG*] [*boost_stacktrace_windbg*] [ Uses COM to show debug info. May require linking with *ole32* and *dbgeng*. ] [MSVC, MinGW-w64, Intel on Windows] [yes] [no]]
|
|
[[['default for other platforms]] [*boost_stacktrace_basic*] [Uses compiler intrinsics to collect stacktrace and if possible `::dladdr` to show information about the symbol. Requires linking with *libdl* library on POSIX platforms.] [Any compiler on POSIX or MinGW] [no] [yes]]
|
|
[[*BOOST_STACKTRACE_USE_WINDBG_CACHED*] [*boost_stacktrace_windbg_cached*] [ Uses COM to show debug info and caches COM instances in TLS for better performance. Useful only for cases when traces are gathered very often. [footnote This may affect other components of your program that use COM, because this mode calls the `CoInitializeEx(0, COINIT_MULTITHREADED)` on first use and does not call `::CoUninitialize();` until the current thread is destroyed. ] May require linking with *ole32* and *dbgeng*. ] [MSVC, Intel on Windows] [yes] [no]]
|
|
[[*BOOST_STACKTRACE_USE_BACKTRACE*] [*boost_stacktrace_backtrace*] [Requires linking with *libdl* on POSIX and *libbacktrace* libraries. *libbacktrace* is probably already installed in your system[footnote If you are using Clang with libstdc++ you could get into troubles of including `<backtrace.h>`, because on some platforms Clang does not search for headers in the GCC's include paths and any attempt to add GCC's include path leads to linker errors. To explicitly specify a path to the `<backtrace.h>` header you could define the *BOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE* to a full path to the header. For example on Ubuntu Xenial use the command line option *-DBOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE=</usr/lib/gcc/x86_64-linux-gnu/5/include/backtrace.h>* while building with Clang. ], or built into your compiler.
|
|
|
|
Otherwise (if you are a *MinGW*/*MinGW-w64* user for example) it can be downloaded [@https://github.com/ianlancetaylor/libbacktrace from here] or [@https://github.com/gcc-mirror/gcc/tree/master/libbacktrace from here]. ] [Any compiler on POSIX, or MinGW, or MinGW-w64] [yes] [yes]]
|
|
[[*BOOST_STACKTRACE_USE_ADDR2LINE*] [*boost_stacktrace_addr2line*] [Use *addr2line* program to retrieve stacktrace. Requires linking with *libdl* library and `::fork` system call. Macro *BOOST_STACKTRACE_ADDR2LINE_LOCATION* must be defined to the absolute path to the addr2line executable if it is not located in /usr/bin/addr2line. ] [Any compiler on POSIX] [yes] [yes]]
|
|
[[*BOOST_STACKTRACE_USE_NOOP*] [*boost_stacktrace_noop*] [Use this if you wish to disable backtracing. `stacktrace::size()` with that macro always returns 0. ] [All] [no] [no]]
|
|
]
|
|
|
|
[*Examples:]
|
|
|
|
* if you wish to switch to more powerful implementation on Clang/MinGW and *BOOST_STACKTRACE_LINK* is defined, you just need link with "*-lboost_stacktrace_backtrace -ldl -lbacktrace*" or "*-lboost_stacktrace_addr2line -ldl*"
|
|
* if you wish to disable backtracing and *BOOST_STACKTRACE_LINK* is defined, you just need link with *-lboost_stacktrace_noop*
|
|
* if you wish to disable backtracing and you use the library in header only mode, you just need to define *BOOST_STACKTRACE_USE_NOOP* for the whole project and recompile it
|
|
|
|
[section MinGW and MinGW-w64 specific notes]
|
|
|
|
MinGW-w64 and MinGW (without -w64) users have to install libbacktrace for getting better stacktraces. Follow the instruction:
|
|
|
|
Let's assume that you've installed MinGW into C:\MinGW and downloaded [@https://github.com/ianlancetaylor/libbacktrace libbacktrace sources] into C:\libbacktrace-master
|
|
|
|
* Configure & build libbacktrace from console:
|
|
* C:\MinGW\msys\1.0\bin\sh.exe
|
|
* cd /c/libbacktrace-master
|
|
* ./configure CC=/c/MinGW/bin/gcc.exe CXX=/c/MinGW/bin/g++.exe
|
|
* make
|
|
* ./libtool --mode=install /usr/bin/install -c libbacktrace.la '/c/libbacktrace-master'
|
|
* Add info to the project-config.jam in the Boost folder:
|
|
* using gcc : 6 : "C:\\MinGW\\bin\\g++.exe" : <compileflags>-I"C:\\libbacktrace-master\\" <linkflags>-L"C:\\libbacktrace-master\\" ;
|
|
* Now you can use a header only version by defining *BOOST_STACKTRACE_USE_BACKTRACE* for your project or build the stacktrace library from Boost folder:
|
|
* b2.exe toolset=gcc-6 --with-stacktrace
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|
|
|
|
[section Acknowledgements]
|
|
|
|
In order of helping and advising:
|
|
|
|
* Great thanks to Bjorn Reese for highlighting the async-signal-safety issues.
|
|
* Great thanks to Nat Goodspeed for requesting [classref boost::stacktrace::frame] like class.
|
|
* Great thanks to Niall Douglas for making an initial review, helping with some platforms and giving great hints on library design.
|
|
* Great thanks to all the library reviewers.
|
|
|
|
[endsect]
|
|
|
|
[xinclude autodoc.xml]
|
|
|