GrabBag/QtUtils/Src/CrashHandler.cpp

199 lines
6.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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);
}