在用戶使用軟件的過程當中突然產生軟件崩潰的問題,必須采取相關的措施去攔截崩潰產生的原因,這有助於程序員解決此類崩潰的再次發生。特別是有些難以復現的崩潰,不穩定的崩潰,更有必要去調查崩潰產生的原因。一般來說,崩潰報告中需要記錄的信息主要包含以下幾點:
1.產生崩潰時電腦的硬件相關信息
2.崩潰發生的時間
3.最重要的,即崩潰時的函數調用堆棧信息
4.用戶可以手動填寫如何制造崩潰的方法,方便復現崩潰
關於dump文件,可以用於恢復崩潰時的函數堆棧信息,方便程序員調試
接下來簡述一下如何利用win32系統函數制作崩潰報告。
首先就是一個輸出崩潰報告的函數:
void CrashHandler::reportCrash() { //將getStackTrace()中讀取到的堆棧日志記錄下來 logErrorAndStackTrace(getStackTrace()); //保存錯誤日志 saveCrashLog(); //保存dump文件 mCrashMiniDumpPath = mSavePath + sMiniDumpName; win32_writeMiniDump(mCrashMiniDumpPath, nullptr); }
函數主要由兩部分組成,第一部分是利用win32的函數調取函數堆棧信息,並將這些信息保存至日志當中;第二部分則是獲取dump文件並保存。
先來看看第一部分是如何做的:
std::string CrashHandler::getStackTrace() { CONTEXT context; RtlCaptureContext(&context); win32_initPSAPI(); win32_loadSymbols(); return win32_getStackTrace(context, 2); }
其中win32_loadSymbols()函數主要用於獲取堆棧中函數名等標志信息,如果不獲取這些標志信息,則堆棧信息中只有十六進制的地址,無法通過這個地址訪問到其他有用的信息,這樣即時記錄了函數堆棧信息也沒有任何意義了。(注意要開啟debug或者debugWithRelease版本去編譯才可以獲得標志信息,使用release版本編譯無法調試,一樣無法獲得標志信息),接下來win32_getStackTrace函數正式獲取到了函數堆棧信息,下面寫一下這幾個函數的具體實現方案:
void CrashHandler::win32_loadSymbols() { if (gSymbolsLoaded) return; HANDLE hProcess = GetCurrentProcess(); UINT32 options = SymGetOptions(); options |= SYMOPT_LOAD_LINES; options |= SYMOPT_EXACT_SYMBOLS; options |= SYMOPT_UNDNAME; options |= SYMOPT_FAIL_CRITICAL_ERRORS; options |= SYMOPT_NO_PROMPTS; SymSetOptions(options); if (!SymInitialize(hProcess, nullptr, false)) { Log::message("SymInitialize failed.Error code : %d", (UINT32)GetLastError()); return; } DWORD bufferSize; gEnumProcessModules(hProcess, nullptr, 0, &bufferSize); HMODULE* modules = (HMODULE*)malloc(bufferSize); gEnumProcessModules(hProcess, modules, bufferSize, &bufferSize); UINT32 numModules = bufferSize / sizeof(HMODULE); for (UINT32 i = 0; i < numModules; i++) { MODULEINFO moduleInfo; char moduleName[MAX_STACKTRACE_NAME_BYTES]; char imageName[MAX_STACKTRACE_NAME_BYTES]; gGetModuleInformation(hProcess, modules[i], &moduleInfo, sizeof(moduleInfo)); gGetModuleFileNameEx(hProcess, modules[i], imageName, MAX_STACKTRACE_NAME_BYTES); gGetModuleBaseName(hProcess, modules[i], moduleName, MAX_STACKTRACE_NAME_BYTES); char pdbSearchPath[MAX_STACKTRACE_NAME_BYTES]; char* fileName = nullptr; GetFullPathNameA(moduleName, MAX_STACKTRACE_NAME_BYTES, pdbSearchPath, &fileName); *fileName = '\0'; SymSetSearchPath(GetCurrentProcess(), pdbSearchPath); DWORD64 moduleAddress = SymLoadModule64(hProcess, modules[i], imageName, moduleName, (DWORD64)moduleInfo.lpBaseOfDll, (DWORD)moduleInfo.SizeOfImage); if (moduleAddress != 0) { IMAGEHLP_MODULE64 imageInfo; memset(&imageInfo, 0, sizeof(imageInfo)); imageInfo.SizeOfStruct = sizeof(imageInfo); if (!SymGetModuleInfo64(GetCurrentProcess(), moduleAddress, &imageInfo)) { Log::message("Warning:Failed retrieving module info for module: %s Error code: %d", moduleName, (UINT32)GetLastError()); } else { // Disabled because too much spam in the log, enable as needed } } else { Log::message("Warning:Failed loading module %s.Error code: %d. Search path: %s. Image name: %s", moduleName, (UINT32)GetLastError(), pdbSearchPath, imageName); } } free(modules); gSymbolsLoaded = true; }
std::string CrashHandler::win32_getStackTrace(CONTEXT context, UINT32 skip) { UINT64 rawStackTrace[MAX_STACKTRACE_DEPTH]; UINT32 numEntries = win32_getRawStackTrace(context, rawStackTrace); numEntries = min((UINT32)MAX_STACKTRACE_DEPTH, numEntries); UINT32 bufferSize = sizeof(PIMAGEHLP_SYMBOL64) + MAX_STACKTRACE_NAME_BYTES; UINT8* buffer = (UINT8*)malloc(bufferSize); PIMAGEHLP_SYMBOL64 symbol = (PIMAGEHLP_SYMBOL64)buffer; symbol->SizeOfStruct = bufferSize; symbol->MaxNameLength = MAX_STACKTRACE_NAME_BYTES; HANDLE hProcess = GetCurrentProcess(); std::stringstream outputStream; for (UINT32 i = skip; i < numEntries; i++) { if (i > skip) outputStream << std::endl; DWORD64 funcAddress = rawStackTrace[i]; // Output function name DWORD64 dummy; if (SymGetSymFromAddr64(hProcess, funcAddress, &dummy, symbol)) { outputStream << std::string(symbol->Name) + "() - "; } // Output file name and line IMAGEHLP_LINE64 lineData; lineData.SizeOfStruct = sizeof(lineData); std::string addressString = std::to_string(funcAddress); DWORD column; if (SymGetLineFromAddr64(hProcess, funcAddress, &column, &lineData)) { std::string filePath = lineData.FileName; outputStream << "0x" + addressString + " File[" + filePath + ":" + std::to_string(lineData.LineNumber) + " (" + std::to_string(column) + ")]"; } else { outputStream << "0x" + addressString; } // Output module name IMAGEHLP_MODULE64 moduleData; moduleData.SizeOfStruct = sizeof(moduleData); if (SymGetModuleInfo64(hProcess, funcAddress, &moduleData)) { std::string filePath = moduleData.ImageName; outputStream << " Module[" + filePath + "]"; } } free(buffer); return outputStream.str(); }
接下來就是dump文件的記錄,由win32_writeMiniDump()函數完成
void CrashHandler::win32_writeMiniDump(const std::string& filePath, EXCEPTION_POINTERS* exceptionData) { MiniDumpParams param = { filePath, exceptionData }; // Write minidump on a second thread in order to preserve the current thread's call stack DWORD threadId = 0; HANDLE hThread = CreateThread(nullptr, 0, &win32_writeMiniDumpWorker, ¶m, 0, &threadId); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); }
其中win32_writeMiniDumpWorker()用於導出dump文件,實現方案如下:
DWORD CALLBACK win32_writeMiniDumpWorker(void* data) { CrashHandler::MiniDumpParams* params = (CrashHandler::MiniDumpParams*)data; std::wstring pathString = string2wstring(params->filePath); HANDLE hFile = CreateFileW(pathString.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo; DumpExceptionInfo.ThreadId = GetCurrentThreadId(); DumpExceptionInfo.ExceptionPointers = params->exceptionData; DumpExceptionInfo.ClientPointers = false; MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &DumpExceptionInfo, nullptr, nullptr); CloseHandle(hFile); } return 0; }