diff --git a/CMakeLists.txt b/CMakeLists.txt index 82372af8417c126345c9ff31415d064163488196..96d8f10786bc48eefd59c2f7eb447b76e47b2f82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(groundstation src/shared/Core/QCustomPlot/QCustomPlot.cpp src/shared/Core/SkywardHubCore.cpp src/shared/Core/XmlObject.cpp + src/shared/Core/CrashLogger.cpp src/shared/Modules/CommandPad/CommandPad.cpp src/shared/Modules/CommandPad/MessageFormElement.cpp src/shared/Modules/CompactCommandPad/CompactCommandPad.cpp @@ -93,8 +94,7 @@ add_executable(groundstation src/shared/Modules/ModuleInfo.cpp src/shared/Modules/ModulesList.cpp src/entrypoints/groundstation/application.qrc - src/entrypoints/groundstation/main.cpp -) + src/entrypoints/groundstation/main.cpp) target_include_directories(groundstation PRIVATE src/shared) target_link_libraries(groundstation PUBLIC Qt5::Widgets diff --git a/SkywardHub.pro b/SkywardHub.pro index c5f3472aabce1eb94f5af97510dfac0dbd3c196f..9bcac9841bc346ae777612be54632dd982d064c0 100644 --- a/SkywardHub.pro +++ b/SkywardHub.pro @@ -19,6 +19,7 @@ INCLUDEPATH += \ libs/mavlink-skyward-lib SOURCES += \ + src/shared/Core/CrashLogger.cpp \ src/shared/Modules/Empty/EmptyModule.cpp \ src/shared/Modules/ValuesConverterViewer/ValuesViewerConfigPanel.cpp \ src/shared/Modules/ValuesConverterViewer/ValueElement.cpp \ @@ -75,6 +76,7 @@ SOURCES += \ src/entrypoints/groundstation/main.cpp HEADERS += \ + src/shared/Core/CrashLogger.h \ src/shared/Modules/Empty/EmptyModule.h \ src/shared/Modules/ValuesConverterViewer/ValueElement.h \ src/shared/Modules/ValuesConverterViewer/ValuesViewerConfigPanel.h \ @@ -157,3 +159,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin RESOURCES += \ src/entrypoints/groundstation/application.qrc + +win32: LIBS += -lDbgHelp diff --git a/src/entrypoints/groundstation/main.cpp b/src/entrypoints/groundstation/main.cpp index 89623e0274aa2c1b041f5690085fd125d8f25156..2e835c33b56d937b70dbdee60e2437d82cae092e 100644 --- a/src/entrypoints/groundstation/main.cpp +++ b/src/entrypoints/groundstation/main.cpp @@ -17,6 +17,7 @@ */ #include <QApplication> +#include <Core/CrashLogger.h> #include "Modules/MainWindow/SkywardHubMainWindow.h" @@ -27,7 +28,19 @@ int main(int argc, char *argv[]) SkywardHubMainWindow skywardHub; application.setStyleSheet(skywardHub.styleSheet()); skywardHub.show(); + +#ifdef Q_OS_WIN + [&]() + { + __try { + application.exec(); + } + __except(sehFilter(GetExceptionInformation())) {} + }(); +#else + // TODO: add linux support application.exec(); +#endif return 0; } diff --git a/src/shared/Core/CrashLogger.cpp b/src/shared/Core/CrashLogger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..366e969371e4e52a46f1ac44eeb88f34da47e5e7 --- /dev/null +++ b/src/shared/Core/CrashLogger.cpp @@ -0,0 +1,195 @@ +#include "CrashLogger.h" + +/////////////////////// WINDOWS /////////////////////// +#ifdef Q_OS_WIN + +int sehFilter(_EXCEPTION_POINTERS *ex) +{ + std::fstream file; + QDateTime timestamp = QDateTime::currentDateTimeUtc(); + + QString fileName = + "crash_log_" + timestamp.toString("dd.MM.yyyy_hh.mm.ss") + ".txt"; + + file.open(fileName.toStdString(), std::fstream::out); + writeException(file, ex); + file.close(); + + return EXCEPTION_EXECUTE_HANDLER; +} + +void writeException(std::fstream &file, _EXCEPTION_POINTERS *ex) +{ + std::string date = + QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toStdString(); + file << "~~ Exception " << ex->ExceptionRecord->ExceptionAddress << " at " + << date << " ~~\n"; + writeStack(file, ex); +} + +void writeStack(std::fstream &file, _EXCEPTION_POINTERS *ex) +{ + constexpr int MaxNameLen = 256; + + BOOL retrieved; + HANDLE process; + HANDLE thread; + HMODULE hModule; + + STACKFRAME64 stack; + ULONG frame; + DWORD64 displacement; + + DWORD disp; + IMAGEHLP_LINE64 *line; + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + char module[MaxNameLen]; + auto pSymbol = (PSYMBOL_INFO)buffer; + + CONTEXT ctxCopy; + memcpy(&ctxCopy, ex->ContextRecord, sizeof(CONTEXT)); + memset(&stack, 0, sizeof(STACKFRAME64)); + + process = GetCurrentProcess(); + thread = GetCurrentThread(); + +#if !defined(_M_AMD64) + stack.AddrPC.Offset = (*ex->ContextRecord).Eip; + stack.AddrPC.Mode = AddrModeFlat; + stack.AddrStack.Offset = (*ex->ContextRecord).Esp; + stack.AddrStack.Mode = AddrModeFlat; + stack.AddrFrame.Offset = (*ex->ContextRecord).Ebp; + stack.AddrFrame.Mode = AddrModeFlat; +#endif + + // Initialize symbol handler for current process + SymInitialize(process, NULL, true); + + for (frame = 0;; frame++) + { + retrieved = StackWalk64( +#if defined(_M_AMD64) + IMAGE_FILE_MACHINE_AMD64, +#else + IMAGE_FILE_MACHINE_I386, +#endif + process, + thread, + &stack, + &ctxCopy, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL); + if (!retrieved) + break; + + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MaxNameLen; + SymFromAddr(process, + (ULONG64)stack.AddrPC.Offset, + &displacement, + pSymbol); + + line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64)); + line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + // Try to get line + if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, line)) + { + file << "\tat " << pSymbol->Name << " in " << line->FileName + << ": line: " << line->LineNumber + << " address: " << pSymbol->Address << "\n"; + } + else + { + // Failed to get line + file << "\tat" << pSymbol->Name << ", address: " << pSymbol->Address + << "\n"; + hModule = NULL; + lstrcpyA(module, ""); + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)(stack.AddrPC.Offset), &hModule); + + // At least print module name + if (hModule != NULL) + GetModuleFileNameA(hModule, module, MaxNameLen); + + file << "in " << module << "\n"; + } + + free(line); + line = NULL; + } +} +/////////////////////// LINUX & MACOS /////////////////////// +#elif Q_OS_LINUX || Q_OS_MAC + +/* +The code should work(untested) until the todo comment. + +void exceptionHandler(int sig, struct sigcontext ctx) +{ + QDateTime timestamp = QDateTime::currentDateTimeUtc(); + QString fileName = QString("crash_log_") + + timestamp.toString("dd.MM.yyyy_hh.mm.ss") + ".txt"; + + auto isoDate = timestamp.toString(Qt::DateFormat::ISODate); + auto sigStr = std::to_string(sig); + int fd = open(fileName.toStdString().c_str(), O_WRONLY | O_CREAT); + + // Generate file with current timestamp + std::fstream file; + void *trace[50]; + char **messages = (char **)NULL; + int i, traceSize = 0; + + write(fd, "~~ Exception at ", 16); + write(fd, isoDate.toStdString().c_str(), isoDate.length()); + write(fd, " , signal: ", 11); + write(fd, sigStr.c_str(), sigStr.length()); + write(fd, " ~~", 3); + + // get void*'s for all entries on the stack + traceSize = backtrace(trace, 50); + + // overwrite sigaction with caller's address + trace[1] = (void *)ctx.eip; + messages = backtrace_symbols(trace, trace_size); + + for (i = 1; i < traceSize; i++) + { + auto iStr = std::to_string(i); + auto messageStr = messages[i]; + auto messageLen = strlen(messageStr); + + write(fd, iStr.c_str(), iStr.length()); + write(fd, ": ", 2); + write(fd, messageStr, messageLen); + + /* find first occurrence of '(' or ' ' in message[i] and assume + * everything before that is the file name. (Don't go beyond 0 though + * (string terminator)*/ + size_t p = 0; + while (messages[i][p] != '(' && messages[i][p] != ' ' && + messages[i][p] != 0) + ++p; + + // TODO: change the following code, as we cannot retrieve + // the line by running addr2line in the terminal. + // See: https://stackoverflow.com/questions/11556321/how-do-i-access-the-addr2line-functionality-within-my-c-program + + char syscom[256]; + sprintf(syscom, "addr2line %p -e %.*s", trace[i], p, messages[i]); + // last parameter is the file name of the symbol + system(syscom); + } + + close(fd); +} + +*/ + +#endif \ No newline at end of file diff --git a/src/shared/Core/CrashLogger.h b/src/shared/Core/CrashLogger.h new file mode 100644 index 0000000000000000000000000000000000000000..34ceb33972af5fc84c9de348b6b106c3ef9603d8 --- /dev/null +++ b/src/shared/Core/CrashLogger.h @@ -0,0 +1,53 @@ +#pragma once + +#include <fstream> +#include <sstream> + +#include <QDateTime> + +/////////////////////// WINDOWS /////////////////////// +#ifdef Q_OS_WIN + +#include <QDebug> +#include <Windows.h> +#include "winnt.h" +#include <process.h> +#include <DbgHelp.h> +#include <format> + +/** + * Structured Exception Handling (SEH) filter. Used together with __try + * and __catch. See: https://learn.microsoft.com/en-us/cpp/cpp/try-except-statement?view=msvc-170 + */ +int sehFilter(_EXCEPTION_POINTERS *ex); +/** + * Write an exception information and a stacktrace to a file. + * @param file The file to write to. + * @param ex Exception info pointer (winnt.h). + */ +void writeException(std::fstream &file, _EXCEPTION_POINTERS *ex); +/** + * Write an exception stacktrace to the file. Used by writeException. + * @param file The file to write to. + * @param ex Exception info pointer (winnt.h). + */ +void writeStack(std::fstream &file, _EXCEPTION_POINTERS *ex); + +/////////////////////// LINUX & MACOS /////////////////////// +#elif Q_OS_LINUX || Q_OS_MAC + +/** + * TODO: add linux support + * + * #include <io.h> + * #include <stdio.h> + * #include <execinfo.h> + * #include <signal.h> + * #include <stdlib.h> + * #include <unistd.h> + * #include <fcntl.h> + * + * void exceptionHandler(int sig); + */ + +#endif \ No newline at end of file