mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-10-13 18:57:48 -04:00
422 lines
12 KiB
C++
422 lines
12 KiB
C++
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
// 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 <http://www.gnu.org/licenses/>. //
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "crashhandler.h"
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <dbghelp.h>
|
||
|
|
||
|
// 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);
|
||
|
}
|