199 lines
6.5 KiB
C++
199 lines
6.5 KiB
C++
|
|
#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);
|
|||
|
|
}
|