/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2025 Jon Beniston, M7RCE // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // // the Free Software Foundation as version 3 of the License, or // // (at your option) any later version. // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include "crashhandler.h" #include #include // Use common controls v6, rather than v5 #pragma comment(linker,"\"/manifestdependency:type='win32' \ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #define IDC_EXIT_BUTTON 1500 #define IDC_COPY_BUTTON 1501 #define IDC_GITHUB_BUTTON 1502 static HWND hWindow; static HWND hLabel; static HWND hEdit; static HWND hGithubButton; static HWND hCopyButton; static HWND hExitButton; static qtwebapp::LoggerWithFile *crashLogger; static void ScaleWindow(HWND wnd, int x, int y, int w, int h) { int dpi = GetDpiForWindow(wnd); int scaledX = MulDiv(x, dpi, USER_DEFAULT_SCREEN_DPI); int scaledY = MulDiv(y, dpi, USER_DEFAULT_SCREEN_DPI); int scaledW = MulDiv(w, dpi, USER_DEFAULT_SCREEN_DPI); int scaledH = MulDiv(h, dpi, USER_DEFAULT_SCREEN_DPI); SetWindowPos(wnd, wnd, scaledX, scaledY, scaledW, scaledH, SWP_NOZORDER | SWP_NOACTIVATE); } static void Scale(HWND wnd, int& w, int &h) { int dpi = GetDpiForWindow(wnd); w = MulDiv(w, dpi, USER_DEFAULT_SCREEN_DPI); h = MulDiv(h, dpi, USER_DEFAULT_SCREEN_DPI); } static void Unscale(HWND wnd, int& w, int &h) { int dpi = GetDpiForWindow(wnd); w = MulDiv(w, USER_DEFAULT_SCREEN_DPI, dpi); h = MulDiv(h, USER_DEFAULT_SCREEN_DPI, dpi); } static void ScaleControls() { RECT rect; int w; int h; GetWindowRect(hWindow, &rect); w = rect.right - rect.left; h = rect.bottom - rect.top; Unscale(hWindow, w, h); int buttonY = h - 100; int editW = w - 40; int editH = h - 200; if (hLabel) { ScaleWindow(hLabel, 10, 10, editW, 70); } if (hEdit) { ScaleWindow(hEdit, 10, 80, editW, editH); } if (hGithubButton) { ScaleWindow(hGithubButton, 10, buttonY, 150, 30); } if (hCopyButton) { ScaleWindow(hCopyButton, 170, buttonY, 150, 30); } if (hExitButton) { ScaleWindow(hExitButton, w - 180, buttonY, 150, 30); } } static void ScaleWindow() { if (hWindow) { RECT rect; GetWindowRect(hWindow, &rect); ScaleWindow(hWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } } static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); switch (wmId) { case IDC_EXIT_BUTTON: PostQuitMessage(0); break; case IDC_GITHUB_BUTTON: // Open SDRangel GitHub issues page in web browser ShellExecute(NULL, L"open", L"https://github.com/f4exb/sdrangel/issues", NULL, NULL, SW_SHOWNORMAL); break; case IDC_COPY_BUTTON: { // Copy contents of edit control to clipboard int len = GetWindowTextLength(hEdit); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(wchar_t)); if (hMem) { void *text = GlobalLock(hMem); if (text) { GetWindowText(hEdit, (LPWSTR) text, len); GlobalUnlock(hMem); OpenClipboard(0); EmptyClipboard(); SetClipboardData(CF_UNICODETEXT, hMem); CloseClipboard(); } } break; } default: return DefWindowProc(hWnd, message, wParam, lParam); } break; } case WM_CREATE: { HINSTANCE hInst = (HINSTANCE) GetWindowLongPtr(hWnd, GWLP_HINSTANCE); LPCREATESTRUCT cs = (LPCREATESTRUCT) lParam; hLabel = CreateWindow(L"Static", L"SDRangel has crashed.\r\n\r\nPlease consider opening a bug report on GitHub, copying the text below and a screenshot.", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL); hEdit = CreateWindowA("EDIT", (LPCSTR) cs->lpCreateParams, WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInst, NULL); hGithubButton = CreateWindow( L"BUTTON", L"Open Github...", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 0, 0, 0, 0, hWnd, (HMENU) IDC_GITHUB_BUTTON, hInst, NULL); hCopyButton = CreateWindow( L"BUTTON", L"Copy text", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 0, 0, 0, 0, hWnd, (HMENU) IDC_COPY_BUTTON, hInst, NULL); hExitButton = CreateWindow( L"BUTTON", L"Exit SDRangel", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 0, 0, 0, 0, hWnd, (HMENU) IDC_EXIT_BUTTON, hInst, NULL); ScaleControls(); break; } case WM_GETMINMAXINFO: { LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam; int w = 500; int h = 220; Scale(hWindow, w, h); lpMMI->ptMinTrackSize.x = w; lpMMI->ptMinTrackSize.y = h; break; } case WM_SIZE: ScaleControls(); RedrawWindow(hWindow, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); break; case WM_DPICHANGED: ScaleControls(); ScaleWindow(); break; case WM_CTLCOLORSTATIC: SetBkMode((HDC) wParam, TRANSPARENT); return (LRESULT) GetStockObject(NULL_BRUSH); case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Create and display crash log when an unhandled exception occurs static LONG crashHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) { const int reportBufferSize = 1024*1024; char *reportBuffer = new char[reportBufferSize]; char *reportBufferPtr = reportBuffer; int reportBufferRemaining = reportBufferSize; int written = snprintf(reportBufferPtr, reportBufferRemaining, "SDRangel crash data\r\n%s %s\r\nQt %s\r\n%s %s\r\nStack trace:\r\n", APPLICATION_NAME, SDRANGEL_VERSION, QT_VERSION_STR, qPrintable(QSysInfo::prettyProductName()), qPrintable(QSysInfo::currentCpuArchitecture()) ); reportBufferPtr += written; 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; } } // Append log file if (crashLogger) { QString log = crashLogger->getBufferLog(); log = log.replace('\n', "\r\n"); written = snprintf(reportBufferPtr, reportBufferRemaining, "Log:\r\n%s\r\n", qPrintable(log)); if (written > 0) { reportBufferPtr += written; reportBufferRemaining -= written; } } // Create a window to display the crash report // Can't use QMessageBox here, as may not work depending where the crash was, so need to use Win32 API SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); // Needed, otherwise GetDpiForWindow always returns 96, rather than the actual value wchar_t windowClass[] = L"SDRangel Crash Window Class"; HMODULE hInstance = GetModuleHandle(NULL); WNDCLASSEX wc = { }; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = windowClass; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1); RegisterClassEx(&wc); hWindow = CreateWindow(windowClass, L"SDRangel Crash", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 640, 500, NULL, NULL, hInstance, (LPVOID) reportBuffer ); if (hWindow) { ScaleWindow(); ShowWindow(hWindow, SW_SHOWNORMAL); UpdateWindow(hWindow); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { fprintf(stderr, "Failed to create window\n"); fprintf(stderr, reportBuffer); } delete[] reportBuffer; return EXCEPTION_EXECUTE_HANDLER; } void installCrashHandler(qtwebapp::LoggerWithFile *logger) { crashLogger = logger; SetUnhandledExceptionFilter(crashHandler); }