Support for custom signal callbacks.
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
d59c13a34d
commit
cfcdbfe984
|
@ -1,4 +1,9 @@
|
||||||
#include "Core.hpp"
|
#include "Core.hpp"
|
||||||
|
#include "../src/SignalHandling.hpp"
|
||||||
|
|
||||||
std::string ExceptionHandler::_ArbutilsLastException = "";
|
std::string ExceptionHandler::_ArbutilsLastException = "";
|
||||||
export const char* Arbutils_C_GetLastException() { return ExceptionHandler::GetLastException(); }
|
export const char* Arbutils_C_GetLastException() { return ExceptionHandler::GetLastException(); }
|
||||||
|
|
||||||
|
static ArbUt::SignalHandling sh;
|
||||||
|
export void Arbutils_C_SetSignalCallback(void (*callback)(const char*)) { sh = ArbUt::SignalHandling(callback); }
|
||||||
|
export void Arbutils_C_RaiseSignal() { raise(SIGSEGV); }
|
||||||
|
|
|
@ -41,18 +41,27 @@ namespace ArbUt {
|
||||||
[[nodiscard]] std::string GetStacktrace([[maybe_unused]] size_t depth = 6,
|
[[nodiscard]] std::string GetStacktrace([[maybe_unused]] size_t depth = 6,
|
||||||
[[maybe_unused]] bool include_addr = true) const {
|
[[maybe_unused]] bool include_addr = true) const {
|
||||||
#if !WINDOWS
|
#if !WINDOWS
|
||||||
if (_stack.size() == 0) {
|
return BuildStacktraceFromStack(_stack, depth, include_addr);
|
||||||
|
#else
|
||||||
|
return "Stack trace not available on Windows.";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !WINDOWS
|
||||||
|
static std::string BuildStacktraceFromStack(const backward::StackTrace& stack, size_t depth = 6,
|
||||||
|
bool include_addr = true) {
|
||||||
|
if (stack.size() == 0) {
|
||||||
return "No stack trace could be retrieved.";
|
return "No stack trace could be retrieved.";
|
||||||
}
|
}
|
||||||
backward::TraceResolver tr;
|
backward::TraceResolver tr;
|
||||||
backward::SnippetFactory snippetFactory;
|
backward::SnippetFactory snippetFactory;
|
||||||
tr.load_stacktrace(_stack);
|
tr.load_stacktrace(stack);
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Stacktrace with depth " << depth << ": " << std::endl;
|
ss << "Stacktrace with depth " << depth << ": " << std::endl;
|
||||||
bool foundExceptionClass = false;
|
bool foundExceptionClass = false;
|
||||||
size_t framesAppended = 0;
|
size_t framesAppended = 0;
|
||||||
for (size_t i = 0; i < _stack.size() && framesAppended <= depth; ++i) {
|
for (size_t i = 0; i < stack.size() && framesAppended <= depth; ++i) {
|
||||||
backward::ResolvedTrace trace = tr.resolve(_stack[i]);
|
backward::ResolvedTrace trace = tr.resolve(stack[i]);
|
||||||
if (trace.source.filename.empty()) {
|
if (trace.source.filename.empty()) {
|
||||||
AppendNoSourceStack(ss, trace, include_addr);
|
AppendNoSourceStack(ss, trace, include_addr);
|
||||||
if (foundExceptionClass) {
|
if (foundExceptionClass) {
|
||||||
|
@ -74,12 +83,8 @@ namespace ArbUt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
#else
|
|
||||||
return "Stack trace not available on Windows.";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !WINDOWS
|
|
||||||
private:
|
private:
|
||||||
static void AppendNoSourceStack(std::stringstream& ss, const backward::ResolvedTrace& trace,
|
static void AppendNoSourceStack(std::stringstream& ss, const backward::ResolvedTrace& trace,
|
||||||
bool include_addr) {
|
bool include_addr) {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "SignalHandling.hpp"
|
||||||
|
|
||||||
|
void(*ArbUt::SignalHandling::_callback)(const char*) = nullptr;
|
|
@ -0,0 +1,334 @@
|
||||||
|
#ifndef ARBUTILS_SIGNALHANDLING_HPP
|
||||||
|
#define ARBUTILS_SIGNALHANDLING_HPP
|
||||||
|
#if PRETTYTRACES
|
||||||
|
#define BACKWARD_HAS_DW 1
|
||||||
|
#endif
|
||||||
|
#include "../extern/backward.hpp"
|
||||||
|
#include "Exception.hpp"
|
||||||
|
|
||||||
|
// Sourced from https://github.com/bombela/backward-cpp/blob/master/backward.hpp#L3849
|
||||||
|
// Modified to allow for custom callbacks.
|
||||||
|
|
||||||
|
namespace ArbUt {
|
||||||
|
|
||||||
|
#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN)
|
||||||
|
|
||||||
|
class SignalHandling {
|
||||||
|
public:
|
||||||
|
static std::vector<int> make_default_signals() {
|
||||||
|
const int posix_signals[] = {
|
||||||
|
// Signals for which the default action is "Core".
|
||||||
|
SIGABRT, // Abort signal from abort(3)
|
||||||
|
SIGBUS, // Bus error (bad memory access)
|
||||||
|
SIGFPE, // Floating point exception
|
||||||
|
SIGILL, // Illegal Instruction
|
||||||
|
SIGIOT, // IOT trap. A synonym for SIGABRT
|
||||||
|
SIGQUIT, // Quit from keyboard
|
||||||
|
SIGSEGV, // Invalid memory reference
|
||||||
|
SIGSYS, // Bad argument to routine (SVr4)
|
||||||
|
SIGTRAP, // Trace/breakpoint trap
|
||||||
|
SIGXCPU, // CPU time limit exceeded (4.2BSD)
|
||||||
|
SIGXFSZ, // File size limit exceeded (4.2BSD)
|
||||||
|
#if defined(BACKWARD_SYSTEM_DARWIN)
|
||||||
|
SIGEMT, // emulation instruction executed
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
return std::vector<int>(posix_signals, posix_signals + sizeof posix_signals / sizeof posix_signals[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalHandling(void(callback)(const char*) = nullptr,
|
||||||
|
const std::vector<int>& posix_signals = make_default_signals())
|
||||||
|
: _loaded(false){
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
const size_t stack_size = 1024 * 1024 * 8;
|
||||||
|
_stack_content.reset(static_cast<char*>(malloc(stack_size)));
|
||||||
|
if (_stack_content) {
|
||||||
|
stack_t ss;
|
||||||
|
ss.ss_sp = _stack_content.get();
|
||||||
|
ss.ss_size = stack_size;
|
||||||
|
ss.ss_flags = 0;
|
||||||
|
if (sigaltstack(&ss, nullptr) < 0) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
_callback = callback;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < posix_signals.size(); ++i) {
|
||||||
|
struct sigaction action;
|
||||||
|
memset(&action, 0, sizeof action);
|
||||||
|
action.sa_flags = static_cast<int>(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND);
|
||||||
|
sigfillset(&action.sa_mask);
|
||||||
|
sigdelset(&action.sa_mask, posix_signals[i]);
|
||||||
|
#if defined(__clang__)
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
|
||||||
|
#endif
|
||||||
|
action.sa_sigaction = &sig_handler;
|
||||||
|
#if defined(__clang__)
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int r = sigaction(posix_signals[i], &action, nullptr);
|
||||||
|
if (r < 0)
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loaded = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loaded() const { return _loaded; }
|
||||||
|
|
||||||
|
static void handleSignal(int, siginfo_t* info, void* _ctx) {
|
||||||
|
ucontext_t* uctx = static_cast<ucontext_t*>(_ctx);
|
||||||
|
|
||||||
|
backward::StackTrace st;
|
||||||
|
void* error_addr = nullptr;
|
||||||
|
#ifdef REG_RIP // x86_64
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.gregs[REG_RIP]);
|
||||||
|
#elif defined(REG_EIP) // x86_32
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.gregs[REG_EIP]);
|
||||||
|
#elif defined(__arm__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.arm_pc);
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.pc);
|
||||||
|
#elif defined(__mips__)
|
||||||
|
error_addr = reinterpret_cast<void*>(reinterpret_cast<struct sigcontext*>(&uctx->uc_mcontext)->sc_pc);
|
||||||
|
#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.regs->nip);
|
||||||
|
#elif defined(__s390x__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext.psw.addr);
|
||||||
|
#elif defined(__APPLE__) && defined(__x86_64__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext->__ss.__rip);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
error_addr = reinterpret_cast<void*>(uctx->uc_mcontext->__ss.__eip);
|
||||||
|
#else
|
||||||
|
#warning ":/ sorry, ain't know no nothing none not of your architecture!"
|
||||||
|
#endif
|
||||||
|
if (error_addr) {
|
||||||
|
st.load_from(error_addr, 32);
|
||||||
|
} else {
|
||||||
|
st.load_here(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_callback != nullptr) {
|
||||||
|
auto str = Exception::BuildStacktraceFromStack(st);
|
||||||
|
_callback(str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
|
||||||
|
psiginfo(info, nullptr);
|
||||||
|
#else
|
||||||
|
(void)info;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
backward::details::handle<char*> _stack_content;
|
||||||
|
bool _loaded;
|
||||||
|
static void(*_callback)(const char*);
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
__attribute__((noreturn))
|
||||||
|
#endif
|
||||||
|
static void
|
||||||
|
sig_handler(int signo, siginfo_t* info, void* _ctx) {
|
||||||
|
handleSignal(signo, info, _ctx);
|
||||||
|
|
||||||
|
// try to forward the signal.
|
||||||
|
raise(info->si_signo);
|
||||||
|
|
||||||
|
// terminate the process immediately.
|
||||||
|
puts("watf? exit");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
|
||||||
|
|
||||||
|
#ifdef BACKWARD_SYSTEM_WINDOWS
|
||||||
|
|
||||||
|
class SignalHandling {
|
||||||
|
public:
|
||||||
|
SignalHandling(const std::vector<int>& = std::vector<int>())
|
||||||
|
: reporter_thread_([]() {
|
||||||
|
/* We handle crashes in a utility thread:
|
||||||
|
backward structures and some Windows functions called here
|
||||||
|
need stack space, which we do not have when we encounter a
|
||||||
|
stack overflow.
|
||||||
|
To support reporting stack traces during a stack overflow,
|
||||||
|
we create a utility thread at startup, which waits until a
|
||||||
|
crash happens or the program exits normally. */
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
cv().wait(lk, [] { return crashed() != crash_status::running; });
|
||||||
|
}
|
||||||
|
if (crashed() == crash_status::crashed) {
|
||||||
|
handle_stacktrace(skip_recs());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::ending;
|
||||||
|
}
|
||||||
|
cv().notify_one();
|
||||||
|
}) {
|
||||||
|
SetUnhandledExceptionFilter(crash_handler);
|
||||||
|
|
||||||
|
signal(SIGABRT, signal_handler);
|
||||||
|
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||||
|
|
||||||
|
set_terminate(&terminator);
|
||||||
|
set_unexpected(&terminator);
|
||||||
|
_set_purecall_handler(&terminator);
|
||||||
|
_set_invalid_parameter_handler(&invalid_parameter_handler);
|
||||||
|
}
|
||||||
|
bool loaded() const { return true; }
|
||||||
|
|
||||||
|
~SignalHandling() {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::normal_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv().notify_one();
|
||||||
|
|
||||||
|
reporter_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static CONTEXT* ctx() {
|
||||||
|
static CONTEXT data;
|
||||||
|
return &data;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class crash_status { running, crashed, normal_exit, ending };
|
||||||
|
|
||||||
|
static crash_status& crashed() {
|
||||||
|
static crash_status data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::mutex& mtx() {
|
||||||
|
static std::mutex data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::condition_variable& cv() {
|
||||||
|
static std::condition_variable data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HANDLE& thread_handle() {
|
||||||
|
static HANDLE handle;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread reporter_thread_;
|
||||||
|
|
||||||
|
// TODO: how not to hardcode these?
|
||||||
|
static const constexpr int signal_skip_recs =
|
||||||
|
#ifdef __clang__
|
||||||
|
// With clang, RtlCaptureContext also captures the stack frame of the
|
||||||
|
// current function Below that, there ar 3 internal Windows functions
|
||||||
|
4
|
||||||
|
#else
|
||||||
|
// With MSVC cl, RtlCaptureContext misses the stack frame of the current
|
||||||
|
// function The first entries during StackWalk are the 3 internal Windows
|
||||||
|
// functions
|
||||||
|
3
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
static int& skip_recs() {
|
||||||
|
static int data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void terminator() {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void signal_handler(int) {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __cdecl invalid_parameter_handler(const wchar_t*, const wchar_t*, const wchar_t*,
|
||||||
|
unsigned int, uintptr_t) {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) {
|
||||||
|
// The exception info supplies a trace from exactly where the issue was,
|
||||||
|
// no need to skip records
|
||||||
|
crash_handler(0, info->ContextRecord);
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE static void crash_handler(int skip, CONTEXT* ct = nullptr) {
|
||||||
|
|
||||||
|
if (ct == nullptr) {
|
||||||
|
RtlCaptureContext(ctx());
|
||||||
|
} else {
|
||||||
|
memcpy(ctx(), ct, sizeof(CONTEXT));
|
||||||
|
}
|
||||||
|
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &thread_handle(), 0, FALSE,
|
||||||
|
DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
|
skip_recs() = skip;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::crashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv().notify_one();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
cv().wait(lk, [] { return crashed() != crash_status::crashed; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_stacktrace(int skip_frames = 0) {
|
||||||
|
// printer creates the TraceResolver, which can supply us a machine type
|
||||||
|
// for stack walking. Without this, StackTrace can only guess using some
|
||||||
|
// macros.
|
||||||
|
// StackTrace also requires that the PDBs are already loaded, which is done
|
||||||
|
// in the constructor of TraceResolver
|
||||||
|
Printer printer;
|
||||||
|
|
||||||
|
StackTrace st;
|
||||||
|
st.set_machine_type(printer.resolver().machine_type());
|
||||||
|
st.set_context(ctx());
|
||||||
|
st.set_thread_handle(thread_handle());
|
||||||
|
st.load_here(32 + skip_frames);
|
||||||
|
st.skip_n_firsts(skip_frames);
|
||||||
|
|
||||||
|
printer.address = true;
|
||||||
|
printer.print(st, std::cerr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BACKWARD_SYSTEM_WINDOWS
|
||||||
|
|
||||||
|
#ifdef BACKWARD_SYSTEM_UNKNOWN
|
||||||
|
|
||||||
|
class SignalHandling {
|
||||||
|
public:
|
||||||
|
SignalHandling(const std::vector<int>& = std::vector<int>()) {}
|
||||||
|
bool init() { return false; }
|
||||||
|
bool loaded() { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BACKWARD_SYSTEM_UNKNOWN
|
||||||
|
|
||||||
|
void SetSignalCallback(void(*callback)(const char*));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ARBUTILS_SIGNALHANDLING_HPP
|
Loading…
Reference in New Issue