Windows 程序捕獲崩潰異常 生成dump
概述
事情的起因是,有個同事開發的程序,交付的版本程序,會偶爾隨機崩潰了。
悲催的是沒有輸出log,也沒有輸出dump文件。
我建議他給程序代碼加個異常捕獲,在崩潰時生成dump,方便找出問題點。
隔了一天之后,短暫交流,發現他沒有這個開發經驗,我只好披掛上陣了。
開動
查閱MSDN文檔,和stackoverlfow.com的相關文章,可知
SetUnhandledExceptionFilter 可以捕獲觸發系統崩潰的異常
風風火火開始寫代碼
void exceptionHandler(PEXCEPTION_POINTERS excpInfo) { // your code to handle the exception. Ideally it should // marshal the exception for processing to some other // thread and wait for the thread to complete the job std::unique_lock<std::mutex> lk(g_handlerLock); generateMiniDump(nullptr, excpInfo); } LONG WINAPI unhandledException(PEXCEPTION_POINTERS excpInfo = nullptr) { DebugBreak(); if (excpInfo == nullptr) { __try // Generate exception to get proper context in dump { RaiseException(EXCEPTION_BREAKPOINT, 0, 0, nullptr); } __except (exceptionHandler(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) { } } else { exceptionHandler(excpInfo); } return 0; } SetUnhandledExceptionFilter(unhandledException);
測試
在main函數入口,設置異常處理函數SetUnhandledExceptionFilter。
異常處理函數負責捕獲異常,調用MiniDumpWriteDump生成dump文件,供開發者使用Windbg調試
編譯運行
Access Volation C000005錯誤可以順利捕獲
令人費解的是,
abort,數組越界,虛函數調用異常等均無法捕獲
系統把這些異常給攔截了,並給出了程序崩潰的提示窗口
改進
為了捕獲這些異常並生成dump文件,必須要把系統攔截的那一層給禁止掉
1. 禁止系統彈出崩潰窗口,該窗口提示非常渣,對開發者和用戶都不友好
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
可以模仿騰訊的QQ程序,專門開發一個對用戶界面友好Bug Report的程序,
在程序崩潰時轉存儲dump文件時,運行該程序提示用戶有用的信息。
2. 注冊異常捕獲函數
SetUnhandledExceptionFilter(unhandledException);
當異常發生時,系統會跳進我們的unhandleException回調中
在該回調函數中,我們可以彈出Bug Report這樣的子進程,並存儲異常dump文件
3. 攔截C Runtime的異常處理
_set_invalid_parameter_handler(invalidParameter); _set_purecall_handler(pureVirtualCall); signal(SIGABRT, sigAbortHandler); _set_abort_behavior(0, 0);
這些異常處理只是簡單的調用unhandleException函數
4. 開啟系統的程序崩潰請求
Vista之后,微軟加了一個特性
程序崩潰時,默認不交給程序崩潰處理
而是使用一個莫名其妙的機制,不讓程序進入崩潰環節
搞得用戶懵逼,開發者也讓代碼無法進入崩潰異常處理
void EnableCrashingOnCrashes() { typedef BOOL(WINAPI *tGetPolicy)(LPDWORD lpFlags); typedef BOOL(WINAPI *tSetPolicy)(DWORD dwFlags); const DWORD EXCEPTION_SWALLOWING = 0x1; HMODULE kernel32 = LoadLibraryA("kernel32.dll"); tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy"); tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy"); if (pGetPolicy && pSetPolicy) { DWORD dwFlags; if (pGetPolicy(&dwFlags)) { // Turn off the filter pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING); } } }
5. 斷了系統SetUnhandledExceptionFilter的后路
C Runtime等異常,運行時庫會調用SetUnhandledExceptionFilter向系統注冊一個NULL
從而使得我們之前注冊的回調失效
真是無語(ˉ▽ˉ;)...
在這里,需要hook掉SetUnhandledExceptionFilter,在我們注冊完回調之后,讓它默認不做任何處理
用到Windows核心編程這本書里面,Jeffrey Richter開發的CAPIHook這個模塊
void PreventSetUnhandledExceptionFilter() { CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)ExceptionFilterHookProc); }
其中ExceptionFilterHookProc這個函數是個空函數,無需做多余操作,直接renturn null即可
6. 完整流程
void setExceptionHandlers() { if (!IsDebuggerPresent() && !g_isHandlerSet) { g_isHandlerSet = true; SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); SetUnhandledExceptionFilter(unhandledException); _set_invalid_parameter_handler(invalidParameter); _set_purecall_handler(pureVirtualCall); signal(SIGABRT, sigAbortHandler); _set_abort_behavior(0, 0); EnableCrashingOnCrashes(); PreventSetUnhandledExceptionFilter(); } }
在VS或者Windbg中調試時,我們就沒有必要生成dump文件了
IsDebuggerPresent這個系統API會幫助我們判斷我們是否在調試環境中
總結
即使看起來這么簡單的一個功能,也是需要挺多細節處理的。