mirror of https://github.com/Icinga/icinga2.git
Merge pull request #8373 from Icinga/feature/improve-crashlog
Improve crashlog
This commit is contained in:
commit
6048d0e800
|
@ -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")
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#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<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());
|
||||
});
|
||||
|
||||
#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++;
|
||||
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 = "<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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue