Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify the async-signal-safety guarantees (refs #131) #154

Merged
merged 3 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions doc/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ doxygen autodoc
<doxygen:param>SEARCH_INCLUDES=YES
<doxygen:param>SHORT_NAMES=NO
<doxygen:param>INCLUDE_PATH=../../../
<doxygen:param>"ALIASES= \\
\"asyncsafe=\\xmlonly<link linkend='stacktrace.theoretical_async_signal_safety'>\\endxmlonly Theoretically async signal safe \\xmlonly</link>\\endxmlonly\" \\
"
<doxygen:param>"PREDEFINED=\"stl_type_info=std::type_info\" \\
\"BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()=explicit operator bool() const noexcept;\" \\
\"BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL()=explicit constexpr operator bool() const noexcept;\" \\
Expand All @@ -39,9 +42,9 @@ boostbook standalone
:
stacktrace
:
<xsl:param>boost.root=http://www.boost.org/doc/libs/1_63_0
<xsl:param>boost.root=http\://www.boost.org/doc/libs/1_84_0
# <xsl:param>boost.root=../../../..
<format>pdf:<xsl:param>boost.url.prefix=http://www.boost.org/doc/libs/release/doc/html
<format>pdf:<xsl:param>boost.url.prefix=http\://www.boost.org/doc/libs/release/doc/html
;

###############################################################################
Expand Down
36 changes: 32 additions & 4 deletions doc/stacktrace.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ Previous run crashed:
9# 0x0000000000402209
```

[warning There's a temptation to write a signal handler that prints the stacktrace on `SIGSEGV` or abort. Unfortunately, there's no cross platform way to do that without a risk of deadlocking. Not all the platforms provide means for even getting stacktrace in async signal safe way.
[warning There's a temptation to write a signal handler that prints the stacktrace on `SIGSEGV` or abort. Unfortunately,
there's no cross platform way to do that without a risk of deadlocking.
Not all the platforms provide means for even getting stacktrace in async signal safe way.

Signal handler is often invoked on a separate stack and trash is returned on attempt to get a trace!

Generic recommendation is to *avoid signal handlers! Use* platform specific ways to store and decode *core files*.

See [link stacktrace.theoretical_async_signal_safety "Theoretical async signal safety"] for more info.
]


Expand Down Expand Up @@ -311,9 +315,9 @@ By default Boost.Stacktrace is a header-only library, but you may change that an
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 `-fvisibility=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 `dbgeng.h` to show debug info. May require linking with *ole32* and *dbgeng*. ] [MSVC, MinGW-w64, Intel on Windows] [yes] [no]]
[[['default for MSVC, Intel on Windows, MinGW-w64] / *BOOST_STACKTRACE_USE_WINDBG*] [*boost_stacktrace_windbg*] [ Uses `dbgeng.h` to show debug info, stores the implementation internals in a static varaible protected with mutex. 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 `dbgeng.h` to show debug info and caches internals in TLS for better performance. Useful only for cases when traces are gathered very often. May require linking with *ole32* and *dbgeng*. ] [MSVC, Intel on Windows] [yes] [no]]
[[*BOOST_STACKTRACE_USE_WINDBG_CACHED*] [*boost_stacktrace_windbg_cached*] [ Uses `dbgeng.h` to show debug info and caches implementation internals in TLS for better performance. Useful only for cases when traces are gathered very often. 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[footnote Some *libbacktrace* packages SEGFAULT if there's a concurrent work with the same `backtrace_state` instance. To avoid that issue the Boost.Stacktrace library uses `thread_local` states, unfortunately this may consume a lot of memory if you often create and destroy execution threads in your application. Define *BOOST_STACKTRACE_BACKTRACE_FORCE_STATIC* to force single instance, but make sure that [@https://github.com/boostorg/stacktrace/blob/develop/test/thread_safety_checking.cpp thread_safety_checking.cpp] works well in your setup. ]. *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]]
Expand Down Expand Up @@ -359,7 +363,31 @@ There are multiple ways to deal with that issue if you distribute PDB files alon

[endsect]

[section Acknowledgements]
[section Theoretical async signal safety]

In theory, walking the stack without decoding should be async signal safe.

In practice, it is not:

* Looks like a page fault while dumping the trace on a containerized/virtualized
Windows system has a chance to deadlock. Page fault could happen easily
as we have to write the dump either to memory or to a file.
* On POSIX systems a deadlock could happen if a signal is received when throwing
an exception [@https://github.com/boostorg/stacktrace/issues/131 #131].
Theoretically this could be worked around by bypassing the mutex locking
in C++-runtime at exception throw
([@https://github.com/userver-framework/userver/blob/4246909c99506d3ab34bd130a5154b4acc8e87de/core/src/engine/task/exception_hacks.cpp#L241-L244 sample implementation]
in the 🐙 userver framework).
* `-fomit-frame-pointer` like flags add additional complexity to the stack
walking implementation, which may also negatively affect the signal safety.

As a rule of thumb: do *not* capture stack traces in signal handlers unless you
are absolutely sure in your environment and inspected all of its source codes.


[endsect]

[section Acknowledgments]

In order of helping and advising:

Expand Down
18 changes: 10 additions & 8 deletions include/boost/stacktrace/safe_dump_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
# pragma warning(disable:2196) // warning #2196: routine is both "inline" and "noinline"
#endif

/// @file safe_dump_to.hpp This header contains low-level async-signal-safe functions for dumping call stacks. Dumps are binary serialized arrays of `void*`,
/// so you could read them by using 'od -tx8 -An stacktrace_dump_failename' Linux command or using boost::stacktrace::stacktrace::from_dump functions.
/// @file safe_dump_to.hpp \asyncsafe low-level
/// functions for dumping call stacks. Dumps are binary serialized arrays of `void*`,
/// so you could read them by using 'od -tx8 -An stacktrace_dump_failename'
/// Linux command or using boost::stacktrace::stacktrace::from_dump functions.

namespace boost { namespace stacktrace {

Expand Down Expand Up @@ -84,7 +86,7 @@ struct this_thread_frames { // struct is required to avoid warning about usage o
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame. To get the actually consumed bytes multiply this value by the sizeof(boost::stacktrace::frame::native_frame_ptr_t)
///
Expand All @@ -99,7 +101,7 @@ BOOST_FORCEINLINE std::size_t safe_dump_to(void* memory, std::size_t size) noexc
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame. To get the actually consumed bytes multiply this value by the sizeof(boost::stacktrace::frame::native_frame_ptr_t)
///
Expand All @@ -117,7 +119,7 @@ BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, void* memory, std::
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame.
///
Expand All @@ -130,7 +132,7 @@ BOOST_FORCEINLINE std::size_t safe_dump_to(const char* file) noexcept {
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame.
///
Expand All @@ -149,7 +151,7 @@ BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, std::size_t max_dep
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame.
///
Expand All @@ -160,7 +162,7 @@ BOOST_FORCEINLINE std::size_t safe_dump_to(platform_specific_descriptor fd) noex
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
///
/// @returns Stored call sequence depth including terminating zero frame.
///
Expand Down
44 changes: 22 additions & 22 deletions include/boost/stacktrace/stacktrace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class basic_stacktrace {
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
BOOST_FORCEINLINE basic_stacktrace() noexcept
: impl_()
{
Expand All @@ -146,7 +146,7 @@ class basic_stacktrace {
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
///
/// @param a Allocator that would be passed to underlying storage.
BOOST_FORCEINLINE explicit basic_stacktrace(const allocator_type& a) noexcept
Expand All @@ -159,7 +159,7 @@ class basic_stacktrace {
///
/// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined.
///
/// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
///
/// @param skip How many top calls to skip and do not store in *this.
///
Expand All @@ -177,14 +177,14 @@ class basic_stacktrace {

/// @b Complexity: O(st.size())
///
/// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
basic_stacktrace(const basic_stacktrace& st)
: impl_(st.impl_)
{}

/// @b Complexity: O(st.size())
///
/// @b Async-Handler-Safety: Safe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe.
basic_stacktrace& operator=(const basic_stacktrace& st) {
impl_ = st.impl_;
return *this;
Expand All @@ -193,21 +193,21 @@ class basic_stacktrace {
#ifdef BOOST_STACKTRACE_DOXYGEN_INVOKED
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe if Allocator::deallocate is async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator::deallocate is async signal safe.
~basic_stacktrace() noexcept = default;
#endif

#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe if Allocator construction and copying are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction and copying are async signal safe.
basic_stacktrace(basic_stacktrace&& st) noexcept
: impl_(std::move(st.impl_))
{}

/// @b Complexity: O(st.size())
///
/// @b Async-Handler-Safety: Safe if Allocator construction and copying are async signal safe.
/// @b Async-Handler-Safety: \asyncsafe if Allocator construction and copying are async signal safe.
basic_stacktrace& operator=(basic_stacktrace&& st)
#ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS
noexcept(( std::is_nothrow_move_assignable< std::vector<boost::stacktrace::frame, Allocator> >::value ))
Expand All @@ -224,7 +224,7 @@ class basic_stacktrace {
///
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
size_type size() const noexcept {
return impl_.size();
}
Expand All @@ -236,43 +236,43 @@ class basic_stacktrace {
///
/// @b Complexity: O(1).
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_reference operator[](std::size_t frame_no) const noexcept {
return impl_[frame_no];
}

/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_iterator begin() const noexcept { return impl_.begin(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_iterator cbegin() const noexcept { return impl_.begin(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_iterator end() const noexcept { return impl_.end(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_iterator cend() const noexcept { return impl_.end(); }

/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_reverse_iterator crbegin() const noexcept { return impl_.rbegin(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_reverse_iterator rend() const noexcept { return impl_.rend(); }
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
const_reverse_iterator crend() const noexcept { return impl_.rend(); }


Expand All @@ -281,15 +281,15 @@ class basic_stacktrace {
///
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
constexpr explicit operator bool () const noexcept { return !empty(); }

/// @brief Allows to check that stack trace failed.
/// @returns `true` if `this->size() == 0`
///
/// @b Complexity: O(1)
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
bool empty() const noexcept { return !size(); }

const std::vector<boost::stacktrace::frame, Allocator>& as_vector() const noexcept {
Expand Down Expand Up @@ -398,7 +398,7 @@ class basic_stacktrace {
///
/// @b Complexity: Amortized O(1); worst case O(size())
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
template <class Allocator1, class Allocator2>
bool operator< (const basic_stacktrace<Allocator1>& lhs, const basic_stacktrace<Allocator2>& rhs) noexcept {
return lhs.size() < rhs.size() || (lhs.size() == rhs.size() && lhs.as_vector() < rhs.as_vector());
Expand All @@ -408,7 +408,7 @@ bool operator< (const basic_stacktrace<Allocator1>& lhs, const basic_stacktrace<
///
/// @b Complexity: Amortized O(1); worst case O(size())
///
/// @b Async-Handler-Safety: Safe.
/// @b Async-Handler-Safety: \asyncsafe.
template <class Allocator1, class Allocator2>
bool operator==(const basic_stacktrace<Allocator1>& lhs, const basic_stacktrace<Allocator2>& rhs) noexcept {
return lhs.as_vector() == rhs.as_vector();
Expand Down
Loading