breakpad是Google開源的一套跨平台工具,用於dump的處理。很全的一套東西,我這里只簡單涉及breakpad客戶端,不涉及純文本符號生成,不涉及dump解析。
一、使用
最簡單的是使用進程內dump捕獲,使用者只需要跟ExceptionHandler打交道,在自己的程序里定義一個ExceptionHandler對象,ExceptionHandler會掛上異常處理、CRT參數錯誤處理、purecall錯誤處理,當發生crash時,breakpad會寫好dump,然后回調通知使用者。進程內dump並不推薦,但也不算太差,它在程序啟動時就開啟了一個“Handler thread”,等到有crash,觸發該線程去寫dump,寫完回調使用者,從google的久未更新的ClientDesign文檔可以猜到以前是只有進程內寫dump的,它已經符合了讓dump盡可能真實而設置下的規定。以前所在團隊在chromium上做二次開發,使用的是進程內dump,沒發現有問題。現在我安裝的chrome瀏覽器,沒發現有crash_server進程,估計要么是沒抓dump,要么是進程內dump,我看到有文章說有一個GoogleCrashHandler.exe進程,但我這里沒有發現,可能是后來修改掉了吧,之前我還一直以為是對crash_server.exe重命名了。
進程外寫dump,使用者一樣要定義一個ExceptionHandler對象,這對象有管道名稱。另外還需要寫一個server進程,server進程負責:寫dump、上傳dump,當客戶進程發生crash時,只需要通過Event置位通知服務進程。server進程只需要定義一個breakpad提供的CrashGenerationServer類對象。客戶進程和服務進程是通過管道通信的,通信可以只發生在客戶進程初始化階段,server進程要先於客戶進程啟動,否則客戶進程就會因為管道連接不上而使用進程內dump捕獲。
進程內、外dump捕獲,都是異步而阻塞的,異步具體是說,進程內dump會讓寫dump、回調通知使用者寫dump完成在另一個安全的線程中做;進程外dump會讓寫dump在另一個進程中做、回調通知寫dump完成在crash線程中做、dump上傳可以放到另一個進程中做。阻塞具體是說,雖然發生crash的線程把dump相關的工作扔給別人做了,但是它會等待別人的工作做完才繼續完下走。
二、內部實現
ExceptionHandler部分。
當使用進程內dump時,會有一個handler thread,該線程啟動之后,等待semaphore觸發寫dump行為,進程外dump則沒有該線程。另外,異常處理初始化是在ExceptionHandler對象構造中做的,如果沒有進程外dump的需求,那么只需要ExceptionHandler就可以搞定,不需要CrashGenerationClient 和 CrashGenerationServer。
生成dump的流程在ExceptionHandler里面跑的很雜,還提供了非崩潰產生dump的接口。畫個流程圖出來(流程圖只是函數的羅列,塊與塊之間可能存在包含關系):
CrashGenerationClient 和 CrashGenerationServer。
這兩個類主要用於進程間通信,包括:管道數據傳輸、Event通知。跟ExceptionHandler有關聯的就是CrashGenerationClient,這里的client可以當作管道的client來理解。ExceptionHandler已經做了異常處理初始化,而進程外dump的生成是另外的進程做的,所以CrashGenerationClient,並不涉及異常、dump生成,它只是為了進程間通信而存在。
以CrashGenerationClient為主線來介紹客戶進程服務進程間的交互。CrashGenerationClient對外提供Register、RequestDump、RequestUpload。
1、Register
1-1 客戶進程連接上服務進程:連接上管道,設置管道狀態
1-2 客戶進程向服務進程注冊:通過TransactNamedPipe,將客戶進程的信息傳遞給服務進程,也從服務進程讀取到數據。
客戶進程傳遞的數據包括:服務進程ID、dump類型、crash線程id的地址、EXCEPTION_POINTERS指針的地址、參數異常和純虛函數異常的斷言信息地址、客戶進程信息。服務進程會監控客戶進程的退出。
客戶進程接收的數據包括:客戶進程用於觸發生成dump的Event Handle;客戶進程用於監聽的dump生成完畢Event Handle;客戶進程用於監聽的服務進程活着Mutex handle;服務進程進程id(后面沒用上)。
這里Handle,都是服務進程通過DuplicateHandle API 復制到客戶進程的,為什么不使用命名Handle呢?這樣子兩個進程只需要約定名字就好,不需要DuplicateHandle,然后再通過管道傳遞。我的理解:命名Handle之所以不用,是因為服務進程要為多個客戶進程提供dump服務,如果使用命名Handle,那么命名會是一個很麻煩的事情,再則,約定名字也增加了耦合,breakpad現在的做法使得客戶和服務的耦合只有管道名、管道數據協議。
客戶進程在TransactNamePipe函數執行完畢之后,再執行了一次WriteFile操作,發送MESSAGE_TAG_REGISTRATION_ACK消息給服務進程,服務進程收到該ACK消息時,關閉跟客戶進程的連接。服務進程對管道的操作順序為:讀-->寫-->讀,第二次讀是客戶進程通知服務進程關閉管道。
2、RequestDump
當發生crash時,或者是在非crash狀態下要求生成dump,就需要調用RequestDump函數。客戶進程觸發之前從服務進程收到的崩潰Event Handle,等待dump完成Event和服務進程Event處於觸發狀態,release下最多等15秒,debug下無限等待,異步阻塞,為了不讓現場被破壞。
服務進程通過RegisterWaitForSingleObject注冊了崩潰Event Handle的回調函數。回調函數里做了這么幾件事情:(1)、通過ReadProcessMemory讀取客戶進程的信息;(2)、生成dump;(3)、觸發dump生成事件,通知客戶進程,復位觸發dump事件。
3、RequestUpload
首先客戶進程連接上服務進程,接着客戶進程寫MESSAGE_TAG_UPLOAD_REQUEST消息到管道,寫完關閉管道。
服務進程讀取數據,發現是MESSAGE_TAG_UPLOAD_REQUEST消息,則調用創建服務進程時注冊的上傳回調函數。注意到
chromium並沒有使用這個通道,而是在服務進程生成dump的回調里做了dump上傳操作,這樣子似乎更好,減少了crash時兩進程的溝通。客戶進程、服務進程、breakpad客戶端之間的聯系圖:
可以看到breakpad客戶端主要包含了CrashGenerationServer\ExceptionHandler\CrashGenerationClient三部分,另外有dump上傳未畫出。
三、從代碼中學到的
學習breakpad_client的代碼,不是為了在工作上使用,以前的、現在的團隊都已經有成熟的dump捕獲、dump分析工具。學習它,是為了體會它的優點和缺點。
breakpad_client的層次划分很好,使用者不需要知道進程間通信的存在,通過回調實現層次間的通知。(這種比較簡單,一般人都可以做到。)
crash之后崩潰線程盡可能少的操作,在客戶進程初始化時就把崩潰時服務進程需要用的全局數據的地址通知服務進程,崩潰時,只需要觸發Event。(我之前的做法是在crash的時候再把崩潰信息通知服務進程,現在看來是不合理的。)
API的使用。RegisterWaitForSingleObject的使用,這個API是我之前沒用過的,非常方便,直到前陣子才通過QueueUserWorkItem API(chromium通過它異步上傳dump)知道windows有自帶線程池的存在;進程間通信對管道+Event的善用(管道的異步操作 + RegisterWaitForSingleObject 非常漂亮);dump生成的各種處理,不僅僅是MiniDumpWriteDump。(這可以說是我知識面不廣帶來的驚喜。)
閱讀ClientDesign文檔,雖然文檔可能老了,但引導我明白了為什么進程內dump會導致現場破壞,最直接的理解是:因為堆壞了導致的崩潰,這時候異常處理函數里又干了堆內存分配的事情,那肯定就又繼續crash。
breakpad_client對使用者的通知是用回調函數做的,回調函數是在對象初始化時傳遞的函數指針,有一個函數有三個回調函數指針(客戶進程連接、客戶進程崩潰、客戶進程要求上傳dump), 我更喜歡用抽象類指針,這樣子只需要一個指針就夠了,參數不需要那么多,而且代碼更像C++。(這是目前唯一能想到的不喜歡。)
四、資料推薦
http://code.google.com/p/google-breakpad/wiki
五、雜
(1)、獲取breakpad_client 代碼及其demo。
breakpad代碼所在svn:http://google-breakpad.googlecode.com/svn/trunk
生成sln:D:\breakpad\src\tools\gyp>gyp.bat --no-circular-check "../../client/windows/breakpad_client.gyp"
需要先安裝python,使用2.7.4版本
python
正常生成sln文件,2.4.3、3.3.2版本均生成失敗。搜索發現,\src\client\windows\build\common.gypi文件下有 'python_ver%': '2.5',,不確定是否要依據它來確定python使用的版本。因為我是在可以編譯chromium的環境下使用breakpad_client的,所以不需要對編譯環境做處理。
(2)、chromium在breakpad_client的使用上,做了些值得看的事情。譬如:log、crash之后的處理...
(5)、breakpad官網的文檔估計比較舊了,但是思路還是正確的。符號文件部分沒有看,因為比較深,而我目前又沒有深究它的需要,看breakpad里有pdb文件生成為純文本格式后的樣子,估計是為了統一各個平台吧。(又是一個《純文本的威力》?)