diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d15014fd394a6f82cb0d33cda11706ddb909f85d..e5ea8291cae2773e8a9eb92b7a2495bce8bf4aac 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 75d9cb0414d8a3f0d2bd082ac0f0de220a2b8770..87e985fd99634fd3896ff809af0045d078755498 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 45afe4c4697b6ab149285997af50bfc180b79f8c..dc328c0cfd1743968caf820770e1d8298a78a9ce 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 44f2eedfa475a2c765fa2de5bc3d7ee90a47216f..479c81743a6f0a41859150636461780bfebdb5a6 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 0000000000000000000000000000000000000000..2395cfa2422edb71929c9d166a6a614571331db3
--- /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 89623e0274aa2c1b041f5690085fd125d8f25156..f4da481ead31c44311ef19015c7b7f14f4522d9c 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 0000000000000000000000000000000000000000..9dc9103bd277a106555264dba0ab09b3b9a21a5e
--- /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