From b00df75b3f0a2cdf2a0a615e3a855d31f7bb2edf Mon Sep 17 00:00:00 2001 From: Giacomo Caironi <giacomo.caironi@skywarder.eu> Date: Fri, 11 Aug 2023 11:32:51 +0000 Subject: [PATCH] Crash report --- .gitlab-ci.yml | 2 +- .gitmodules | 3 + CMakeLists.txt | 11 + SkywardHub.pro | 8 +- libs/backward-cpp | 1 + src/entrypoints/groundstation/main.cpp | 2 + src/shared/Core/CrashLogger.h | 409 +++++++++++++++++++++++++ 7 files changed, 434 insertions(+), 2 deletions(-) create mode 160000 libs/backward-cpp create mode 100644 src/shared/Core/CrashLogger.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d15014fd..e5ea8291 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,7 +72,7 @@ build_windows: - export PATH=/home/gitlab-runner/mxe/usr/bin:$PATH script: - ./sbs --clean - - x86_64-w64-mingw32.static-qmake-qt5 + - x86_64-w64-mingw32.static-qmake-qt5 "LIBS+=-lmsvcr90" - make - mv release/SkywardHub.exe Ground_Station-$CI_COMMIT_SHORT_SHA.exe artifacts: diff --git a/.gitmodules b/.gitmodules index 75d9cb04..87e985fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "MAVLink Skyward library"] path = libs/mavlink-skyward-lib url = ../mavlink/mavlink-skyward-lib +[submodule "libs/backward-cpp"] + path = libs/backward-cpp + url = https://github.com/bombela/backward-cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 45afe4c4..dc328c0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ cmake_minimum_required(VERSION 3.16) add_subdirectory(libs/mavlink-skyward-lib EXCLUDE_FROM_ALL) +add_subdirectory(libs/backward-cpp) project(SkywardHub) @@ -102,3 +103,13 @@ target_link_libraries(groundstation PUBLIC if(APPLE) set_target_properties(groundstation PROPERTIES MACOSX_BUNDLE TRUE) endif() + +add_backward(groundstation) + +if (UNIX) + set(BACKWARD_HAS_DW 1) + set(BACKWARD_HAS_LIBUNWIND 1) +endif (UNIX) + +# For now we keep debug information on +set(CMAKE_BUILD_TYPE RelWithDebInfo) diff --git a/SkywardHub.pro b/SkywardHub.pro index 44f2eedf..479c8174 100644 --- a/SkywardHub.pro +++ b/SkywardHub.pro @@ -16,7 +16,8 @@ RC_ICONS = src/entrypoints/groundstation/assets/icons/SkywardHub.ico INCLUDEPATH += \ src/shared \ - libs/mavlink-skyward-lib + libs/mavlink-skyward-lib \ + libs/backward-cpp SOURCES += \ src/shared/Modules/Empty/EmptyModule.cpp \ @@ -52,6 +53,7 @@ SOURCES += \ src/shared/Core/QCustomPlot/QCustomPlot.cpp \ src/shared/Core/MessageBroker/MessageBroker.cpp \ src/shared/Core/Module/Module.cpp \ + src/shared/Core/CrashLogger.h \ src/shared/Core/SkywardHubCore.cpp \ src/shared/Core/Message/Filter.cpp \ src/shared/Core/Message/Field.cpp \ @@ -138,3 +140,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin RESOURCES += \ src/entrypoints/groundstation/application.qrc + +win32: LIBS += -ldbghelp -lpsapi +unix: LIBS += -ldw -lunwind +unix: QMAKE_CXXFLAGS += -DBACKWARD_HAS_DW=1 -DBACKWARD_HAS_LIBUNWIND=1 diff --git a/libs/backward-cpp b/libs/backward-cpp new file mode 160000 index 00000000..2395cfa2 --- /dev/null +++ b/libs/backward-cpp @@ -0,0 +1 @@ +Subproject commit 2395cfa2422edb71929c9d166a6a614571331db3 diff --git a/src/entrypoints/groundstation/main.cpp b/src/entrypoints/groundstation/main.cpp index 89623e02..f4da481e 100644 --- a/src/entrypoints/groundstation/main.cpp +++ b/src/entrypoints/groundstation/main.cpp @@ -18,6 +18,7 @@ #include <QApplication> +#include "Core/CrashLogger.h" #include "Modules/MainWindow/SkywardHubMainWindow.h" int main(int argc, char *argv[]) @@ -27,6 +28,7 @@ int main(int argc, char *argv[]) SkywardHubMainWindow skywardHub; application.setStyleSheet(skywardHub.styleSheet()); skywardHub.show(); + application.exec(); return 0; diff --git a/src/shared/Core/CrashLogger.h b/src/shared/Core/CrashLogger.h new file mode 100644 index 00000000..9dc9103b --- /dev/null +++ b/src/shared/Core/CrashLogger.h @@ -0,0 +1,409 @@ +#include <Modules/SkywardHubStrings.h> + +#include <QDateTime> +#include <QDebug> +#include <QDir> +#include <backward.hpp> + +namespace backward +{ + +// FIXME: can probably clean up with inheritance +// Copied from backward.hpp, for now we can't use a custom printer +// See https://github.com/bombela/backward-cpp/pull/238 +/*************** SIGNALS HANDLING ***************/ +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + +class SkywardSignalHandling +{ +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]); + } + + SkywardSignalHandling( + 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; + } + + 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); + + 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__) +#if defined(__APPLE__) + error_addr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__pc); +#else + error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.pc); +#endif +#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(__riscv) + error_addr = + reinterpret_cast<void *>(uctx->uc_mcontext.__gregs[REG_PC]); +#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, reinterpret_cast<void *>(uctx), + info->si_addr); + } + else + { + st.load_here(32, reinterpret_cast<void *>(uctx), info->si_addr); + } + + Printer printer; + printer.address = true; + QDateTime timestamp = QDateTime::currentDateTimeUtc(); + QString crashdir = + SkywardHubStrings::defaultConfigurationFolder + "/crashes/"; + if (!QDir(crashdir).exists()) + { + QDir().mkdir(crashdir); + } + QString fileName = crashdir + QString("crash_log_") + + timestamp.toString("dd.MM.yyyy_hh.mm.ss") + ".txt"; + std::ofstream fp(fileName.toStdString().c_str()); + printer.print(st, fp); + +#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) + psiginfo(info, nullptr); +#else + (void)info; +#endif + } + +private: + details::handle<char *> _stack_content; + bool _loaded; + +#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 SkywardSignalHandling +{ +public: + SkywardSignalHandling(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); + + std::set_terminate(&terminator); +#ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); +#endif + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalid_parameter_handler); + } + bool loaded() const { return true; } + + ~SkywardSignalHandling() + { + { + 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 are 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 printer; + + StackTrace st; + st.set_machine_type(printer.resolver().machine_type()); + st.set_thread_handle(thread_handle()); + st.load_here(32 + skip_frames, ctx()); + st.skip_n_firsts(skip_frames); + + printer.address = true; + QDateTime timestamp = QDateTime::currentDateTimeUtc(); + QString crashdir = + SkywardHubStrings::defaultConfigurationFolder + "/crashes/"; + if (!QDir(crashdir).exists()) + { + QDir().mkdir(crashdir); + } + QString fileName = crashdir + QString("crash_log_") + + timestamp.toString("dd.MM.yyyy_hh.mm.ss") + ".txt"; + std::ofstream fp(fileName.toStdString().c_str()); + printer.print(st, fp); + } +}; + +#endif // BACKWARD_SYSTEM_WINDOWS + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +class SkywardSignalHandling +{ +public: + SkywardSignalHandling(const std::vector<int> & = std::vector<int>()) {} + bool init() { return false; } + bool loaded() { return false; } +}; + +#endif // BACKWARD_SYSTEM_UNKNOWN + +/*************** SIGNALS HANDLING ***************/ + +SkywardSignalHandling sh; + +} // namespace backward \ No newline at end of file -- GitLab