Google Breakpad是什么?
一個開源的多平臺崩潰報告系統。
Google breakpad是一個非常實用的跨平臺的崩潰轉儲和分析模塊,它支持Windows,Linux和Mac和Solaris。由于他本身跨平臺,所以很大程度上減少了我們在平臺移植時的工作,畢竟崩潰轉儲,每個平臺下都不同,使用起來很難統一,而Google breakpad就幫我們做到了這一點,不管是哪個平臺下的崩潰,都能夠進行統一的分析。
現在很多工程都在使用它:最著名的幾個如Chrome,Firefox,Picasa和Google Earth。另外他的License是BSD的,也就是說,我們即便是在商業軟件中使用,也是合法的。好東西!
Google Breakpad原理(比較抽象)
?
? ? ? breakpad把應用程序分成三個部分,代碼,breakpad客戶端和調試信息。
??????1. 在build system中,通過symbol dumper用平臺相關的調試信息生成平臺無關的symbol文件。這樣做的好處很明顯,一旦平臺無關了,所有平臺的崩潰就可以做統一的分析了。
2. breakpad采取進程外轉儲和分析崩潰的方式,他使用C/S結構,客戶端用來捕獲當前進程中發生的崩潰,并通知服務端崩潰發生。服務端用來響應客戶端,抓取dump文件。這樣做的目的是為了減少崩潰進程對dump的影響。
3. Dump生成后轉發到崩潰分析器中,這個部分可以在本地也可以在服務器上,它對Dump文件進行解析,生成可讀的堆棧信息。
Google Breakpad安裝和編譯(Windows)
1、? 下載Google breakpad源代碼(從svn中簽出最新代碼)
2、? 安裝python(2.7版本可用)
3、? 生成Windows工程文件
cd "源碼目錄/src/tools/gyp"# 注意,此處不能使用全路徑,不然會出錯gyp.bat --no-circular-check "../../client/windows/breakpad_client.gyp"
4、? Build All
備注:如果無法通過svn下載源代碼,可在CSDN上利用網友分享的。
Google Breakpad的使用
?????? 在Windows下使用breakpad的方法很簡單,只需要創建一個ExceptionHandler的類即可,這個ExceptionHandler就是用戶捕獲崩潰的類。
1 handler = new ExceptionHandler(const wstring& dump_path, 2 FilterCallback filter, 3 MinidumpCallback callback, 4 void* callback_context, 5 int handler_types, 6 MINIDUMP_TYPE dump_type, 7 const wchar_t* pipe_name, 8 const CustomClientInfo* custom_info);
參數說明:
l? //dump文件路徑。
l? //crash時調用回調函數,返回ture/false來繼續/停止異常處理。
l? //minidump寫入后調用的回調函數
l? //設備上下文,回調使用的
l? //HandlerType異常類型,可在exception_handler.h查看
l? //minidump的類型,使用DbgHelp.h中MINIDUMP_TYPE類型
l? //接收crash的server端的管道名
l? //使用OOP產生minidump時,使用這個自定義客戶信息類指針來發送自定義數據
? ? ?使用breakpad的時候,有兩個地方需要注意:
1. 把breakpad的solution下的幾個工程,包含到你開發的工程中,或者直接包含它們的lib。
common:基礎功能,包含一個對GUID的封裝和http上傳的類。
exception_handler:用來捕獲崩潰的類。
crash_generation_server:breakpad的服務端,用來在產生崩潰時抓取dump。
crash_generation_client:breakpad的客戶端,用來捕獲當前進程的崩潰。
2. 在初始化breakpad之前,記得先創建好dump文件的目錄,不然breakpad服務端將不能正常的寫dump,這會導致breakpad客戶端在崩潰時無限等待服務端dump寫完的消息,最后失去響應。
進程內抓取Dump
進程內抓取Dump文件是最簡單的breakpad的用法。
1 bool InitBreakpad() 2 { 3 google_breakpad::ExceptionHandler *pCrashHandler = 4 new google_breakpad::ExceptionHandler(L"c:\dumps", 5 onExceptionFilter, 6 onMinidumpDumped, 7 NULL, 8 google_breakpad::ExceptionHandler::HANDLER_ALL, 9 MiniDumpNormal, 10 NULL, 11 NULL); 12 13 if(pCrashHandler == NULL) 14 { 15 return false; 16 } 17 18 return true; 19 }
進程外抓取Dump
使用進程外抓取Dump是比較推薦的做法。使用進程外抓取Dump時,需要指定服務端和客戶端,在服務端中需要創建CrashGenerationServer的實例,而在客戶端中則只需要創建ExceptionHandler即可。此外,如果服務端自己需要抓進程內的Dump,請將pipe的參數置為NULL。
1 const wchar_t s_pPipeName[] = L"\\.\pipe\breakpad\crash_handler_server"; 2 const std::wstring s_strCrashDir = L"c:\dumps"; 3 4 bool InitBreakpad() 5 { 6 google_breakpad::CrashGenerationServer *pCrashServer = 7 new google_breakpad::CrashGenerationServer(s_pPipeName, 8 NULL, 9 onClientConnected, 10 NULL, 11 onClientDumpRequest, 12 NULL, 13 onClientExited, 14 NULL, 15 true, 16 &s_strCrashDir); 17 18 if(pCrashServer == NULL) 19 { 20 return false; 21 } 22 23 if(!pCrashServer->Start()) 24 { 25 delete pCrashServer; 26 pCrashServer = NULL; 27 } 28 29 google_breakpad::ExceptionHandler *pCrashHandler = 30 new google_breakpad::ExceptionHandler(s_strCrashDir, 31 onExceptionFilter, 32 onMinidumpDumped, 33 NULL, 34 google_breakpad::ExceptionHandler::HANDLER_ALL, 35 MiniDumpNormal, 36 (pCrashServer == NULL) ? s_pPipeName : NULL, 37 NULL); 38 39 if(pCrashHandler == NULL) 40 { 41 return false; 42 } 43 44 return true; 45}
Google Breakpad代碼分析
?????? 代碼結構
在我們來看breakpad是如何實現其強大的功能之前,我們先來看一下他的代碼結構吧。
?
? ? ? Google breakpad的源代碼都在src的目錄下,分為如下幾個文件夾:
client:這下面包含了前臺應用程序中捕捉dump的部分代碼,里面按照平臺分成各個子文件夾
common:前臺后臺都會用到的部分基礎代碼,字符串轉換,內存讀寫,md5神馬的
google_breakpad:breakpad中公共的頭文件
processor:用于在后臺處理崩潰的核心代碼
testing:測試工程
third_party:第三方庫
tools:一些小工具,用于處理dump文件和符號表
Google Breakpad的崩潰捕獲機制
在Windows下捕獲崩潰,大家很容易會想到那個捕獲結構化異常的Api:SetUnhandledExceptionFilter。
breakpad中也使用了這個Api來實現的崩潰捕獲,另外,breakpad還捕獲了另外兩種C++運行庫提供的崩潰,一種是使用_set_purecall_handler捕獲純虛函數調用產生的崩潰,還有一種是使用_set_invalid_parameter_handler捕獲錯誤的參數調用產生的崩潰。
breakpad中的C/S結構
由于breakpad是在進程外抓取dump,所以breakpad需要實現一個C/S結構來處理崩潰進程抓取dump的請求。
breakpad中使用了命名管道來實現IPC。
(1)、Register
客戶進程連接上服務進程:連接上管道,設置管道狀態
客戶進程向服務進程注冊:通過NamedPipe,將客戶進程的信息傳遞給服務進程,也從服務進程讀取到數據。
客戶進程傳遞的數據包括:服務進程ID、dump類型、crash線程id的地址、EXCEPTION_POINTERS指針的地址、參數異常和純虛函數異常的斷言信息地址、客戶進程信息。服務進程會監控客戶進程的退出。
客戶進程接收的數據包括:客戶進程用于觸發生成dump的Event Handle;客戶進程用于監聽的dump生成完畢Event Handle;客戶進程用于監聽的服務進程活著Mutex handle等。
客戶進程在TransactNamePipe函數執行完畢之后,再執行了一次WriteFile操作,發送MESSAGE_TAG_REGISTRATION_ACK消息給服務進程,服務進程收到該ACK消息時,關閉跟客戶進程的連接。服務進程對管道的操作順序為:讀-->寫-->讀,第二次讀是客戶進程通知服務進程關閉管道。
在客戶端,初始化ExceptionHandler的時候,如果指定了PipeName,也就表示此時需要使用進程外的dump抓取,ExceptionHandler會建立一個 CrashGenerationClient的對象,由這個對象連接服務端,將自己注冊到服務端上去。注冊的過程會順序調用IsRegistered、ConnectToServer、ConnectToPipe、RegisterClient等函數。
大家可以參看exception_handler.cc中的ExceptionHandler::Initialize函數。
?
在服務端,初始化CrashGenerationServer的時候,就會建立一個命名管道,并等待客戶端來連接(OnPipeConnected)。一旦有客戶端連接上來(HandleReadDoneState),服務端會為每一個客戶端生成一個ClientInfo的對象,之后用這個對象來管理所有的客戶端(ClientInfo::Initialize()),并創建客戶連接句柄將連接結果信息(PrepareReply、DuplicateHandle)回送給客戶端(RespondToClient),客戶端接收到回送信息(ValidateResponse)。
?????? (2)、RequestDump
一旦有崩潰發生,客戶端就會向服務端請求Dump(RequestDump),服務端響應(OnDumpRequest)就會從這個ClientInfo對象中取出dump所需要的信息,具體地,通過RegisterWaitForSingleObject注冊了崩潰Event Handle的回調函數。回調函數里做了這么幾件事情:
1)、通過ReadProcessMemory讀取客戶進程的信息;
2)、生成dump;
3)、觸發dump生成事件,通知客戶進程,復位觸發dump事件。
大家可以參看crash_generation_server.cc中的CrashGenerationServer::HandleReadDoneState函數。
Google Breakpad存在的問題
進程外生成dump有很多好處,其中最大的好處就是不會被崩潰進程影響,這樣dump的過程就不容易出錯,但是這樣也有一定的弊端。
1. 部分崩潰無法抓取。在一些極端的崩潰,如堆棧溢出之類的崩潰,進程外抓取dump有時候會失敗。
2. 無法抓取死鎖或者其他原因導致的進程僵死。breakpad現在沒有檢測進程死鎖的代碼,也沒有在服務端控制客戶端請求dump的代碼,所以現在breakpad無法抓取死鎖等進程僵死的問題。
3. 對服務端有依賴。如果指定了在使用進程外抓取dump,breakpad對服務端就有依賴。主要體現在抓取dump時,如果服務端不存在,客戶端將無法正常抓取dump,甚至有時會出現阻塞。
?
PS:深入理解實現方式請調試Breakpad源代碼。