背景
隨著軟件代碼量的增加,軟件崩潰閃退的肯能行越來越大,其中一些是難以復現的,比如訪問了訪問了非法地址、被操作系統殺死等。
為此,在軟件出現閃退情況時,盡可能多的記錄閃退發生時信息,對排查閃退原因是非常有幫助的。
實現
因為閃退發生時軟件已經不在運行了,因此需要在閃退前就告訴操作系統閃退后需要執行的操作,在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.logsprintf(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 用 RipstackFrame.AddrPC.Mode = AddrModeFlat;stackFrame.AddrStack.Offset = ex->ContextRecord->Rsp; // x86 用 Esp, x64 用 RspstackFrame.AddrStack.Mode = AddrModeFlat;stackFrame.AddrFrame.Offset = ex->ContextRecord->Rbp; // x86 用 Ebp, x64 用 RbpstackFrame.AddrFrame.Mode = AddrModeFlat;DWORD imageType;
#ifdef _M_IX86imageType = IMAGE_FILE_MACHINE_I386;
#elif _M_X64imageType = IMAGE_FILE_MACHINE_AMD64;
#endiflogFile << "調用堆棧:" << 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;
}
#endifint main(int argc, char *argv[])
{#ifdef Q_OS_WIN// Windows 注冊異常(閃退)處理函數SetUnhandledExceptionFilter(windowsCrashHandler);
#endifQApplication a(argc, argv);return a.exec();
}
這樣在程序出現閃退后,就可以看到閃退時計算機內存的占用情況以及引起閃退的調用堆棧。