【Qt】为程序增加闪退crash报告日志
背景
随着软件代码量的增加,软件崩溃闪退的肯能行越来越大,其中一些是难以复现的,比如访问了访问了非法地址、被操作系统杀死等。
为此,在软件出现闪退情况时,尽可能多的记录闪退发生时信息,对排查闪退原因是非常有帮助的。
实现
因为闪退发生时软件已经不在运行了,因此需要在闪退前就告诉操作系统闪退后需要执行的操作,在Qt中就是在QApplication
的exec()
前调用操作系统提供的接口,注册闪退后的处理函数。
我们以Windows平台为例,在Windows平台,时利用SetUnhandledExceptionFilter函数实现异常(闪退)处理函数的注册的。
简单代码如下:
#include <QApplication>
#ifdef Q_OS_WIN
#include <windows.h>
#include <psapi.h>
#include <DbgHelp.h>
#include <fstream>
#include <sstream>
#pragma comment(lib, "DbgHelp.lib")
LONG WINAPI windowsCrashHandler(EXCEPTION_POINTERS* ex) {
SYSTEMTIME time;
GetLocalTime(&time);
char logName[256];
// 文件名格式crash_yyyymmdd_hhmmss.log
sprintf(logName, "crash_%04d%02d%02d_%02d%02d%02d.log",
time.wYear, time.wMonth, time.wDay,
time.wHour, time.wMinute, time.wSecond);
// 打开日志文件
std::ofstream logFile(logName);
if (!logFile.is_open()) return EXCEPTION_EXECUTE_HANDLER;
// 记录异常信息
logFile << "=== Exception: "
<< ex->ExceptionRecord->ExceptionCode
<<" ==="
<< std::endl;
// 记录内存占用(Windows)
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) {
logFile << "总内存:" << statex.ullTotalPhys / (1024 * 1024) << " MB" << std::endl;
}
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
logFile << "内存占用: "
<< pmc.WorkingSetSize / (1024 * 1024)
<< " MB" << std::endl;
logFile << "Error Code: 0x" << std::hex << ex->ExceptionRecord->ExceptionCode << std::endl;
// 获取调用堆栈
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
SymInitialize(process, NULL, TRUE); // 初始化符号表
// 遍历堆栈帧
STACKFRAME64 stackFrame = {{0}};
stackFrame.AddrPC.Offset = ex->ContextRecord->Rip; // x86 用 Eip, x64 用 Rip
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = ex->ContextRecord->Rsp; // x86 用 Esp, x64 用 Rsp
stackFrame.AddrStack.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = ex->ContextRecord->Rbp; // x86 用 Ebp, x64 用 Rbp
stackFrame.AddrFrame.Mode = AddrModeFlat;
DWORD imageType;
#ifdef _M_IX86
imageType = IMAGE_FILE_MACHINE_I386;
#elif _M_X64
imageType = IMAGE_FILE_MACHINE_AMD64;
#endif
logFile << "调用堆栈:" << std::endl;
int frameNum = 0;
while (StackWalk64(imageType, process, thread, &stackFrame, ex->ContextRecord,
NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
// 获取符号信息
BYTE symbolBuffer[sizeof(SYMBOL_INFO) + 256] = {0};
SYMBOL_INFO* symbol = (SYMBOL_INFO*)symbolBuffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = 255;
DWORD64 displacement = 0;
if (SymFromAddr(process, stackFrame.AddrPC.Offset, &displacement, symbol)) {
logFile << "[" << frameNum << "] " << symbol->Name << std::endl;
} else {
logFile << "[" << frameNum << "] Unknown Address" << std::endl;
}
frameNum++;
}
// 清理符号表
SymCleanup(process);
logFile.close();
// 退出程序
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
// Windows 注册异常(闪退)处理函数
SetUnhandledExceptionFilter(windowsCrashHandler);
#endif
QApplication a(argc, argv);
return a.exec();
}
这样在程序出现闪退后,就可以看到闪退时计算机内存的占用情况以及引起闪退的调用堆栈。