diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b5e1329c2..47c7eadf7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -36,7 +36,7 @@ jobs: libusb-1.0-0-dev libhidapi-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \ libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \ libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \ - libbladerf-dev libsoapysdr-dev libiio-dev libuhd-dev libhamlib-dev \ + libbladerf-dev libsoapysdr-dev libiio-dev libuhd-dev libhamlib-dev libunwind-dev \ python3-mako python3-cheetah python3-numpy \ autoconf automake libtool ninja-build bash cmake/ci/build_sdrplay.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cd2b0ba8..5ce0bd70c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,9 +37,64 @@ set(ARCH_OPT "native" CACHE STRING "Specify instruction set to use. Will be pass 'native' option will figure out host machine compatibilities and set flags accordingly (even with MSVC).") option(ENABLE_QT6 "Build with Qt6 rather than Qt5" OFF) option(ENABLE_PROFILER "Enable runtime profiler" OFF) +if(UNIX AND NOT ANDROID) + set(DEFAULT_ENABLE_LIBUNWIND ON) +else() + set(DEFAULT_ENABLE_LIBUNWIND OFF) +endif() +option(ENABLE_LIBUNWIND "Use libunwind for stack traces in exception handlers" ${DEFAULT_ENABLE_LIBUNWIND}) set(VKFFT_BACKEND 1 CACHE STRING "vkFFT Backend: 0 - Vulkan, 1 - CUDA") option(BUILD_SHARED_LIBS "Build using shared libraries" ON) +set(HAVE_LIBUNWIND OFF) +if(ENABLE_LIBUNWIND AND UNIX AND NOT ANDROID) + find_package(PkgConfig QUIET) + + if(PKG_CONFIG_FOUND) + pkg_check_modules(LIBUNWIND QUIET libunwind) + endif() + + # Fall back to find_library if pkg-config doesn't find it + if(NOT LIBUNWIND_FOUND) + find_library(LIBUNWIND_LIB unwind) + if(LIBUNWIND_LIB) + set(LIBUNWIND_LIBRARIES ${LIBUNWIND_LIB}) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + find_library(LIBUNWIND_ARCH_LIB unwind-x86_64) + if(LIBUNWIND_ARCH_LIB) + list(APPEND LIBUNWIND_LIBRARIES ${LIBUNWIND_ARCH_LIB}) + endif() + endif() + set(LIBUNWIND_FOUND TRUE) + endif() + endif() + + if(LIBUNWIND_FOUND) + if(NOT LIBUNWIND_LIBRARIES) + set(LIBUNWIND_LIBRARIES unwind) + endif() + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + list(JOIN LIBUNWIND_LIBRARIES ";" _libunwind_libs_joined) + if(NOT _libunwind_libs_joined MATCHES "unwind-x86_64") + find_library(LIBUNWIND_ARCH_LIB unwind-x86_64) + if(LIBUNWIND_ARCH_LIB) + list(APPEND LIBUNWIND_LIBRARIES ${LIBUNWIND_ARCH_LIB}) + else() + message(WARNING "libunwind-x86_64 not found; stack traces may fail to link on x86_64.") + endif() + endif() + endif() + set(HAVE_LIBUNWIND ON) + message(STATUS "libunwind found - LIBUNWIND_LIBRARIES=${LIBUNWIND_LIBRARIES}") + message(STATUS "libunwind include dirs - ${LIBUNWIND_INCLUDE_DIRS}") + message(STATUS "libunwind cflags - ${LIBUNWIND_CFLAGS}") + message(STATUS "libunwind ldflags - ${LIBUNWIND_LDFLAGS}") + else() + message(WARNING "libunwind requested but not found; stack traces disabled. Install libunwind-dev or similar package.") + set(ENABLE_LIBUNWIND OFF) + endif() +endif() + # Sampling devices enablers option(ENABLE_AARONIARTSA "Enable AaroniaRTSA support" ON) option(ENABLE_AIRSPY "Enable AirSpy support" ON) @@ -1071,6 +1126,14 @@ if (BUILD_GUI) target_link_libraries(${CMAKE_PROJECT_NAME} "-framework AVFoundation" objc) endif() + if(HAVE_LIBUNWIND) + target_link_libraries(${CMAKE_PROJECT_NAME} ${LIBUNWIND_LIBRARIES}) + if(LIBUNWIND_INCLUDE_DIRS) + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${LIBUNWIND_INCLUDE_DIRS}) + endif() + target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE HAVE_LIBUNWIND=1) + endif() + if(WIN32) set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ${HIDE_CONSOLE}) @@ -1093,6 +1156,14 @@ if (BUILD_SERVER) sdrsrv logging ) + + if(HAVE_LIBUNWIND) + target_link_libraries(sdrangelsrv ${LIBUNWIND_LIBRARIES}) + if(LIBUNWIND_INCLUDE_DIRS) + target_include_directories(sdrangelsrv PRIVATE ${LIBUNWIND_INCLUDE_DIRS}) + endif() + target_compile_definitions(sdrangelsrv PRIVATE HAVE_LIBUNWIND=1) + endif() endif() ############ install ################## diff --git a/app/crashhandler.h b/app/crashhandler.h index ac5de8395..2c857ccb7 100644 --- a/app/crashhandler.h +++ b/app/crashhandler.h @@ -20,3 +20,7 @@ #include "loggerwithfile.h" void installCrashHandler(qtwebapp::LoggerWithFile *logger); + +#ifdef _WIN32 +void logWindowsStackTrace(); +#endif diff --git a/app/crashhandlerwin.cpp b/app/crashhandlerwin.cpp index 11dfedc97..891fc4e90 100644 --- a/app/crashhandlerwin.cpp +++ b/app/crashhandlerwin.cpp @@ -38,6 +38,102 @@ static HWND hExitButton; static qtwebapp::LoggerWithFile *crashLogger; +static void appendStackTrace(char *&reportBufferPtr, int &reportBufferRemaining) +{ + STACKFRAME64 stack; + CONTEXT context; + HANDLE process; + DWORD64 displacement; + ULONG frame; + BOOL symInit; + char symName[(MAX_PATH * sizeof(TCHAR))]; + char storage[sizeof(IMAGEHLP_SYMBOL64) + (sizeof(symName))]; + IMAGEHLP_SYMBOL64* symbol; + + RtlCaptureContext(&context); + memset(&stack, 0, sizeof(STACKFRAME64)); +#if defined(_AMD64_) + stack.AddrPC.Offset = context.Rip; + stack.AddrPC.Mode = AddrModeFlat; + stack.AddrStack.Offset = context.Rsp; + stack.AddrStack.Mode = AddrModeFlat; + stack.AddrFrame.Offset = context.Rbp; + stack.AddrFrame.Mode = AddrModeFlat; +#else + stack.AddrPC.Offset = context.Eip; + stack.AddrPC.Mode = AddrModeFlat; + stack.AddrStack.Offset = context.Esp; + stack.AddrStack.Mode = AddrModeFlat; + stack.AddrFrame.Offset = context.Ebp; + stack.AddrFrame.Mode = AddrModeFlat; +#endif + displacement = 0; + process = GetCurrentProcess(); + symInit = SymInitialize(process, "plugins", TRUE); + symbol = (IMAGEHLP_SYMBOL64*) storage; + + if (!symInit) + { + int written = snprintf(reportBufferPtr, reportBufferRemaining, "(symbol init failed)\r\n"); + if (written > 0) + { + reportBufferPtr += written; + reportBufferRemaining -= written; + } + return; + } + + for (frame = 0; reportBufferRemaining > 0; frame++) + { + BOOL result = StackWalk(IMAGE_FILE_MACHINE_AMD64, + process, + GetCurrentThread(), + &stack, + &context, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL); + + if (result) + { + symbol->SizeOfStruct = sizeof(storage); + symbol->MaxNameLength = sizeof(symName); + + BOOL symResult = SymGetSymFromAddr64(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbol); + if (symResult) { + UnDecorateSymbolName(symbol->Name, (PSTR)symName, sizeof(symName), UNDNAME_COMPLETE); + } + + int written = snprintf( + reportBufferPtr, + reportBufferRemaining, + "%02u 0x%p %s\r\n", + frame, + stack.AddrPC.Offset, + symResult ? symbol->Name : "Unknown" + ); + if (written > 0) + { + if (written <= reportBufferRemaining) + { + reportBufferPtr += written; + reportBufferRemaining -= written; + } + else + { + reportBufferPtr += reportBufferRemaining; + reportBufferRemaining = 0; + } + } + } + else + { + break; + } + } +} + static void ScaleWindow(HWND wnd, int x, int y, int w, int h) { int dpi = GetDpiForWindow(wnd); @@ -273,88 +369,7 @@ static LONG crashHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) reportBufferRemaining -= written; // Create stack trace - - STACKFRAME64 stack; - CONTEXT context; - HANDLE process; - DWORD64 displacement; - ULONG frame; - BOOL symInit; - char symName[(MAX_PATH * sizeof(TCHAR))]; - char storage[sizeof(IMAGEHLP_SYMBOL64) + (sizeof(symName))]; - IMAGEHLP_SYMBOL64* symbol; - - RtlCaptureContext(&context); - memset(&stack, 0, sizeof(STACKFRAME64)); -#if defined(_AMD64_) - stack.AddrPC.Offset = context.Rip; - stack.AddrPC.Mode = AddrModeFlat; - stack.AddrStack.Offset = context.Rsp; - stack.AddrStack.Mode = AddrModeFlat; - stack.AddrFrame.Offset = context.Rbp; - stack.AddrFrame.Mode = AddrModeFlat; -#else - stack.AddrPC.Offset = context.Eip; - stack.AddrPC.Mode = AddrModeFlat; - stack.AddrStack.Offset = context.Esp; - stack.AddrStack.Mode = AddrModeFlat; - stack.AddrFrame.Offset = context.Ebp; - stack.AddrFrame.Mode = AddrModeFlat; -#endif - displacement = 0; - process = GetCurrentProcess(); - symInit = SymInitialize(process, "plugins", TRUE); - symbol = (IMAGEHLP_SYMBOL64*) storage; - - for (frame = 0; reportBufferRemaining > 0; frame++) - { - BOOL result = StackWalk(IMAGE_FILE_MACHINE_AMD64, - process, - GetCurrentThread(), - &stack, - &context, - NULL, - SymFunctionTableAccess64, - SymGetModuleBase64, - NULL); - - if (result) - { - symbol->SizeOfStruct = sizeof(storage); - symbol->MaxNameLength = sizeof(symName); - - BOOL symResult = SymGetSymFromAddr64(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbol); - if (symResult) { - UnDecorateSymbolName(symbol->Name, (PSTR)symName, sizeof(symName), UNDNAME_COMPLETE); - } - - written = snprintf( - reportBufferPtr, - reportBufferRemaining, - "%02u 0x%p %s\r\n", - frame, - stack.AddrPC.Offset, - symResult ? symbol->Name : "Unknown" - ); - if (written > 0) - { - if (written <= reportBufferRemaining) - { - reportBufferPtr += written; - reportBufferRemaining -= written; - } - else - { - reportBufferPtr += reportBufferRemaining; - reportBufferRemaining = 0; - } - } - } - else - { - break; - } - } + appendStackTrace(reportBufferPtr, reportBufferRemaining); // Append log file if (crashLogger) @@ -432,3 +447,22 @@ void installCrashHandler(qtwebapp::LoggerWithFile *logger) crashLogger = logger; SetUnhandledExceptionFilter(crashHandler); } + +void logWindowsStackTrace() +{ + const int reportBufferSize = 64 * 1024; + char *reportBuffer = new char[reportBufferSize]; + char *reportBufferPtr = reportBuffer; + int reportBufferRemaining = reportBufferSize; + + int written = snprintf(reportBufferPtr, reportBufferRemaining, "Stack trace (DbgHelp):\r\n"); + if (written > 0) + { + reportBufferPtr += written; + reportBufferRemaining -= written; + } + + appendStackTrace(reportBufferPtr, reportBufferRemaining); + qCritical("%s", reportBuffer); + delete[] reportBuffer; +} diff --git a/app/main.cpp b/app/main.cpp index f6b1a8b59..4d9d4d93b 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -28,6 +28,11 @@ #include #include #include +#include +#if defined(HAVE_LIBUNWIND) +#include +#include +#endif #ifdef __APPLE__ #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) #include @@ -47,6 +52,50 @@ #include "dsp/dsptypes.h" #include "crashhandler.h" +static void logExceptionStackTrace() +{ +#if defined(HAVE_LIBUNWIND) + unw_context_t context; + unw_cursor_t cursor; + if (unw_getcontext(&context) != 0) { + qCritical("Failed to capture unwind context."); + return; + } + if (unw_init_local(&cursor, &context) != 0) { + qCritical("Failed to initialize unwind cursor."); + return; + } + + qCritical("Stack trace (libunwind):"); + int frame = 0; + while (unw_step(&cursor) > 0 && frame < 64) { + unw_word_t pc = 0; + unw_word_t offset = 0; + char symbol[256]; + symbol[0] = '\0'; + + if (unw_get_reg(&cursor, UNW_REG_IP, &pc) != 0) { + pc = 0; + } + + if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) { + int status = 0; + char *demangled = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); + const char *name = (status == 0 && demangled) ? demangled : symbol; + qCritical("#%d 0x%llx %s + 0x%llx", frame, static_cast(pc), name, + static_cast(offset)); + std::free(demangled); + } else { + qCritical("#%d 0x%llx ", frame, static_cast(pc)); + } + + ++frame; + } +#else + qCritical("Stack trace unavailable (libunwind not enabled)."); +#endif +} + class SDRangelApplication final : public QApplication { public: @@ -66,10 +115,25 @@ public: } else { qCritical() << "Failed on object: (null)"; } +#ifdef _WIN32 + logWindowsStackTrace(); +#else + logExceptionStackTrace(); +#endif } catch (const std::exception &ex) { qCritical("Unhandled exception in event handler: %s", ex.what()); +#ifdef _WIN32 + logWindowsStackTrace(); +#else + logExceptionStackTrace(); +#endif } catch (...) { qCritical("Unhandled non-standard exception in event handler."); +#ifdef _WIN32 + logWindowsStackTrace(); +#else + logExceptionStackTrace(); +#endif } if (!s_exitRequested) { diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 6b24a3922..f4662daa0 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -23,6 +23,11 @@ #include #include #include +#include +#if defined(HAVE_LIBUNWIND) +#include +#include +#endif #include #include @@ -33,6 +38,50 @@ #include "remotetcpsinkstarter.h" #include "dsp/dsptypes.h" +static void logExceptionStackTrace() +{ +#if defined(HAVE_LIBUNWIND) + unw_context_t context; + unw_cursor_t cursor; + if (unw_getcontext(&context) != 0) { + qCritical("Failed to capture unwind context."); + return; + } + if (unw_init_local(&cursor, &context) != 0) { + qCritical("Failed to initialize unwind cursor."); + return; + } + + qCritical("Stack trace (libunwind):"); + int frame = 0; + while (unw_step(&cursor) > 0 && frame < 64) { + unw_word_t pc = 0; + unw_word_t offset = 0; + char symbol[256]; + symbol[0] = '\0'; + + if (unw_get_reg(&cursor, UNW_REG_IP, &pc) != 0) { + pc = 0; + } + + if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) { + int status = 0; + char *demangled = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); + const char *name = (status == 0 && demangled) ? demangled : symbol; + qCritical("#%d 0x%llx %s + 0x%llx", frame, static_cast(pc), name, + static_cast(offset)); + std::free(demangled); + } else { + qCritical("#%d 0x%llx ", frame, static_cast(pc)); + } + + ++frame; + } +#else + qCritical("Stack trace unavailable (libunwind not enabled)."); +#endif +} + class SDRangelServerApplication final : public QCoreApplication { public: @@ -52,10 +101,13 @@ public: } else { qCritical() << "Failed on object: (null)"; } + logExceptionStackTrace(); } catch (const std::exception &ex) { qCritical("Unhandled exception in event handler: %s", ex.what()); + logExceptionStackTrace(); } catch (...) { qCritical("Unhandled non-standard exception in event handler."); + logExceptionStackTrace(); } if (!s_exitRequested) { diff --git a/debian/control b/debian/control index 3e152564d..5c0f6a9f9 100644 --- a/debian/control +++ b/debian/control @@ -55,7 +55,8 @@ Build-Depends: debhelper (>= 9), libhackrf-dev, libuhd-dev, libhamlib-dev, - zlib1g-dev + zlib1g-dev, + libunwind-dev # TODO: # - more dependencies based on version; newer has more devices # - manage dependencies not present upstream