mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-25 06:34:42 +02:00
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
|
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/lib
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_LIBEXECINFO)
|
|
||||||
list(APPEND base_DEPS execinfo)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(UNIX OR CYGWIN)
|
if(UNIX OR CYGWIN)
|
||||||
list(APPEND base_OBJS $<TARGET_OBJECTS:execvpe>)
|
list(APPEND base_OBJS $<TARGET_OBJECTS:execvpe>)
|
||||||
endif()
|
endif()
|
||||||
@ -365,6 +361,20 @@ check_include_file_cxx(cxxabi.h HAVE_CXXABI_H)
|
|||||||
|
|
||||||
if(HAVE_LIBEXECINFO)
|
if(HAVE_LIBEXECINFO)
|
||||||
set(HAVE_BACKTRACE_SYMBOLS TRUE)
|
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()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
@ -378,6 +388,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|||||||
endif()
|
endif()
|
||||||
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)
|
if(NOT MSVC)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#cmakedefine HAVE_SYSTEMD
|
#cmakedefine HAVE_SYSTEMD
|
||||||
|
|
||||||
#cmakedefine ICINGA2_UNITY_BUILD
|
#cmakedefine ICINGA2_UNITY_BUILD
|
||||||
|
#cmakedefine ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS
|
||||||
|
|
||||||
#define ICINGA_CONFIGDIR "${ICINGA2_FULL_CONFIGDIR}"
|
#define ICINGA_CONFIGDIR "${ICINGA2_FULL_CONFIGDIR}"
|
||||||
#define ICINGA_DATADIR "${ICINGA2_FULL_DATADIR}"
|
#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)
|
- [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)
|
- [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)
|
- [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:
|
Events and Runtime:
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <boost/exception/errinfo_api_function.hpp>
|
#include <boost/exception/errinfo_api_function.hpp>
|
||||||
#include <boost/exception/errinfo_errno.hpp>
|
#include <boost/exception/errinfo_errno.hpp>
|
||||||
#include <boost/exception/errinfo_file_name.hpp>
|
#include <boost/exception/errinfo_file_name.hpp>
|
||||||
|
#include <boost/stacktrace.hpp>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -34,6 +35,14 @@
|
|||||||
|
|
||||||
using namespace icinga;
|
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);
|
REGISTER_TYPE(Application);
|
||||||
|
|
||||||
boost::signals2::signal<void ()> Application::OnReopenLogs;
|
boost::signals2::signal<void ()> Application::OnReopenLogs;
|
||||||
@ -55,6 +64,10 @@ double Application::m_StartTime;
|
|||||||
bool Application::m_ScriptDebuggerEnabled = false;
|
bool Application::m_ScriptDebuggerEnabled = false;
|
||||||
double Application::m_LastReloadFailed;
|
double Application::m_LastReloadFailed;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static LPTOP_LEVEL_EXCEPTION_FILTER l_DefaultUnhandledExceptionFilter = nullptr;
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the Application class.
|
* Constructor for the Application class.
|
||||||
*/
|
*/
|
||||||
@ -746,11 +759,12 @@ void Application::SigAbrtHandler(int)
|
|||||||
Log(LogCritical, "Application")
|
Log(LogCritical, "Application")
|
||||||
<< "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'" << "\n";
|
<< "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);
|
DisplayInfoMessage(ofs);
|
||||||
|
|
||||||
StackTrace trace;
|
ofs << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n";
|
||||||
ofs << "Stacktrace:" << "\n";
|
|
||||||
trace.Print(ofs, 1);
|
|
||||||
|
|
||||||
DisplayBugMessage(ofs);
|
DisplayBugMessage(ofs);
|
||||||
|
|
||||||
@ -853,9 +867,8 @@ void Application::ExceptionHandler()
|
|||||||
std::ofstream ofs;
|
std::ofstream ofs;
|
||||||
ofs.open(fname.CStr());
|
ofs.open(fname.CStr());
|
||||||
|
|
||||||
ofs << "Caught unhandled exception." << "\n"
|
ofs << "Caught unhandled exception.\n"
|
||||||
<< "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n"
|
<< "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n";
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
DisplayInfoMessage(ofs);
|
DisplayInfoMessage(ofs);
|
||||||
|
|
||||||
@ -867,8 +880,19 @@ void Application::ExceptionHandler()
|
|||||||
<< "\n"
|
<< "\n"
|
||||||
<< "Additional information is available in '" << fname << "'" << "\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"
|
ofs << "\n"
|
||||||
<< DiagnosticInformation(ex)
|
<< DiagnosticInformation(ex, true, stack)
|
||||||
<< "\n";
|
<< "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,6 +909,15 @@ void Application::ExceptionHandler()
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi)
|
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)
|
if (l_InExceptionHandler)
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
|
||||||
@ -909,15 +942,20 @@ LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi)
|
|||||||
Log(LogCritical, "Application")
|
Log(LogCritical, "Application")
|
||||||
<< "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'";
|
<< "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);
|
DisplayInfoMessage(ofs);
|
||||||
|
|
||||||
ofs << "Caught unhandled SEH exception." << "\n"
|
std::ios::fmtflags savedflags(ofs.flags());
|
||||||
<< "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n"
|
ofs << std::showbase << std::hex
|
||||||
<< "\n";
|
<< "\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 << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n";
|
||||||
ofs << "Stacktrace:" << "\n";
|
|
||||||
trace.Print(ofs, 1);
|
|
||||||
|
|
||||||
DisplayBugMessage(ofs);
|
DisplayBugMessage(ofs);
|
||||||
|
|
||||||
@ -938,7 +976,7 @@ void Application::InstallExceptionHandlers()
|
|||||||
sa.sa_handler = &Application::SigAbrtHandler;
|
sa.sa_handler = &Application::SigAbrtHandler;
|
||||||
sigaction(SIGABRT, &sa, nullptr);
|
sigaction(SIGABRT, &sa, nullptr);
|
||||||
#else /* _WIN32 */
|
#else /* _WIN32 */
|
||||||
SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter);
|
l_DefaultUnhandledExceptionFilter = SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter);
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
||||||
|
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
|
#include "base/stacktrace.hpp"
|
||||||
#include <boost/thread/tss.hpp>
|
#include <boost/thread/tss.hpp>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -14,7 +15,7 @@
|
|||||||
|
|
||||||
using namespace icinga;
|
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;
|
static boost::thread_specific_ptr<ContextTrace> l_LastExceptionContext;
|
||||||
|
|
||||||
#ifdef HAVE_CXXABI_H
|
#ifdef HAVE_CXXABI_H
|
||||||
@ -33,6 +34,14 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
#if defined(__GLIBCXX__) || defined(_LIBCPPABI_VERSION)
|
#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)
|
inline void *cast_exception(void *obj, const std::type_info *src, const std::type_info *dst)
|
||||||
{
|
{
|
||||||
#ifdef __GLIBCXX__
|
#ifdef __GLIBCXX__
|
||||||
@ -93,6 +102,13 @@ void icinga::RethrowUncaughtException()
|
|||||||
extern "C"
|
extern "C"
|
||||||
void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *))
|
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);
|
auto *tinfo = static_cast<std::type_info *>(pvtinfo);
|
||||||
|
|
||||||
typedef void (*cxa_throw_fn)(void *, std::type_info *, void (*)(void *)) __attribute__((noreturn));
|
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));
|
l_LastExceptionDest.reset(new DestCallback(dest));
|
||||||
#endif /* !defined(__GLIBCXX__) && !defined(_WIN32) */
|
#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)
|
if (real_cxa_throw == nullptr)
|
||||||
real_cxa_throw = (cxa_throw_fn)dlsym(RTLD_NEXT, "__cxa_throw");
|
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) {
|
if (!uex) {
|
||||||
#endif /* NO_CAST_EXCEPTION */
|
#endif /* NO_CAST_EXCEPTION */
|
||||||
StackTrace stack;
|
// save the current stack trace in a thread-local variable
|
||||||
|
boost::stacktrace::stacktrace stack;
|
||||||
SetLastExceptionStack(stack);
|
SetLastExceptionStack(stack);
|
||||||
|
|
||||||
#ifndef NO_CAST_EXCEPTION
|
#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))
|
if (ex && !boost::get_error_info<StackTraceErrorInfo>(*ex))
|
||||||
*ex << StackTraceErrorInfo(stack);
|
*ex << StackTraceErrorInfo(stack);
|
||||||
}
|
}
|
||||||
@ -126,6 +145,7 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *))
|
|||||||
SetLastExceptionContext(context);
|
SetLastExceptionContext(context);
|
||||||
|
|
||||||
#ifndef NO_CAST_EXCEPTION
|
#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))
|
if (ex && !boost::get_error_info<ContextTraceErrorInfo>(*ex))
|
||||||
*ex << ContextTraceErrorInfo(context);
|
*ex << ContextTraceErrorInfo(context);
|
||||||
#endif /* NO_CAST_EXCEPTION */
|
#endif /* NO_CAST_EXCEPTION */
|
||||||
@ -134,14 +154,14 @@ void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *))
|
|||||||
}
|
}
|
||||||
#endif /* HAVE_CXXABI_H */
|
#endif /* HAVE_CXXABI_H */
|
||||||
|
|
||||||
StackTrace *icinga::GetLastExceptionStack()
|
boost::stacktrace::stacktrace *icinga::GetLastExceptionStack()
|
||||||
{
|
{
|
||||||
return l_LastExceptionStack.get();
|
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()
|
ContextTrace *icinga::GetLastExceptionContext()
|
||||||
@ -154,7 +174,7 @@ void icinga::SetLastExceptionContext(const ContextTrace& context)
|
|||||||
l_LastExceptionContext.reset(new 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;
|
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);
|
const auto *pex = dynamic_cast<const posix_error *>(&ex);
|
||||||
|
|
||||||
if (!uex && !pex && verbose) {
|
if (!uex && !pex && verbose) {
|
||||||
const StackTrace *st = boost::get_error_info<StackTraceErrorInfo>(ex);
|
// Print the first of the following stack traces (if any exists)
|
||||||
|
// 1. stack trace from boost exception error information
|
||||||
if (st) {
|
const boost::stacktrace::stacktrace *st = boost::get_error_info<StackTraceErrorInfo>(ex);
|
||||||
result << *st;
|
// 2. stack trace explicitly passed as a parameter
|
||||||
} else {
|
if (!st) {
|
||||||
result << std::endl;
|
st = stack;
|
||||||
|
}
|
||||||
if (!stack)
|
// 3. stack trace saved when the last exception was thrown
|
||||||
stack = GetLastExceptionStack();
|
if (!st) {
|
||||||
|
st = GetLastExceptionStack();
|
||||||
if (stack)
|
}
|
||||||
result << *stack;
|
|
||||||
|
|
||||||
|
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);
|
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) {
|
if (ct && ct->GetLength() > 0) {
|
||||||
result << *ct;
|
result << "\nContext:\n" << *ct;
|
||||||
} else {
|
|
||||||
result << std::endl;
|
|
||||||
|
|
||||||
if (!context)
|
|
||||||
context = GetLastExceptionContext();
|
|
||||||
|
|
||||||
if (context)
|
|
||||||
result << *context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.str();
|
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)
|
String icinga::DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose)
|
||||||
{
|
{
|
||||||
StackTrace *pt = GetLastExceptionStack();
|
boost::stacktrace::stacktrace *pt = GetLastExceptionStack();
|
||||||
StackTrace stack;
|
boost::stacktrace::stacktrace stack;
|
||||||
|
|
||||||
ContextTrace *pc = GetLastExceptionContext();
|
ContextTrace *pc = GetLastExceptionContext();
|
||||||
ContextTrace context;
|
ContextTrace context;
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "base/i2-base.hpp"
|
#include "base/i2-base.hpp"
|
||||||
#include "base/string.hpp"
|
#include "base/string.hpp"
|
||||||
#include "base/stacktrace.hpp"
|
|
||||||
#include "base/context.hpp"
|
#include "base/context.hpp"
|
||||||
#include "base/debuginfo.hpp"
|
#include "base/debuginfo.hpp"
|
||||||
#include "base/dictionary.hpp"
|
#include "base/dictionary.hpp"
|
||||||
@ -15,6 +14,7 @@
|
|||||||
#include <boost/exception/errinfo_file_name.hpp>
|
#include <boost/exception/errinfo_file_name.hpp>
|
||||||
#include <boost/exception/diagnostic_information.hpp>
|
#include <boost/exception/diagnostic_information.hpp>
|
||||||
#include <boost/exception_ptr.hpp>
|
#include <boost/exception_ptr.hpp>
|
||||||
|
#include <boost/stacktrace.hpp>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <boost/algorithm/string/trim.hpp>
|
# include <boost/algorithm/string/trim.hpp>
|
||||||
@ -76,15 +76,16 @@ private:
|
|||||||
Dictionary::Ptr m_DebugHint;
|
Dictionary::Ptr m_DebugHint;
|
||||||
};
|
};
|
||||||
|
|
||||||
StackTrace *GetLastExceptionStack();
|
boost::stacktrace::stacktrace *GetLastExceptionStack();
|
||||||
void SetLastExceptionStack(const StackTrace& trace);
|
void SetLastExceptionStack(const boost::stacktrace::stacktrace& trace);
|
||||||
|
|
||||||
ContextTrace *GetLastExceptionContext();
|
ContextTrace *GetLastExceptionContext();
|
||||||
void SetLastExceptionContext(const ContextTrace& context);
|
void SetLastExceptionContext(const ContextTrace& context);
|
||||||
|
|
||||||
void RethrowUncaughtException();
|
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&);
|
std::string to_string(const StackTraceErrorInfo&);
|
||||||
|
|
||||||
@ -92,7 +93,27 @@ typedef boost::error_info<ContextTrace, ContextTrace> ContextTraceErrorInfo;
|
|||||||
|
|
||||||
std::string to_string(const ContextTraceErrorInfo& e);
|
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);
|
String DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose = true);
|
||||||
|
|
||||||
class posix_error : virtual public std::exception, virtual public boost::exception {
|
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/stacktrace.hpp"
|
||||||
#include "base/utility.hpp"
|
#include <iostream>
|
||||||
#include "base/initialize.hpp"
|
#include <iomanip>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifdef HAVE_BACKTRACE_SYMBOLS
|
#ifdef HAVE_BACKTRACE_SYMBOLS
|
||||||
# include <execinfo.h>
|
# include <execinfo.h>
|
||||||
@ -10,130 +12,32 @@
|
|||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
std::ostream &icinga::operator<<(std::ostream &os, const StackTraceFormatter &f)
|
||||||
# pragma optimize("", off)
|
|
||||||
#endif /* _MSC_VER */
|
|
||||||
|
|
||||||
StackTrace::StackTrace()
|
|
||||||
{
|
{
|
||||||
#ifdef HAVE_BACKTRACE_SYMBOLS
|
/* In most cases, this operator<< just relies on the operator<< for the `boost::stacktrace::stacktrace` wrapped in
|
||||||
m_Count = backtrace(m_Frames, sizeof(m_Frames) / sizeof(m_Frames[0]));
|
* the `StackTraceFormatter`. But as this operator turned out to not work properly on some platforms, there is a
|
||||||
#else /* HAVE_BACKTRACE_SYMBOLS */
|
* fallback implementation that can be enabled using the `-DICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS` flag at
|
||||||
# ifdef _WIN32
|
* compile time. This will then switch to `backtrace_symbols()` from `<execinfo.h>` instead of the implementation
|
||||||
m_Count = CaptureStackBackTrace(0, sizeof(m_Frames) / sizeof(m_Frames), m_Frames, nullptr);
|
* provided by Boost.
|
||||||
# else /* _WIN32 */
|
*/
|
||||||
m_Count = 0;
|
|
||||||
# endif /* _WIN32 */
|
|
||||||
#endif /* HAVE_BACKTRACE_SYMBOLS */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
const boost::stacktrace::stacktrace &stack = f.m_Stack;
|
||||||
# pragma optimize("", on)
|
|
||||||
#endif /* _MSC_VER */
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS
|
||||||
StackTrace::StackTrace(PEXCEPTION_POINTERS exi)
|
std::vector<void *> addrs;
|
||||||
{
|
addrs.reserve(stack.size());
|
||||||
STACKFRAME64 frame;
|
std::transform(stack.begin(), stack.end(), std::back_inserter(addrs), [](const boost::stacktrace::frame &f) {
|
||||||
int architecture;
|
return const_cast<void *>(f.address());
|
||||||
|
});
|
||||||
|
|
||||||
#ifdef _WIN64
|
char **symbols = backtrace_symbols(addrs.data(), addrs.size());
|
||||||
architecture = IMAGE_FILE_MACHINE_AMD64;
|
for (size_t i = 0; i < addrs.size(); i++) {
|
||||||
|
os << std::setw(2) << i << "# " << symbols[i] << std::endl;
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
}
|
std::free(symbols);
|
||||||
#endif /* _WIN32 */
|
#else /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */
|
||||||
|
os << stack;
|
||||||
#ifdef _WIN32
|
#endif /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */
|
||||||
INITIALIZE_ONCE([]() {
|
|
||||||
(void) SymSetOptions(SYMOPT_UNDNAME | SYMOPT_LOAD_LINES);
|
return os;
|
||||||
(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;
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,31 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||||
|
|
||||||
#ifndef STACKTRACE_H
|
#ifndef STACKTRACE_H
|
||||||
#define STACKTRACE_H
|
#define STACKTRACE_H
|
||||||
|
|
||||||
#include "base/i2-base.hpp"
|
#include <boost/stacktrace.hpp>
|
||||||
#include <iosfwd>
|
|
||||||
|
|
||||||
namespace icinga
|
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:
|
public:
|
||||||
StackTrace();
|
StackTraceFormatter(const boost::stacktrace::stacktrace &stack) : m_Stack(stack) {}
|
||||||
#ifdef _WIN32
|
|
||||||
explicit StackTrace(PEXCEPTION_POINTERS exi);
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
|
|
||||||
void Print(std::ostream& fp, int ignoreFrames = 0) const;
|
|
||||||
|
|
||||||
static void StaticInitialize();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *m_Frames[64];
|
const boost::stacktrace::stacktrace &m_Stack;
|
||||||
int m_Count;
|
|
||||||
|
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 "base/stacktrace.hpp"
|
||||||
#include <BoostTestTargetConfig.h>
|
#include <BoostTestTargetConfig.h>
|
||||||
|
|
||||||
using namespace icinga;
|
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)
|
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)
|
BOOST_AUTO_TEST_CASE(stacktrace)
|
||||||
{
|
{
|
||||||
StackTrace st;
|
stack_test_func_a();
|
||||||
std::ostringstream obuf;
|
|
||||||
obuf << st;
|
|
||||||
BOOST_CHECK(obuf.str().size() > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
|
#pragma GCC pop_options
|
||||||
|
#pragma clang optimize on
|
||||||
|
#ifdef _MSVC_VER
|
||||||
|
#pragma optimize("", on)
|
||||||
|
#endif /* _MSVC_VER */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user