GrabBag/AppUtils/UICommon/Src/CrashHandler.cpp

199 lines
6.5 KiB
C++
Raw Normal View History

2025-10-12 16:46:46 +08:00
#include "CrashHandler.h"
#include <QDir>
#include <QDateTime>
#include <QStandardPaths>
#include <QDebug>
#include <QApplication>
#include <QMessageBox>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef Q_OS_WIN
#include <windows.h>
#ifdef _MSC_VER
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
#endif
#endif
QString CrashHandler::dumpPath;
void CrashHandler::registerCrashHandler()
{
signal(SIGSEGV, crashHandlerFunction); // 段错误
signal(SIGABRT, crashHandlerFunction); // abort调用
signal(SIGFPE, crashHandlerFunction); // 浮点异常
signal(SIGILL, crashHandlerFunction); // 非法指令
#ifdef Q_OS_WIN
SetUnhandledExceptionFilter(windowsCrashHandler);
#endif
}
void CrashHandler::setDumpPath(const QString& path)
{
dumpPath = path;
QDir dir(dumpPath);
if (!dir.exists()) {
dir.mkpath(dumpPath);
}
}
QString CrashHandler::getDumpPath()
{
if (dumpPath.isEmpty()) {
dumpPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/crash_dumps";
QDir dir(dumpPath);
if (!dir.exists()) {
dir.mkpath(dumpPath);
}
}
return dumpPath;
}
#ifdef Q_OS_WIN
LONG WINAPI CrashHandler::windowsCrashHandler(struct _EXCEPTION_POINTERS* exceptionInfo)
{
// 使用 C 函数生成时间戳,避免依赖 Qt可能已损坏
time_t now = time(NULL);
struct tm timeinfo;
localtime_s(&timeinfo, &now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &timeinfo);
// 获取dump路径
QString dumpDir = CrashHandler::getDumpPath();
// 创建目录(如果不存在)
CreateDirectoryW((WCHAR*)dumpDir.utf16(), NULL);
// 构建文件路径
QString dumpFilePath = dumpDir + "/crash_dump_" + QString(timestamp) + ".dmp";
QString logFilePath = dumpDir + "/crash_log_" + QString(timestamp) + ".txt";
// 先写日志文件
FILE* logFile = _wfopen((WCHAR*)logFilePath.utf16(), L"w");
if (logFile) {
fprintf(logFile, "Application crashed at: %s\n", timestamp);
fprintf(logFile, "Exception Code: 0x%08X\n", exceptionInfo->ExceptionRecord->ExceptionCode);
fprintf(logFile, "Exception Address: 0x%p\n", exceptionInfo->ExceptionRecord->ExceptionAddress);
fprintf(logFile, "Dump file: %s\n", dumpFilePath.toLocal8Bit().constData());
fclose(logFile);
}
#ifdef _MSC_VER
// 创建 dump 文件
HANDLE hFile = CreateFileW((WCHAR*)dumpFilePath.utf16(),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exceptionInfoEx;
exceptionInfoEx.ThreadId = GetCurrentThreadId();
exceptionInfoEx.ExceptionPointers = exceptionInfo;
exceptionInfoEx.ClientPointers = FALSE;
// 使用更详细的 dump 类型
MINIDUMP_TYPE dumpType = (MINIDUMP_TYPE)(
MiniDumpWithDataSegs |
MiniDumpWithHandleData |
MiniDumpWithThreadInfo);
BOOL success = MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
dumpType,
&exceptionInfoEx,
NULL,
NULL);
CloseHandle(hFile);
if (success) {
// 成功写入,显示消息框
wchar_t msg[512];
swprintf_s(msg, 512, L"程序崩溃!\n\nDump文件已保存至:\n%s\n\n请联系技术支持。",
(WCHAR*)dumpFilePath.utf16());
MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST);
} else {
DWORD error = GetLastError();
wchar_t msg[256];
swprintf_s(msg, 256, L"程序崩溃!\n\n无法创建dump文件错误代码: %d\n路径: %s",
error, (WCHAR*)dumpFilePath.utf16());
MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST);
}
} else {
DWORD error = GetLastError();
wchar_t msg[256];
swprintf_s(msg, 256, L"程序崩溃!\n\n无法创建dump文件文件创建失败错误代码: %d\n路径: %s",
error, (WCHAR*)dumpFilePath.utf16());
MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST);
}
#else
// 非 MSVC 编译器,只显示崩溃信息
wchar_t msg[256];
swprintf_s(msg, 256, L"程序崩溃!\n\n日志文件: %s", (WCHAR*)logFilePath.utf16());
MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST);
#endif
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
void CrashHandler::crashHandlerFunction(int sig)
{
// 使用 C 函数生成时间戳,避免依赖可能已损坏的 Qt
time_t now = time(NULL);
struct tm timeinfo;
#ifdef Q_OS_WIN
localtime_s(&timeinfo, &now);
#else
localtime_r(&now, &timeinfo);
#endif
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &timeinfo);
QString dumpDir = getDumpPath();
QString dumpFilePath = dumpDir + "/crash_signal_" + QString(timestamp) + ".txt";
FILE* file = fopen(dumpFilePath.toLocal8Bit().constData(), "w");
if (file) {
fprintf(file, "Crash occurred at: %s\n", timestamp);
fprintf(file, "Signal: %d (", sig);
switch(sig) {
case SIGSEGV: fprintf(file, "SIGSEGV - Segmentation fault"); break;
case SIGABRT: fprintf(file, "SIGABRT - Abort signal"); break;
case SIGFPE: fprintf(file, "SIGFPE - Floating point exception"); break;
case SIGILL: fprintf(file, "SIGILL - Illegal instruction"); break;
default: fprintf(file, "Unknown signal"); break;
}
fprintf(file, ")\n");
if (qApp) {
fprintf(file, "Application: %s\n", qApp->applicationName().toLocal8Bit().constData());
fprintf(file, "Version: %s\n", qApp->applicationVersion().toLocal8Bit().constData());
}
fclose(file);
#ifdef Q_OS_WIN
wchar_t msg[512];
swprintf_s(msg, 512, L"程序崩溃(信号 %d\n\n日志文件已保存至:\n%s",
sig, (WCHAR*)dumpFilePath.utf16());
MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST);
#endif
}
// 调用默认的信号处理函数来终止程序
signal(sig, SIG_DFL);
raise(sig);
}