Merge pull request #8373 from Icinga/feature/improve-crashlog

Improve crashlog
This commit is contained in:
Alexander Aleksandrovič Klimov 2021-06-29 17:52:25 +02:00 committed by GitHub
commit 6048d0e800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 252 additions and 201 deletions

View File

@ -203,10 +203,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 $<TARGET_OBJECTS:execvpe>)
endif()
@ -365,6 +361,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")
@ -378,6 +388,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")

View File

@ -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}"

View File

@ -1153,6 +1153,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:

View File

@ -19,6 +19,7 @@
#include <boost/exception/errinfo_api_function.hpp>
#include <boost/exception/errinfo_errno.hpp>
#include <boost/exception/errinfo_file_name.hpp>
#include <boost/stacktrace.hpp>
#include <sstream>
#include <iostream>
#include <fstream>
@ -34,6 +35,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<void ()> Application::OnReopenLogs;
@ -55,6 +64,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.
*/
@ -746,11 +759,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);
@ -853,9 +867,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);
@ -867,8 +880,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";
}
@ -885,6 +909,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;
@ -909,15 +942,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);
@ -938,7 +976,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 */
}

View File

@ -1,6 +1,7 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "base/exception.hpp"
#include "base/stacktrace.hpp"
#include <boost/thread/tss.hpp>
#include <utility>
@ -14,7 +15,7 @@
using namespace icinga;
static boost::thread_specific_ptr<StackTrace> l_LastExceptionStack;
static boost::thread_specific_ptr<boost::stacktrace::stacktrace> l_LastExceptionStack;
static boost::thread_specific_ptr<ContextTrace> l_LastExceptionContext;
#ifdef HAVE_CXXABI_H
@ -33,6 +34,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__
@ -93,6 +102,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<std::type_info *>(pvtinfo);
typedef void (*cxa_throw_fn)(void *, std::type_info *, void (*)(void *)) __attribute__((noreturn));
@ -104,6 +120,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");
@ -113,10 +130,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<StackTraceErrorInfo>(*ex))
*ex << StackTraceErrorInfo(stack);
}
@ -126,6 +145,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<ContextTraceErrorInfo>(*ex))
*ex << ContextTraceErrorInfo(context);
#endif /* NO_CAST_EXCEPTION */
@ -134,14 +154,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()
@ -154,7 +174,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;
@ -216,34 +236,37 @@ String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, Sta
const auto *pex = dynamic_cast<const posix_error *>(&ex);
if (!uex && !pex && verbose) {
const StackTrace *st = boost::get_error_info<StackTraceErrorInfo>(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<StackTraceErrorInfo>(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<ContextTraceErrorInfo>(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();
@ -251,8 +274,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;

View File

@ -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 <boost/exception/errinfo_file_name.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>
#include <boost/stacktrace.hpp>
#ifdef _WIN32
# include <boost/algorithm/string/trim.hpp>
@ -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<StackTrace, StackTrace> StackTraceErrorInfo;
struct errinfo_stacktrace_;
typedef boost::error_info<struct errinfo_stacktrace_, boost::stacktrace::stacktrace> StackTraceErrorInfo;
std::string to_string(const StackTraceErrorInfo&);
@ -92,7 +93,27 @@ typedef boost::error_info<ContextTrace, ContextTrace> 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 {

View File

@ -1,8 +1,10 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include <base/i2-base.hpp>
#include "base/stacktrace.hpp"
#include "base/utility.hpp"
#include "base/initialize.hpp"
#include <iostream>
#include <iomanip>
#include <vector>
#ifdef HAVE_BACKTRACE_SYMBOLS
# include <execinfo.h>
@ -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 */
}
#ifdef _MSC_VER
# pragma optimize("", on)
#endif /* _MSC_VER */
#ifdef _WIN32
StackTrace::StackTrace(PEXCEPTION_POINTERS exi)
{
STACKFRAME64 frame;
int architecture;
#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<void *>(frame.AddrPC.Offset);
m_Count++;
}
}
#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.
/* 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 `<execinfo.h>` instead of the implementation
* provided by Boost.
*/
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);
const boost::stacktrace::stacktrace &stack = f.m_Stack;
for (int i = ignoreFrames + 1; i < m_Count && messages; ++i) {
String message = messages[i];
#ifdef ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS
std::vector<void *> addrs;
addrs.reserve(stack.size());
std::transform(stack.begin(), stack.end(), std::back_inserter(addrs), [](const boost::stacktrace::frame &f) {
return const_cast<void *>(f.address());
});
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 = "<unknown function>";
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);
}
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;
}
std::free(symbols);
#else /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */
os << stack;
#endif /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */
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;
return os;
}

View File

@ -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 <iosfwd>
#include <boost/stacktrace.hpp>
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 */

View File

@ -1,18 +1,72 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/stacktrace.hpp"
#include <BoostTestTargetConfig.h>
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 */