向大厂看齐!为自己的程序增加自动转储的功能!
如果你還不清楚什么是轉儲文件,不知道什么時候需要轉儲文件,請參考轉儲文件系列文章的第一篇 —— 轉儲文件知多少。
前言
不知道各位小伙伴有沒有遇到過 微信 或者 QQ 崩潰的情況。它們在崩潰的時候都會自動彈出一個對話框,提示用戶上傳相關文件,供開發人員分析問題的原因。有的小伙伴兒可能不太清楚我在說什么。沒關系,下圖就是微信崩潰后自動彈出的界面。
微信 Crash Report 界面如果勾選了 發送錯誤報告(S) 按鈕,點擊 確定(O) 按鈕后,會把收集到的文件上傳給開發人員。同樣的,如果勾選了 重啟程序(R),點擊 確定(O) 按鈕后,微信會自動重啟。有沒有覺得很酷?我們自己的程序可以做到類似的效果嗎?答案是肯定的。如果感興趣,就請繼續閱讀吧!
MiniDumpWriteDump
微軟提供了專門的 API 來生成轉儲文件,這個 API 就是 MiniDumpWriteDump()。
BOOL MiniDumpWriteDump(HANDLE hProcess,DWORD ProcessId,HANDLE hFile,MINIDUMP_TYPE DumpType,PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,PMINIDUMP_CALLBACK_INFORMATION CallbackParam );簡單介紹下每個參數:
hProcess:要轉儲的進程句柄。
打開進程的時候,至少需要指定 PROCESS_QUERY_INFORMATION 和 PROCESS_VM_READ 權限。如果需要轉儲 句柄信息,還需要 PROCESS_DUP_HANDLE 權限。如果需要轉儲線程信息,需要 THREAD_ALL_ACCESS 權限。
ProcessId:要轉儲的進程ID。
有點不太理解為什么要額外傳遞一個進程 ID 的參數,調用GetProcessId() 就可以根據 hProcess 獲取進程 ID 了,難道是為了效率?希望有知道的小伙伴兒指點一二。
hFile:通過 CreateFile() 等 API 打開的,用來保存 dump 的文件句柄。
DumpType:轉儲類型。此參數會直接影響轉儲文件的大小,如果想自己寫一個手動收集 dump 的工具,了解下這個參數會很有用。稍后介紹。
ExceptionParam:指向異常信息結構 MINIDUMP_EXCEPTION_INFORMATION 的指針。如果本參數為 NULL,則轉儲文件中不會包含異常信息。
UserStreamParam:指向用戶自定義信息結構 MINIDUMP_USER_STREAM_INFORMATION 的指針。如果本參數為 NULL,則轉儲文件中不會包含用戶定義的信息。
CallbackParam:指向回調例程 MINIDUMP_CALLBACK_INFORMATION 的指針。如果此參數為NULL,轉儲過程中不會執行任何回調例程。
本結構中的CallbackParam 是傳遞給回調函數的參數,用戶可以指定一些自己需要傳遞的參數,很常見的做法。CallbackRoutine 是回調函數,類型為 MINIDUMP_CALLBACK_ROUTINE 。原型如下:
typedefBOOL(WINAPI * MINIDUMP_CALLBACK_ROUTINE) (_Inout_ PVOID CallbackParam,_In_ PMINIDUMP_CALLBACK_INPUT CallbackInput,_Inout_ PMINIDUMP_CALLBACK_OUTPUT CallbackOutput);因為 DumpType會影響最后生成的轉儲文件的大小,這里介紹下這個參數。
DumpType 參數
DumpType 類型為 MINIDUMP_TYPE。以下定義摘自 10.0.18362.0 版本的 minidumpapiset.h,應該是比較全的了。如果你問我:你怎么知道 MINIDUMP_TYPE 定義在這個文件里?答案很簡單:用 File Locator 搜的唄。
搜索結果typedefenum _MINIDUMP_TYPE {MiniDumpNormal = 0x00000000,MiniDumpWithDataSegs = 0x00000001,MiniDumpWithFullMemory = 0x00000002,MiniDumpWithHandleData = 0x00000004,MiniDumpFilterMemory = 0x00000008,MiniDumpScanMemory = 0x00000010,MiniDumpWithUnloadedModules = 0x00000020,MiniDumpWithIndirectlyReferencedMemory = 0x00000040,MiniDumpFilterModulePaths = 0x00000080,MiniDumpWithProcessThreadData = 0x00000100,MiniDumpWithPrivateReadWriteMemory = 0x00000200,MiniDumpWithoutOptionalData = 0x00000400,MiniDumpWithFullMemoryInfo = 0x00000800,MiniDumpWithThreadInfo = 0x00001000,MiniDumpWithCodeSegs = 0x00002000,MiniDumpWithoutAuxiliaryState = 0x00004000,MiniDumpWithFullAuxiliaryState = 0x00008000,MiniDumpWithPrivateWriteCopyMemory = 0x00010000,MiniDumpIgnoreInaccessibleMemory = 0x00020000,MiniDumpWithTokenInformation = 0x00040000,MiniDumpWithModuleHeaders = 0x00080000,MiniDumpFilterTriage = 0x00100000,MiniDumpWithAvxXStateContext = 0x00200000,MiniDumpWithIptTrace = 0x00400000,MiniDumpScanInaccessiblePartialPages = 0x00800000,MiniDumpValidTypeFlags = 0x00ffffff, } MINIDUMP_TYPE;下面是每個選項的意義,主要翻譯自官方幫助文檔。
| MiniDumpNormal | 只包含調用棧相關信息 |
| MiniDumpWithDataSegs | 包含已加載的模塊的數據段信息,比如全局變量 |
| MiniDumpWithFullMemory | 包含全部可訪問的內存 |
| MiniDumpWithHandleData | 包含句柄信息 |
| MiniDumpFilterMemory | 過濾一些敏感信息,保護重建調用棧需要的信息 |
| MiniDumpScanMemory | 掃描,以包含引用內存 |
| MiniDumpWithUnloadedModules | 包含最近被卸載的模塊信息 |
| MiniDumpWithIndirectlyReferencedMemory | 包含未直接引用的內存 |
| MiniDumpFilterModulePaths | 過濾某塊的路徑信息 |
| MiniDumpWithProcessThreadData | 包含完整的進程和線程信息 |
| MiniDumpWithPrivateReadWriteMemory | 包含頁面屬性為 PAGE_READWRITE 的頁面 |
| MiniDumpWithoutOptionalData | 不包含可選數據 |
| MiniDumpWithFullMemoryInfo | 包含內存區信息 |
| MiniDumpWithThreadInfo | 包含線程狀態信息 |
| MiniDumpWithCodeSegs | 包含所有代碼和有關的內存段 |
| MiniDumpWithoutAuxiliaryState | 關閉輔助內存收集 |
| MiniDumpWithFullAuxiliaryState | 使用所有的內存收集器 |
| MiniDumpWithPrivateWriteCopyMemory | 包含頁面屬性為 PAGE_WRITECOPY 的頁面 |
| MiniDumpIgnoreInaccessibleMemory | 忽略不可訪問的頁面 |
| MiniDumpWithTokenInformation | 包含安全令牌相關信息。可以在調試的時候使用 "!token" 命令 |
| MiniDumpWithModuleHeaders | 包含模塊頭相關信息 |
| MiniDumpFilterTriage | 添加與篩選器分類相關的數據 |
| MiniDumpWithAvxXStateContext | |
| MiniDumpWithIptTrace | |
| MiniDumpValidTypeFlags | 設置所有標志位 |
說明:
MINIDUMP_TYPE 的值是不斷發展變化的(向后兼容),舊版本的 DbgHelp.dll 可能不支持某些值,具體可以參考 微軟官方介紹 MINIDUMP_TYPE 的文檔[1]。
關于MINIDUMP_TYPE 每一項的作用更為詳細的介紹請參考 Effective minidumps (Part 1)[2] 和 Effective minidumps (Part 2)[3]。真的是超級詳細,強烈建議大家點開看一看!唯一的遺憾是作者寫的比較早,很多新出現的標志沒總結進來,但仍然是非常好的參考資料!
MiniDumpWriteDump() 可以用來生成轉儲文件,我們應該怎么使用呢?
使用場景
通常,我們希望在自己的程序發生異常的時候,能自動保存一份轉儲文件,供我們事后分析。我們需要做的大概是:捕獲各種異常,在異常處理函數中判斷發生的異常是否能恢復,如果不能恢復就保存轉儲文件和其它一些關鍵文件(比如,程序的配置文件,出現問題時的屏幕截圖等),并一起打包并保存,然后提示用戶,讓用戶上傳我們打包好的文件供我們分析。當程序發生異常的時候,我們很難確定發生異常的進程的運行狀態。所以我們需要啟動一個新的進程,并在新進程中調用 MiniDumpWriteDump() 來保存異常進程的信息。
以上的操作看起來比較簡單,但是處理起來,有很多細節需要考慮。比如,我們需要捕獲哪些異常?怎么捕獲這些異常?保存好的文件怎么上傳?通過什么形式上傳?等等等等…… 如果有可以直接拿來使用的框架,就太好了!別說,還真有!
相關開源庫
以上的這些功能,早已經有開源軟件可以用了。作為一個謙卑的程序員,盡量復用現有的輪子吧。給大家推薦一些不錯的開源庫。
CrashRpt[4] (之前在項目里用過,如果你想自己手動實現一個類似的,可以參考此項目的代碼)。
除了 CrashRpt,chromium 里使用的 crashpad[5] 也是一個非常好的選擇。google 出品,質量有保障。
crashpad 的前身是 breakpad[6](根據 google 官方介紹,breakpad 使用的是進程內報告機制,不再建議大家使用)。
適用于.NET程序的 CrashReporter[7]。
其它平臺(Android,iOS等)也有類似的開源庫。可以在 github 上搜索 crash report。我就不截圖了。
說明:
本文介紹的方法只適用于我們自己的程序。如果我們想轉儲其它進程,我們需要借助現有工具(當然,如果有時間和精力,也可以自己寫一個)。關于抓取轉儲的工具的介紹,可以參考之前的文章—— 你需要知道的 N 種抓取 dump 的工具。
總結
我們可以通過 MiniDumpWriteDump() 來保存轉儲文件。
我們可以借助 CrashRpt, CrashPad 等開源框架來為我們的程序添加崩潰轉儲功能。
參考資料
《軟件調試》
MiniDumpWriteDump 文檔[8]
Effective minidumps (Part 1)[2]
Effective minidumps (Part 2)[3]
References:
[1]
微軟官方介紹 MINIDUMP_TYPE 的文檔: https://docs.microsoft.com/zh-cn/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type
[2]Effective minidumps (Part 1): http://www.debuginfo.com/articles/effminidumps.html
[3]Effective minidumps (Part 2): http://www.debuginfo.com/articles/effminidumps2.html
[4]CrashRpt: http://crashrpt.sourceforge.net/
[5]crashpad: https://github.com/chromium/crashpad
[6]breakpad: https://github.com/google/breakpad
[7]CrashReporter: https://github.com/ravibpatel/CrashReporter.NET
[8]MiniDumpWriteDump 文檔: https://docs.microsoft.com/zh-cn/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump
猜你喜歡:
轉儲文件系列:
轉儲文件知多少
你需要知道的 N 種抓取 dump 的工具
你生成的轉儲文件有問題嗎?
JIT Debug Info 簡介
調試系列:
調試實戰——你知道怎么使用DebugView查看調試信息嗎?
調試實戰——程序CPU占用率飆升,你知道如何快速定位嗎?
調試實戰——崩潰在ComFriendlyWaitMtaThreadProc
調試實戰——使用windbg調試崩潰在ole32!CStdMarshal::DisconnectSrvIPIDs
調試實戰——調試PInvoke導致的內存破壞
調試實戰——調試excel啟動時死鎖
調試實戰——調試DLL卸載時的死鎖
調試實戰——調試TerminateThread導致的死鎖
排錯系列:
排錯實戰——VS清空最近打開的工程記錄
排錯實戰——拯救加載調試符號失敗的IDA
排錯實戰——你知道拖動窗口時只顯示虛框怎么設置嗎?
排錯實戰——解決Tekla通過.tsep安裝插件失敗的問題
排錯實戰——使用process explorer替換任務管理器
排錯實戰——通過對比分析sysinternals事件修復程序功能異常
歡迎留言交流
總結
以上是生活随笔為你收集整理的向大厂看齐!为自己的程序增加自动转储的功能!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#如何安全、高效地玩转任何种类的内存之
- 下一篇: 在.NET中执行Async/Await的