簡介:
Breadpad為google chrominum項目下用於處理dump的一套工具;內部采用跨平台方式實現捕獲、生成、解析與平台無關的dump,便於統一處理;支持進程內與進程外捕獲,當為進程外捕獲時,客戶端捕獲異常並告知服務器端抓取該crash並生成相應dump文件。以下僅針對windows平台下進行分析。
項目構成:
Common:公共部分主要有:
GUIDString:得到唯一的guid符號RFC4122格式
(%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x)的字符串GUIDToWString,主要用來在MinidumpGenerator中創建生成的dump文件的名稱,轉化為十六進制大寫的不帶分隔符的字符串GUIDToSymbolServerWString,主要用來作為symbol服務器ID的標識;
WindowsStringUtils:字符串轉換工具,包括:安全拷貝、多字節與寬字節間的轉化、獲取文件路徑中的文件名稱;
HTTPUpload:一個基於WinInet簡單封裝的http文件上傳類,也包含UTF8與UTF16間的轉化,此類用在CrashReportSender中提供上傳生成的dump文件(似乎未用到該方式);
Crash_generation_app:
一個提供使用示例的demo;測試進程外捕獲時需要運行兩次該demo,第一個作為服務器,第二次作為客戶端,服務器也可以捕獲自己的異常crash dump;
Crash_report_sender:
崩潰crash報告發送者,內部會使用HTTPUpload上傳;
ReportResult:定義了四種結果狀態:
RESULT_FAILED:連接服務器失敗,嘗試重試;
RESULT_REJECTED:發送至服務器成功,但服務器拒絕接收,可不再發送此報告;
RESULT_SUCCEEDED:發送至服務器成功並接收;
RESULT_THROTTLED:已到達每天上傳的最大報告數;
checkpoint_file_:報告發送文件檢查點文件,主要用來作為一個上傳報告的識別標識;
max_reports_per_day_:每天至多可上傳的最大報告數;
last_sent_date_:最近一次上傳報告的時間;格式YYYYMMDD;
reports_sent_:最近一天得上傳報告數目;
set_max_reports_per_day:設置每天上傳的最大報告數目;
max_reports_per_day:獲取當前每天上傳的最大報告數目;
CrashReportSender:構造函數,參數checkpoint_file為檢查點文件,並初始化
max_reports_per_day_值為-1,表示無限制、last_sent_date_值為-1、reports_sent_值為0,此外內部還調用OpenCheckpointFile打開文件和ReadCheckpoint讀取到檢查點文件的last_sent_date_和reports_sent_值;SendCrashReport:發送crash報告,參數url為指定的服務器地址,parameters為被格式化並將發送的參數信息,dump_file_name為dump文件路徑名,report_code為報告請求響應碼;
GetCurrentDate:獲取當前日期,內部通過GetSystemTime獲取年月日;
ReportSent:更新檢查點文件內容,主要重寫回檢點點文件(kCheckpointSignature("GBP1\n")、last_sent_date_、reports_sent_);
總結crash_report_sender:其中的SendCrashReport發送crash報告內部細節為:先獲取當前系統時間與文件中讀取的比較,若設置了上傳上界且達到了上界則不再發送;發送指定的crash文件;重新更新檢查點文件內容返回執行結果ReportResult;此外檢查點文件格式形如為:
GBP1\n
20151208\n
120\n
Except_handler:異常捕獲器對象,主要用來捕獲異常,分為進程外和進程內,進程內可不再需要服務器,進程外需要服務器和客戶端並開啟服務,此時交由服務器抓取,而客戶端則只需要捕獲異常並通知服務器來抓取不涉及異常和抓取操作;Except_handler內部針對參數不同執行不同的處理方式,此外服務器也可以捕獲自己的異常(只要有Except_handler定義),這樣進程內捕獲既可以直接使用Except_handler也可以使用服務器但pipe參數設置為NULL即可;
AppMemory:進程內存區域信息,包含基地址、長度,主要用在保存minidump時用到;
FilterCallback:任何執行捕獲異常前調用的回調函數,可用來過濾、預處理異常信息,該函數返回true則繼續進程並導致產生dump文件,否則會忽略該異常,此時若用戶需要可以自己再去處理該異常;參數context為回調上下文該參數為Except_handler的構造函數傳入的對應參數,exinfo為異常信息,內含異常記錄,assertion為斷言信息,一般在純虛函數異常、無效參數異常中用到;
MinidumpCallback:捕獲處理dump后的回調函數,返回值含義同上,參數dump_path為生成dump文件的路徑,minidump_id為生成dump文件id,succeeded為是否創建成功;其他參數含義同上;
HandlerType:捕獲類型,包含:
HANDLER_NONE:捕獲任意的異常;
HANDLER_EXCEPTION:設置SetUnhandledExceptionFilter的異常捕獲;
HANDLER_INVALID_PARAMETER:設置_set_invalid_parameter_handler的異常捕獲;
HANDLER_PURECALL:設置_set_purecall_handler的異常捕獲;
HANDLER_ALL:HANDLER_EXCEPTION、HANDLER_INVALID_PARAMETER、HANDLER_PURECALL;
ExceptionHandler構造函數:多個重載版本提供外部簡單調用的接口,這樣可以方便的實現進程內捕獲還是進程外捕獲便於區分參數傳遞避免混亂,事實上內部統一調用函數Initialize完成初始化,未提供的參數則使用默認參數;
Initialize:參數dump_path為生成dump文件路徑,filter抓取前濾波器回調函數,callback抓取后回調函數,callback_context傳入filter的回調參數,handler_types為將安裝的捕獲異常類型,dump_type為產生minidump類型;pipe_name為命名管道名稱,pipe_handle為命名管道句柄,crash_generation_client為crash生成客戶端對象,custom_info為客戶端自定義的信息,主要包含客戶端信息、軟件版本等在進程外捕獲模式下會發送給服務器注冊時的信息;
dump_path:獲取dump文件路徑;
set_dump_path:設置dump文件路徑,內部調用UpdateNextID更新下一個保存dump文件的路徑名稱和文件名稱,文件名稱使用_time64獲取並轉化為格式為"%Y-%m-%d_%H-%M-%S";
RequestUpload:請求上傳某個早期指定的ID的dump文件,用在客戶端請求服務器上傳某個早期的dump文件;
WriteMinidump:立即寫入minidump文件,一般用在用戶獨自處理異常時調用的接口,但是該接口適合用在當前線程,此外重置版本提供了一個遍歷的接口供外部使用,此時可不需要ExceptionHandler實例即可方便處理異常,參數含義同構造函數;
WriteMinidumpForException:立即寫入minidump文件,用在用戶保存自定義的異常信息,也是用在用戶獨自處理異常時調用;
WriteMinidumpForChild:立即寫入”子”進程的某線程的dump文件,child為某個進程的句柄,child_blamed_thread為改子進程的線程ID,其他參數同構造函數;
get_requesting_thread_id:獲取請求線程ID,該值為WriteMinidump或WriteMinidumpForException的請求線程ID;
set_handle_debug_exceptions:設置控制EXCEPTION_BREAKPOINT與
EXCEPTION_SINGLE_STEP異常類型,直接寫入dump文件,用在用戶自己控制異常的情況;
IsOutOfProcess:是否為進程外捕獲,內部通過獲取crash_generation_client_是否為NULL判斷是否為進程外捕獲;
RegisterAppMemory:注冊保存至dump文件的進程開始基地址和長度;
UnregisterAppMemory:移除對應的保存至dump文件的進程開始基地址;
其他私有成員:如設置異常捕獲、無效參數、純虛函數異常、寫dump回調、以及其他狀態控制變量,不再一一列舉;
總結:ExceptionHandler作為捕獲異常類,提供了多次捕獲方式,包括進程內捕獲、進程外捕獲、用戶自定義控制捕獲、捕獲任何異常、捕獲無效參數異常、捕獲純虛函數調用異常、用戶手動保存dump、用戶保存自定義設置的異常並保存dump等;
重點說明的函數:
Initialize:初始化操作內部通過參數crash_generation_client、pipe_name、pipe_handle決定是進程內還是進程外捕獲,對於進程內捕獲,內部通過創建一個專門用於捕獲異常、寫異常的線程(通過創建兩個信號量來實現線程間同步);而對於進程外捕獲,將創建一個CrashGenerationClient客戶端對象,並將服務器端的必要的參數通過命名管道傳回給該客戶端,當ExceptionHandler中的HandleException、HandleInvalidParameter、HandlePureVirtualCall捕獲到異常時通過該客戶端對象的RequestDump方法將異常信號保存到客戶端對象,最后通過SignalCrashEventAndWait方法重置服務器端的生成crash事件和crash事件(事實上每個客戶端都有一個自己的這兩個事件),服務器收到crash事件后保存該客戶端保存的異常信號,並設置生成crash事件,完成整個異常捕獲、告知、保存、交互的過程;基本上ExceptionHandler就差不多只處理這些工作,其他的操作移交給客戶端和服務器端處理,再次說明:捕獲進程內異常只需要ExceptionHandler就可以,不需要服務器和客戶端;進程外捕獲需要客戶端和服務器端,另外客戶端在其中扮演的角色也比較簡單,只是提供捕獲通告事件、通告捕獲內容、請求服務器上傳捕獲ID對應dump文件等;
CrashGenerationClient:客戶端,連接服務器、提供異常告知事件、異常捕獲內容;
在ExceptionHandler中創建客戶端時,內部執行Register實現注冊該客戶端到服務器,並構造ProtocolMessage協議消息將客戶端必要信息通過命名管道發送給服務器並獲取服務器的響應信息(響應信息保護crash事件句柄、crash生成事件句柄、服務器是否存活句柄、服務器進程ID);ExceptionHandler捕獲到異常將通過該客戶端的接口RequestDump請求服務器抓取,此時沒有采用命名管道而是使用事件信號通告;
CrashGenerationServer:服務器,連接客戶端、處理客戶端請求、抓取客戶端crash並生成dump文件,必要時上傳dump文件;構造函數中創建了一個MinidumpGenerator的對象dump_generator_用於保存dump文件,此外服務器封裝比較簡單,對外只提供了Start接口和pre_fetch_custom_info,Start:開啟服務器,監聽客戶端連接,創建服務器存活句柄、與客戶端連接、管道讀寫用到的事件信號、注冊回調事件函數(線程池中),創建命名管道用於與客戶端通信,pre_fetch_custom_info:是否預抓取客戶進程中自定義內存信息;此外注冊的回調事件函數OnPipeConnected內部實現當客戶連接請求時,獲取到客戶信息,並注冊客戶請求dump回調事件和客戶端退出回調事件,保存維護客戶列表;
總結:CrashGenerationServer通過注冊OnPipeConnected事件回調實現不同客戶端連接時管道通信(包含必要信息相互通告),並內部處理注冊客戶請求dump回調事件和客戶端退出回調事件,此外還有客戶請求上傳dump(上傳請求將調用OnClientUploadRequestCallback回調,服務器需要自己實現上傳操作),其他的均采用事件通知的方式而不再是命名管道,減少協議、通信的麻煩;保存dump使用其成員dump_generator_的 WriteMinidump接口,其內部使用ReadProcessMemory讀取客戶進程下對應的異常信息並調用dbghelp.dll 庫中的接口MiniDumpWriteDump保存,具體保存內容、格式,MinidumpGenerator類實現不再詳細分析;
項目總結:
基本上ExceptionHandler在進程內捕獲時采用開啟另外一個線程實現捕獲抓取,並不再生成客戶端和服務器端;進程外捕獲時將額外開啟服務器進程,此外捕獲進程還需要生成客戶端;客戶端輔助通告服務器連接、請求捕獲、請求上傳dump;服務器端輔助維護客戶端連接、客戶端捕獲請求、上傳請求、dump保存操作;通信基於命名管道、命名管道傳遞多個事件句柄后期請求捕獲通告、客戶退出通告將通告事件告知而不再是命名管道,請求上傳仍采用管道通知;
還有其他的工具如ms_symbol_server_converter、dump_syms、symupload暫不分析;其他項目中需要用到的時候僅根據需要結合示例工程Crash_generation_app即可方便使用異常捕獲機制,此外可以根據需要設置ExceptionHandler實現自定義捕獲的需要。