一、前情回顧
最近把公司的一個視頻處理程序更新了一個版本,准備提交測試的發現了崩潰的情況。這個程序采用Qt和ffmpeg技術棧開發,主要用於對視頻進行渲染拼接處理,在Windows和mac兩個平台同時進行發布。在windows上測試完一切正常,然而就在我以為一切大功告成的時候,測試的同事直接給我來了個當頭棒喝,程序崩潰了!沒有道理啊,同一套代碼在Windows上安然無恙,在Mac上為何直接崩潰?好消息是程序在崩潰的時候保存了dump文件。
這得感謝前段時間集成的Google Breakpad了。Google Breakpad是Google開發的一個跨平台異常捕獲和dump文件(准確的說是mini dump)生成的開發庫。利用這個庫可以在Windows, Mac, Linux, iOS, Android平台上對程序異常崩潰進行捕獲,並生成dump文件供后期調試。據說Google Chrome, Chromium, Firefox都使用了這套機制,因此其可用性是經得起考驗的,並且這個庫現在依然更新的很頻繁。
如此強大的東西,怎么使用呢?好在網上關於breakpad的資料是還是挺多的,只不過都不是很完整很簡潔。要么就只介紹了實現原理、或者只介紹了怎么編譯、或者就只介紹了怎么集成,對於新手使用非常不友善。這里就根據我在Windows和Mac兩個平台的使用經驗來總結下吧。
二、breakpad的使用
breakpad以源代碼的形式發布,所以首先要從倉庫中把代碼下下來:
git clone https://chromium.googlesource.com/breakpad/breakpad
這個是Google的代碼倉庫,基於國內的環境需要把VPN打開。下載下來的代碼包含了windows, mac, linux三個平台所有的文件了,也包含了各個平台的工具源碼。沒錯,breakpad的工具需要自己編譯。
假設源代碼下載到了E:/breakpad,那么進入到這個目錄運行make命令:
./configure make
在Windows上需要用gyp工具來編譯,所以還得下載gyp非常麻煩。在mac上就非常簡單了,直接運行上述命令即可生成靜態庫文件。但是工具的話需要進入到tools目錄,里面有個已經配置好的xcode工程,直接打開即可編譯。
不過要注意的是,最新的breakpad源碼在編譯工具的時候會報錯:
Undefined symbols for architecture x86_64: "google_breakpad::BaseName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", referenced from: google_breakpad::DumpSymbols::CreateEmptyModule(google_breakpad::scoped_ptr<google_breakpad::Module>&) in dump_syms.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
解決辦法是:
在這一步中我們編譯源碼主要是為了得到兩個工具:minidump_stackwalk和dump_syms。這兩個分別有什么用呢?dump_syms用於從可執行程序中抽取出調試符號保存到syms符號文件中,而minidump_stackwalk則根據syms文件來分析mini dump文件,得到一個可讀性強的崩潰調用堆棧。由於我的工程是基於Qt的,所以我直接利用了Github上面的一個開源項目進行編譯。這個項目針對Qt剔除了一些無用的頭文件,並對源代碼做了稍微的調整。
基於QMake的工程,可以直接用Qt Creator打開編譯。在Windows上和Mac上無縫支持。編譯即可得到我們需要的lib文件了。這個在我們后面集成工程中鏈接需要用到。當然也可以直接將源代碼集成到工程去。
接下來就講講如何集成吧。集成步驟其實非常簡單,直接上代碼:
#ifdef _WINDOWS #include <client/windows/handler/exception_handler.h> #else #include <client/mac/handler/exception_handler.h> #endif #ifdef _WINDOWS bool minidumpCB(const wchar_t *dump_path, const wchar_t *id, void *context, EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *assertion, bool succeeded) { #else bool minidumpCB(const char* dump_path, const char* id, void* context, bool succeeded) { #endif if (succeeded) { std::wcout << "Mini Dump file: " << id << ".dump Path: " << dump_path << std::endl; } return succeeded; } int main() {
#ifdef NDEBUG // 只在Release模式下啟用Breakpad #ifdef _WINDOWS google_breakpad::ExceptionHandler eh(dumpLocation.toStdWString(), NULL, minidumpCB, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL); #else google_breakpad::ExceptionHandler eh(dumpLocation.toStdString(), NULL, minidumpCB, NULL, true, NULL); #endif
#endif }
接口非常簡單,只要定義一個回調函數minidumpCB()。當程序崩潰被捕捉到的時候就會調用這個函數,這里只是輸出了mini dump文件保存的位置。如果第一張截圖中的紅框所示。
三、dump文件如何利用
生成的dump文件如何利用?如何轉換成我們能看得懂的調用堆棧信息?其實有上面編譯出來的兩個工具,接下來的工作分三個步驟:
- 使用dump_syms生成符號表:
./dump_syms ~/Test/Caputre > Capture.syms
- 創建有層次的調試符號文件夾:
head -n1 Capture.syms // 查看文件層次 mkdir -p ./symbols/PanoramaCapture/3EXXXXXX/ //這一步根據上面的輸出來 mv Capture.syms ./symbols/PanoramaCapture/3EXXXXX/ // 將符號文件移動進去
- 利用minidump_stackwalk分析dump文件:
./minidump_stackwalk minidump.dmp ./symbols
最后一步將輸出詳細的堆棧信息:
相信有了這些信息,找出代碼中潛伏的bug不是什么難事了。而我也正是根據這些信息,成功解決了這次的崩潰問題。再提一句,不管在Windows上還是Mac上,編譯Release的時候最好把調試符號文件保存好。這樣利用breakpad來分析的時候才能事半功倍,breakpad方才能展現其強大的一面。
四、參考鏈接
1. https://www.jianshu.com/p/295ebf42b05b
2. https://github.com/google/breakpad
3. https://groups.google.com/forum/#!topic/google-breakpad-discuss/fierVnIAv1M