diff --git a/CMakeLists.txt b/CMakeLists.txt index 7331f0cf6..a974a817d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,10 +196,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/lib ) -if(HAVE_LIBEXECINFO) - list(APPEND base_DEPS execinfo) -endif() - if(UNIX OR CYGWIN) list(APPEND base_OBJS $) endif() @@ -359,6 +355,20 @@ check_include_file_cxx(cxxabi.h HAVE_CXXABI_H) if(HAVE_LIBEXECINFO) set(HAVE_BACKTRACE_SYMBOLS TRUE) + list(APPEND base_DEPS execinfo) +endif() + +if(NOT WIN32) + # boost::stacktrace uses _Unwind_Backtrace which is only exposed if _GNU_SOURCE is defined on most systems + add_definitions(-D_GNU_SOURCE) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS TRUE) +endif() + +if(ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS AND NOT HAVE_BACKTRACE_SYMBOLS) + message(FATAL_ERROR "ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS is set but backtrace_symbols() was not found") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -372,6 +382,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() endif() +if(MSVC) + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "19.20") + message(FATAL_ERROR "Your version of MSVC (${CMAKE_CXX_COMPILER_VERSION}) is too old for building Icinga 2 (MSVC >= 19.20 from Visual Studio 2019 is required).") + endif() +endif() + if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") diff --git a/config.h.cmake b/config.h.cmake index 16fa190f1..3ed2ae46d 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -12,6 +12,7 @@ #cmakedefine HAVE_SYSTEMD #cmakedefine ICINGA2_UNITY_BUILD +#cmakedefine ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS #define ICINGA_CONFIGDIR "${ICINGA2_FULL_CONFIGDIR}" #define ICINGA_DATADIR "${ICINGA2_FULL_DATADIR}" diff --git a/doc/21-development.md b/doc/21-development.md index 7872a3b48..724d156d4 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -1119,6 +1119,7 @@ General: - [function_types](https://www.boost.org/doc/libs/1_66_0/libs/function_types/doc/html/index.html) (header only) - [circular_buffer](https://www.boost.org/doc/libs/1_66_0/doc/html/circular_buffer.html) (header only) - [math](https://www.boost.org/doc/libs/1_66_0/libs/math/doc/html/index.html) (header only) +- [stacktrace](https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace.html) (header only) Events and Runtime: diff --git a/lib/base/application.cpp b/lib/base/application.cpp index e8fd04704..c6b31910d 100644 --- a/lib/base/application.cpp +++ b/lib/base/application.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,14 @@ using namespace icinga; +#ifdef _WIN32 +/* MSVC throws unhandled C++ exceptions as SEH exceptions with this specific error code. + * There seems to be no system header that actually defines this constant. + * See also https://devblogs.microsoft.com/oldnewthing/20160915-00/?p=94316 + */ +#define EXCEPTION_CODE_CXX_EXCEPTION 0xe06d7363 +#endif /* _WIN32 */ + REGISTER_TYPE(Application); boost::signals2::signal Application::OnReopenLogs; @@ -54,6 +63,10 @@ double Application::m_StartTime; bool Application::m_ScriptDebuggerEnabled = false; double Application::m_LastReloadFailed; +#ifdef _WIN32 +static LPTOP_LEVEL_EXCEPTION_FILTER l_DefaultUnhandledExceptionFilter = nullptr; +#endif /* _WIN32 */ + /** * Constructor for the Application class. */ @@ -742,11 +755,12 @@ void Application::SigAbrtHandler(int) Log(LogCritical, "Application") << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'" << "\n"; + ofs << "Caught SIGABRT.\n" + << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; + DisplayInfoMessage(ofs); - StackTrace trace; - ofs << "Stacktrace:" << "\n"; - trace.Print(ofs, 1); + ofs << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n"; DisplayBugMessage(ofs); @@ -849,9 +863,8 @@ void Application::ExceptionHandler() std::ofstream ofs; ofs.open(fname.CStr()); - ofs << "Caught unhandled exception." << "\n" - << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n" - << "\n"; + ofs << "Caught unhandled exception.\n" + << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; DisplayInfoMessage(ofs); @@ -863,8 +876,19 @@ void Application::ExceptionHandler() << "\n" << "Additional information is available in '" << fname << "'" << "\n"; + /* On platforms where HAVE_CXXABI_H is defined, we prefer to print the stack trace that was saved + * when the last exception was thrown. Everywhere else, we do not have this information so we + * collect a stack trace here, which might lack some information, for example when an exception + * is rethrown, but this is still better than nothing. + */ + boost::stacktrace::stacktrace *stack = nullptr; +#ifndef HAVE_CXXABI_H + boost::stacktrace::stacktrace local_stack; + stack = &local_stack; +#endif /* HAVE_CXXABI_H */ + ofs << "\n" - << DiagnosticInformation(ex) + << DiagnosticInformation(ex, true, stack) << "\n"; } @@ -881,6 +905,15 @@ void Application::ExceptionHandler() #ifdef _WIN32 LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi) { + /* If an unhandled C++ exception occurs with both a termination handler (std::set_terminate()) and an unhandled + * SEH filter (SetUnhandledExceptionFilter()) set, the latter one is called. However, our termination handler is + * better suited for dealing with C++ exceptions. In this case, the SEH exception will have a specific code and + * we can just call the default filter function which will take care of calling the termination handler. + */ + if (exi->ExceptionRecord->ExceptionCode == EXCEPTION_CODE_CXX_EXCEPTION) { + return l_DefaultUnhandledExceptionFilter(exi); + } + if (l_InExceptionHandler) return EXCEPTION_CONTINUE_SEARCH; @@ -905,15 +938,20 @@ LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi) Log(LogCritical, "Application") << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'"; + ofs << "Caught unhandled SEH exception.\n" + << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; + DisplayInfoMessage(ofs); - ofs << "Caught unhandled SEH exception." << "\n" - << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n" - << "\n"; + std::ios::fmtflags savedflags(ofs.flags()); + ofs << std::showbase << std::hex + << "\nSEH exception:\n" + << " Code: " << exi->ExceptionRecord->ExceptionCode << "\n" + << " Address: " << exi->ExceptionRecord->ExceptionAddress << "\n" + << " Flags: " << exi->ExceptionRecord->ExceptionFlags << "\n"; + ofs.flags(savedflags); - StackTrace trace(exi); - ofs << "Stacktrace:" << "\n"; - trace.Print(ofs, 1); + ofs << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n"; DisplayBugMessage(ofs); @@ -934,7 +972,7 @@ void Application::InstallExceptionHandlers() sa.sa_handler = &Application::SigAbrtHandler; sigaction(SIGABRT, &sa, nullptr); #else /* _WIN32 */ - SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter); + l_DefaultUnhandledExceptionFilter = SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter); #endif /* _WIN32 */ } diff --git a/lib/base/exception.cpp b/lib/base/exception.cpp index c694b6bf2..2d5331f01 100644 --- a/lib/base/exception.cpp +++ b/lib/base/exception.cpp @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/exception.hpp" +#include "base/stacktrace.hpp" #include #ifdef _WIN32 @@ -13,7 +14,7 @@ using namespace icinga; -static boost::thread_specific_ptr l_LastExceptionStack; +static boost::thread_specific_ptr l_LastExceptionStack; static boost::thread_specific_ptr l_LastExceptionContext; #ifdef HAVE_CXXABI_H @@ -32,6 +33,14 @@ public: #if defined(__GLIBCXX__) || defined(_LIBCPPABI_VERSION) +/** + * Attempts to cast an exception to a destination type + * + * @param obj Exception to be casted + * @param src Type information of obj + * @param dst Information of which type to cast to + * @return Pointer to the exception if the cast is possible, nullptr otherwise + */ inline void *cast_exception(void *obj, const std::type_info *src, const std::type_info *dst) { #ifdef __GLIBCXX__ @@ -92,6 +101,13 @@ void icinga::RethrowUncaughtException() extern "C" void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) { + /* This function overrides an internal function of libstdc++ that is called when a C++ exception is thrown in order + * to capture as much information as possible at that time and then call the original implementation. This + * information includes: + * - stack trace (for later use in DiagnosticInformation) + * - context trace (for later use in DiagnosticInformation) + */ + auto *tinfo = static_cast(pvtinfo); typedef void (*cxa_throw_fn)(void *, std::type_info *, void (*)(void *)) __attribute__((noreturn)); @@ -103,6 +119,7 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) l_LastExceptionDest.reset(new DestCallback(dest)); #endif /* !defined(__GLIBCXX__) && !defined(_WIN32) */ + // resolve symbol to original implementation of __cxa_throw for the call at the end of this function if (real_cxa_throw == nullptr) real_cxa_throw = (cxa_throw_fn)dlsym(RTLD_NEXT, "__cxa_throw"); @@ -112,10 +129,12 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) if (!uex) { #endif /* NO_CAST_EXCEPTION */ - StackTrace stack; + // save the current stack trace in a thread-local variable + boost::stacktrace::stacktrace stack; SetLastExceptionStack(stack); #ifndef NO_CAST_EXCEPTION + // save the current stack trace in the boost exception error info if the exception is a boost::exception if (ex && !boost::get_error_info(*ex)) *ex << StackTraceErrorInfo(stack); } @@ -125,6 +144,7 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) SetLastExceptionContext(context); #ifndef NO_CAST_EXCEPTION + // save the current context trace in the boost exception error info if the exception is a boost::exception if (ex && !boost::get_error_info(*ex)) *ex << ContextTraceErrorInfo(context); #endif /* NO_CAST_EXCEPTION */ @@ -133,14 +153,14 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) } #endif /* HAVE_CXXABI_H */ -StackTrace *icinga::GetLastExceptionStack() +boost::stacktrace::stacktrace *icinga::GetLastExceptionStack() { return l_LastExceptionStack.get(); } -void icinga::SetLastExceptionStack(const StackTrace& trace) +void icinga::SetLastExceptionStack(const boost::stacktrace::stacktrace& trace) { - l_LastExceptionStack.reset(new StackTrace(trace)); + l_LastExceptionStack.reset(new boost::stacktrace::stacktrace(trace)); } ContextTrace *icinga::GetLastExceptionContext() @@ -153,7 +173,7 @@ void icinga::SetLastExceptionContext(const ContextTrace& context) l_LastExceptionContext.reset(new ContextTrace(context)); } -String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, StackTrace *stack, ContextTrace *context) +String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, boost::stacktrace::stacktrace *stack, ContextTrace *context) { std::ostringstream result; @@ -215,34 +235,37 @@ String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, Sta const auto *pex = dynamic_cast(&ex); if (!uex && !pex && verbose) { - const StackTrace *st = boost::get_error_info(ex); - - if (st) { - result << *st; - } else { - result << std::endl; - - if (!stack) - stack = GetLastExceptionStack(); - - if (stack) - result << *stack; + // Print the first of the following stack traces (if any exists) + // 1. stack trace from boost exception error information + const boost::stacktrace::stacktrace *st = boost::get_error_info(ex); + // 2. stack trace explicitly passed as a parameter + if (!st) { + st = stack; + } + // 3. stack trace saved when the last exception was thrown + if (!st) { + st = GetLastExceptionStack(); + } + if (st && !st->empty()) { + result << "\nStacktrace:\n" << StackTraceFormatter(*st); } } + // Print the first of the following context traces (if any exists) + // 1. context trace from boost exception error information const ContextTrace *ct = boost::get_error_info(ex); + // 2. context trace explicitly passed as a parameter + if (!ct) { + ct = context; + } + // 3. context trace saved when the last exception was thrown + if (!ct) { + ct = GetLastExceptionContext(); + } - if (ct) { - result << *ct; - } else { - result << std::endl; - - if (!context) - context = GetLastExceptionContext(); - - if (context) - result << *context; + if (ct && ct->GetLength() > 0) { + result << "\nContext:\n" << *ct; } return result.str(); @@ -250,8 +273,8 @@ String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, Sta String icinga::DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose) { - StackTrace *pt = GetLastExceptionStack(); - StackTrace stack; + boost::stacktrace::stacktrace *pt = GetLastExceptionStack(); + boost::stacktrace::stacktrace stack; ContextTrace *pc = GetLastExceptionContext(); ContextTrace context; diff --git a/lib/base/exception.hpp b/lib/base/exception.hpp index 246c28a78..279b1d036 100644 --- a/lib/base/exception.hpp +++ b/lib/base/exception.hpp @@ -5,7 +5,6 @@ #include "base/i2-base.hpp" #include "base/string.hpp" -#include "base/stacktrace.hpp" #include "base/context.hpp" #include "base/debuginfo.hpp" #include "base/dictionary.hpp" @@ -15,6 +14,7 @@ #include #include #include +#include #ifdef _WIN32 # include @@ -76,15 +76,16 @@ private: Dictionary::Ptr m_DebugHint; }; -StackTrace *GetLastExceptionStack(); -void SetLastExceptionStack(const StackTrace& trace); +boost::stacktrace::stacktrace *GetLastExceptionStack(); +void SetLastExceptionStack(const boost::stacktrace::stacktrace& trace); ContextTrace *GetLastExceptionContext(); void SetLastExceptionContext(const ContextTrace& context); void RethrowUncaughtException(); -typedef boost::error_info StackTraceErrorInfo; +struct errinfo_stacktrace_; +typedef boost::error_info StackTraceErrorInfo; std::string to_string(const StackTraceErrorInfo&); @@ -92,7 +93,27 @@ typedef boost::error_info ContextTraceErrorInfo; std::string to_string(const ContextTraceErrorInfo& e); -String DiagnosticInformation(const std::exception& ex, bool verbose = true, StackTrace *stack = nullptr, ContextTrace *context = nullptr); +/** + * Generate diagnostic information about an exception + * + * The following information is gathered in the result: + * - Exception error message + * - Debug information about the Icinga config if the exception is a ValidationError + * - Stack trace + * - Context trace + * + * Each, stack trace and the context trace, are printed if the they were saved in the boost exception error + * information, are explicitly passed as a parameter, or were stored when the last exception was thrown. If multiple + * of these exist, the first one is used. + * + * @param ex exception to print diagnostic information about + * @param verbose if verbose is set, a stack trace is added + * @param stack optionally supply a stack trace + * @param context optionally supply a context trace + * @return string containing the aforementioned information + */ +String DiagnosticInformation(const std::exception& ex, bool verbose = true, + boost::stacktrace::stacktrace *stack = nullptr, ContextTrace *context = nullptr); String DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose = true); class posix_error : virtual public std::exception, virtual public boost::exception { diff --git a/lib/base/stacktrace.cpp b/lib/base/stacktrace.cpp index 81fdd3380..e3f15ceb7 100644 --- a/lib/base/stacktrace.cpp +++ b/lib/base/stacktrace.cpp @@ -1,8 +1,10 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ +#include #include "base/stacktrace.hpp" -#include "base/utility.hpp" -#include "base/initialize.hpp" +#include +#include +#include #ifdef HAVE_BACKTRACE_SYMBOLS # include @@ -10,130 +12,32 @@ using namespace icinga; -#ifdef _MSC_VER -# pragma optimize("", off) -#endif /* _MSC_VER */ - -StackTrace::StackTrace() +std::ostream &icinga::operator<<(std::ostream &os, const StackTraceFormatter &f) { -#ifdef HAVE_BACKTRACE_SYMBOLS - m_Count = backtrace(m_Frames, sizeof(m_Frames) / sizeof(m_Frames[0])); -#else /* HAVE_BACKTRACE_SYMBOLS */ -# ifdef _WIN32 - m_Count = CaptureStackBackTrace(0, sizeof(m_Frames) / sizeof(m_Frames), m_Frames, nullptr); -# else /* _WIN32 */ - m_Count = 0; -# endif /* _WIN32 */ -#endif /* HAVE_BACKTRACE_SYMBOLS */ -} + /* In most cases, this operator<< just relies on the operator<< for the `boost::stacktrace::stacktrace` wrapped in + * the `StackTraceFormatter`. But as this operator turned out to not work properly on some platforms, there is a + * fallback implementation that can be enabled using the `-DICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS` flag at + * compile time. This will then switch to `backtrace_symbols()` from `` instead of the implementation + * provided by Boost. + */ -#ifdef _MSC_VER -# pragma optimize("", on) -#endif /* _MSC_VER */ + const boost::stacktrace::stacktrace &stack = f.m_Stack; -#ifdef _WIN32 -StackTrace::StackTrace(PEXCEPTION_POINTERS exi) -{ - STACKFRAME64 frame; - int architecture; +#ifdef ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS + std::vector addrs; + addrs.reserve(stack.size()); + std::transform(stack.begin(), stack.end(), std::back_inserter(addrs), [](const boost::stacktrace::frame &f) { + return const_cast(f.address()); + }); -#ifdef _WIN64 - architecture = IMAGE_FILE_MACHINE_AMD64; - - frame.AddrPC.Offset = exi->ContextRecord->Rip; - frame.AddrFrame.Offset = exi->ContextRecord->Rbp; - frame.AddrStack.Offset = exi->ContextRecord->Rsp; -#else /* _WIN64 */ - architecture = IMAGE_FILE_MACHINE_I386; - - frame.AddrPC.Offset = exi->ContextRecord->Eip; - frame.AddrFrame.Offset = exi->ContextRecord->Ebp; - frame.AddrStack.Offset = exi->ContextRecord->Esp; -#endif /* _WIN64 */ - - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Mode = AddrModeFlat; - - m_Count = 0; - - while (StackWalk64(architecture, GetCurrentProcess(), GetCurrentThread(), - &frame, exi->ContextRecord, nullptr, &SymFunctionTableAccess64, - &SymGetModuleBase64, nullptr) && m_Count < sizeof(m_Frames) / sizeof(m_Frames[0])) { - m_Frames[m_Count] = reinterpret_cast(frame.AddrPC.Offset); - m_Count++; + char **symbols = backtrace_symbols(addrs.data(), addrs.size()); + for (size_t i = 0; i < addrs.size(); i++) { + os << std::setw(2) << i << "# " << symbols[i] << std::endl; } -} -#endif /* _WIN32 */ - -#ifdef _WIN32 -INITIALIZE_ONCE([]() { - (void) SymSetOptions(SYMOPT_UNDNAME | SYMOPT_LOAD_LINES); - (void) SymInitialize(GetCurrentProcess(), nullptr, TRUE); -}); -#endif /* _WIN32 */ - -/** - * Prints a stacktrace to the specified stream. - * - * @param fp The stream. - * @param ignoreFrames The number of stackframes to ignore (in addition to - * the one this function is executing in). - * @returns true if the stacktrace was printed, false otherwise. - */ -void StackTrace::Print(std::ostream& fp, int ignoreFrames) const -{ - fp << std::endl; - -#ifndef _WIN32 -# ifdef HAVE_BACKTRACE_SYMBOLS - char **messages = backtrace_symbols(m_Frames, m_Count); - - for (int i = ignoreFrames + 1; i < m_Count && messages; ++i) { - String message = messages[i]; - - char *sym_begin = strchr(messages[i], '('); - - if (sym_begin) { - char *sym_end = strchr(sym_begin, '+'); - - if (sym_end) { - String sym = String(sym_begin + 1, sym_end); - String sym_demangled = Utility::DemangleSymbolName(sym); - - if (sym_demangled.IsEmpty()) - sym_demangled = ""; - - String path = String(messages[i], sym_begin); - - size_t slashp = path.RFind("/"); - - if (slashp != String::NPos) - path = path.SubStr(slashp + 1); - - message = path + ": " + sym_demangled + " (" + String(sym_end); - } - } - - fp << "\t(" << i - ignoreFrames - 1 << ") " << message << std::endl; - } - - std::free(messages); - - fp << std::endl; -# else /* HAVE_BACKTRACE_SYMBOLS */ - fp << "(not available)" << std::endl; -# endif /* HAVE_BACKTRACE_SYMBOLS */ -#else /* _WIN32 */ - for (int i = ignoreFrames + 1; i < m_Count; i++) { - fp << "\t(" << i - ignoreFrames - 1 << "): " << Utility::GetSymbolName(m_Frames[i]) << std::endl; - } -#endif /* _WIN32 */ -} - -std::ostream& icinga::operator<<(std::ostream& stream, const StackTrace& trace) -{ - trace.Print(stream, 1); - - return stream; + std::free(symbols); +#else /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */ + os << stack; +#endif /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */ + + return os; } diff --git a/lib/base/stacktrace.hpp b/lib/base/stacktrace.hpp index 1416d6558..b4a9765f9 100644 --- a/lib/base/stacktrace.hpp +++ b/lib/base/stacktrace.hpp @@ -1,38 +1,31 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ #ifndef STACKTRACE_H #define STACKTRACE_H -#include "base/i2-base.hpp" -#include +#include namespace icinga { /** - * A stacktrace. + * Formatter for `boost::stacktrace::stacktrace` objects * - * @ingroup base + * This class wraps `boost::stacktrace::stacktrace` objects and provides an operator<< + * for printing them to an `std::ostream` in a custom format. */ -class StackTrace -{ +class StackTraceFormatter { public: - StackTrace(); -#ifdef _WIN32 - explicit StackTrace(PEXCEPTION_POINTERS exi); -#endif /* _WIN32 */ - - void Print(std::ostream& fp, int ignoreFrames = 0) const; - - static void StaticInitialize(); + StackTraceFormatter(const boost::stacktrace::stacktrace &stack) : m_Stack(stack) {} private: - void *m_Frames[64]; - int m_Count; + const boost::stacktrace::stacktrace &m_Stack; + + friend std::ostream &operator<<(std::ostream &os, const StackTraceFormatter &f); }; -std::ostream& operator<<(std::ostream& stream, const StackTrace& trace); +std::ostream& operator<<(std::ostream& os, const StackTraceFormatter &f); } -#endif /* UTILITY_H */ +#endif /* STACKTRACE_H */ diff --git a/test/base-stacktrace.cpp b/test/base-stacktrace.cpp index 349c2364a..f0b87e2cf 100644 --- a/test/base-stacktrace.cpp +++ b/test/base-stacktrace.cpp @@ -1,18 +1,72 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ #include "base/stacktrace.hpp" #include using namespace icinga; + +/* If you are reading this, you are probably doing so because this test case just failed. This might happen as it + * heavily depends on platform and compiler behavior. There are two likely causes why this could break: + * + * - Your compiler found new ways to optimize the functions that are called to create a stack, even though we tried + * to disable optimizations using #pragmas for some compilers. If you know a way to disable (more) optimizations for + * your compiler, you can try if this helps. + * + * - Boost fails to resolve symbol names as we've already seen on some platforms. In this case, you can try again + * passing the additional flag `-DICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS=ON` to CMake and see if this helps. + * + * In any case, please report a bug. If you run `make CTEST_OUTPUT_ON_FAILURE=1 test`, the stack trace in question + * should be printed. If it looks somewhat meaningful, you can probably ignore a failure of this test case. + */ + +#pragma GCC push_options +#pragma GCC optimize ("O0") +#pragma clang optimize off +#ifdef _MSVC_VER +#pragma optimize("", off) +#endif /* _MSVC_VER */ + BOOST_AUTO_TEST_SUITE(base_stacktrace) +[[gnu::noinline]] +void stack_test_func_b() +{ + boost::stacktrace::stacktrace stack; + std::ostringstream obuf; + obuf << StackTraceFormatter(stack); + std::string result = obuf.str(); + BOOST_CHECK_MESSAGE(!result.empty(), "stack trace must not be empty"); + size_t pos_a = result.find("stack_test_func_a"); + size_t pos_b = result.find("stack_test_func_b"); + BOOST_CHECK_MESSAGE(pos_a != std::string::npos, "'stack_test_func_a' not found\n\n" << result); + BOOST_CHECK_MESSAGE(pos_b != std::string::npos, "'stack_test_func_b' not found\n\n" << result); + BOOST_CHECK_MESSAGE(pos_a > pos_b, "'stack_test_func_a' must appear after 'stack_test_func_b'\n\n" << result); +} + +[[gnu::noinline]] +void stack_test_func_a() +{ + boost::stacktrace::stacktrace stack; + std::ostringstream obuf; + obuf << StackTraceFormatter(stack); + std::string result = obuf.str(); + BOOST_CHECK_MESSAGE(!result.empty(), "stack trace must not be empty"); + size_t pos_a = result.find("stack_test_func_a"); + BOOST_CHECK_MESSAGE(pos_a != std::string::npos, "'stack_test_func_a' not found\n\n" << result); + + stack_test_func_b(); +} + BOOST_AUTO_TEST_CASE(stacktrace) { - StackTrace st; - std::ostringstream obuf; - obuf << st; - BOOST_CHECK(obuf.str().size() > 0); + stack_test_func_a(); } BOOST_AUTO_TEST_SUITE_END() + +#pragma GCC pop_options +#pragma clang optimize on +#ifdef _MSVC_VER +#pragma optimize("", on) +#endif /* _MSVC_VER */