作者:lds(lds2012@gmail.com)
日期:2017-03-24
一. BreakPad簡介
Google breakpad是一個跨平臺的崩潰轉(zhuǎn)儲和分析框架和工具集合。
Breakpad由三個主要組件:
- client,以library的形式內(nèi)置在你的應(yīng)用中,當崩潰發(fā)生時寫 minidump文件
- symbol dumper, 讀取由編譯器生成的調(diào)試信息(debugging information),并生成 symbol file
- processor, 讀取 minidump文件 和 symbol file ,生成可讀的c/c++ Stack trace.
簡單來說就是一個生成 minidump,一個生成symbol file,然后將其合并處理成可讀的Stack trace。
二. MiniDump文件格式
minidump文件格式是由微軟開發(fā)的用于崩潰上傳,它包括:
- 當dump生成時進程中一系列executable和shared libraries, 包括這些文件的文件名和版本號。
- 進程中的線程列表,對于每個線程,minidump包含它在寄存器中的狀態(tài),線程的stack memory內(nèi)容。這些數(shù)據(jù)都是未解析的字節(jié)流,Breakpad client通常沒有調(diào)試信息(debugging information)能生成函數(shù)名,行號,甚至無法確定stack frame的邊界。
- 其他收集關(guān)于系統(tǒng)的信息,如:處理器,操作系統(tǒng)高版本,dump的原因等等。
breakpad在所有平臺上(windows/linux等)都統(tǒng)一使用minidump文件格式,而不使用core files,原因是因為:
- core files可能很大,而minidump比較小。
- core files文檔不全
- 很難說服windows機器去生成core files,但可以說服其他機器來生成minidump文件。
- breakpad只支持一種統(tǒng)一的格式會比較簡單,而不是同時支持多種格式。
什么是core files?core files是unit系統(tǒng)上程序崩潰時生成的文件。
三. Symbols文件格式
symbols文件是基于純文本的,每一行一條記錄,每條記錄中的字段以一個空格作為分隔符,每條記錄的第一個字段表示這一行是什么類型的記錄。
記錄類型:
- 模塊記錄:
MODULE
operatingsystem architecture id name - 文件記錄:
FILE
number name - 函數(shù)記錄:
FUNC
address size parameter_size name - 行號記錄:address size line filenum
- PUBLIC記錄:
PUBLIC
address parameter_size name STACK WIN
STACK CFI
參見:https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
四. 不同平臺的實現(xiàn)原理
默認情況下,當崩潰時breakpad會生成一個minidump文件,在不同平臺上的實現(xiàn)機制不一樣:
- 在windows平臺上,使用微軟提供的
SetUnhandledExceptionFilter()
方法來實現(xiàn)。 - 在OS X平臺上,通過創(chuàng)建一個線程來監(jiān)聽
Mach Exception port
來實現(xiàn)。 - 在Linux平臺上,通過設(shè)置一個信號處理器來監(jiān)聽
SIGILL
SIGSEGV
等異常信號。
當minidump被生成后,在不同平臺上也使用不同的機制來上傳crash dump文件。
參見:Windows SetUnhandledExceptionFilter
參見:Mac OS X Exception handling
參見:Catching Exceptions and Printing Stack Traces for C on Windows, Linux, & Mac
五. 異常處理機制
提供兩種不同的異常處理機制:
- 同進程(in-process)
- 跨進程(out-precess)
因為在崩潰的進程寫minidump文件是不安全的,所以三個平臺(windows、linux、mac os)都提供跨進程的異常處理機制。
六. 在Linux平臺使用breakpad
這里因為要研究Android上使用breakpad,所有主要研究linux平臺,windows和mac平臺的使用方法請自行參考文檔。
6.1 構(gòu)建Breakpad庫
breakpad提供自動構(gòu)建工具來構(gòu)建linux client庫和processor庫。
在breakpad源碼目錄下通過運行命令:
./configure
make
將自動生成 src/client/linux/libbreakpad_client.a
文件, 其包含了在你的應(yīng)用中生成minidumps文件的必要代碼。
6.2 在你的應(yīng)用中使用Breakpad
首先配置build precess來link剛生成的 libbreakpad_client.a
文件。
然后設(shè)置 include paths 來 包含 google-breakpad 目錄下的 src 目錄。
接下來include頭文件:
#include "client/linux/handler/exception_handler.h"
現(xiàn)在你就可以初始化 ExceptionHandler
對象,初始化的時候需要提供一個用于寫minidump文件的目錄,以及一個回調(diào)函數(shù)用于在minidump文件寫完以后調(diào)用。
int main(int argc, char* argv[]) {
// 初始化ExceptionHandler
google_breakpad::MinidumpDescriptor descriptor("/tmp"); // minidump文件寫入到的目錄
google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
crash();
return 0;
}
// 寫完minidump后的回調(diào)函數(shù)
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
printf("Dump path: %s\n", descriptor.path());
return succeeded;
}
// 觸發(fā)crash來測試
void crash() {
volatile int* a = (int*)(NULL);
*a = 1;
}
編譯并運行這個實例應(yīng)該會在 /tmp 目錄下生成 minidump 文件,并且終端下會輸入minidump文件的路徑。
關(guān)于初始化ExceptionHandler可以接受的參數(shù)需要參見源碼
ExceptionHandler(const MinidumpDescriptor& descriptor,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
bool install_handler,
const int server_fd);
具體參數(shù):
- descripter,minidump文件寫入的目錄
- filter,可選,在寫minidump文件之前,會先調(diào)用filter回調(diào)。根據(jù)它返回true/false來決定是否需要寫minidump文件。
- callback, 可選,在寫minidump文件之后調(diào)用的回調(diào)函數(shù)
- callback_context,
- install_handler, 如果為ture,不管怎樣當未捕捉異常被拋出時都會寫入minidump文件,如果為false則必須明確調(diào)用了
WriteMinidump
才會寫入minidump 文件 - server_fd, 如果為-1,則使用同線程模式(in-precess),如果有一個有效的值,則使用跨線程模式(out-of-process)
需要注意的是,你必須在callback回調(diào)函數(shù)中做盡量少的工作,因為你的程序處于一個不安全的狀態(tài),它需要無法安全的去分配內(nèi)存,或調(diào)用其他共享庫中的函數(shù)。安全的方式是 fork
和 exec
一個新進程去做想要做的事情。如果你需要在回調(diào)中做一些工作,breakpad源碼提供一些簡單的重新實現(xiàn)的libc庫里的方法,來避免直接調(diào)用libc, 并提供一個a header file for making linux system calls,來避免直接調(diào)用其他共享庫的方法。
6.3 發(fā)送minidump文件
在真實環(huán)境中,你通常需要以某種方式來處理minidump文件,例如把它發(fā)送給服務(wù)器來進行分析,Breakpad源碼提供了一些HTTP上傳的代碼,并提供一個minidump上傳工具( 詳見minidump_upload.cc)。
6.4 生成symbols文件
為了生成可讀的stack trace, breakpad需要你將binaries里的調(diào)試符號(debugging symbols)轉(zhuǎn)換成基于文本格式的symbol files。
首先確保你在編譯代碼的時候加上 -g
參數(shù)來生成帶調(diào)試符號的。
然后使用 configure && make
breakpad源碼來生成 dump_syms
工具。
接著運行 dump_syms
命令來生成 symbol files,如下:
$ google-breakpad/src/tools/linux/dump_syms/dump_syms ./test > test.sym
為了可以使用 ``minidump_stackwalk` 工具來生成stack trace,你需要將文件放置在一定的目錄結(jié)構(gòu),symbol file的第一行說明了需要放置的目錄結(jié)構(gòu),可以使用命令,或使用 Mozilla 提供的 symbolstore.py 工具來新建這樣的目錄結(jié)構(gòu)。
$ head -n1 test.sym
// MODULE Linux x86_64 6EDC6ACDB282125843FD59DA9C81BD830 test
$ mkdir -p ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830
$ mv test.sym ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830
6.5 生成Stack Trace
breakpad包含一個叫做 minidump_stackwalk
的工具來將 minidump 文件,外加symbol files來生成一個人可讀的stack trace。在編譯breakpad后,這個工具一般在 google-breakpad/src/processor
目錄下, 通過將 minidump 和 symbol files 傳入給它即可:
$ google-breakpad/src/processor/minidump_stackwalk minidump.dmp ./symbols
它會生成verbose output到stderr, stacktrace到stdout。
七. 在Android平臺使用Breakpad
Breakpad支持 ARM x86 和 MIPS 架構(gòu)的Android系統(tǒng)。需要Android NDK r11c及以上版本。
7.1 構(gòu)建client庫
Android版本的Client庫設(shè)計為一個靜態(tài)庫(static library)來使得你可以鏈接到你的Android native代碼里。
一共有兩種方法來build。
7.1.1 方法1:使用ndk-build構(gòu)建(推薦)
使用ndk-build來構(gòu)建,需要如下幾個步驟:
- 在你的項目中的 Android.mk 中 include google-breakpad的 Android.mk
include $(LOCAL_PATH)/breakpad/android/google_breakpad/Android.mk
- 鏈接break-client未你的靜態(tài)庫模塊
LOCAL_STATIC_LIBRARIES += breakpad_client
- 因為需要c++ STL,所以還需要在
Application.mk
文件中加入 STL 支持:
APP_STL := stlport_static
7.1.2 方法2:使用Android toolchain構(gòu)建
$GOOGLE_BREAKPAD_PATH/configure --host=arm-linux-androideabi \
--disable-processor \
--disable-tools
make -j4
將會build到 src/client/linux/libbreakpad_client.a 文件。
可使用 make check
來在Android設(shè)備上運行測試。
7.2 在Android上使用Breakpad
在Android平臺上使用breakpad,基本和在linux平臺上是基本類似的。
需要如下幾個步驟:
- 在cpp文件中include exception_handler頭文件
#include "client/linux/handler/exception_handler.h"
- 如果使用ndk-build,需要配置include path(在Android.mk文件中)
LOCAL_C_INCLUDES := $(GOOGLE_BREAKPAD_PATH)/src/common/android/include \
$(GOOGLE_BREAKPAD_PATH)/src
并且加上 -llog flag
LOCAL_LDLIBS := -llog
- 時刻記住在Android系統(tǒng)上沒有
/tmp
目錄,需要指定一個其他目錄,可以是 application 專有目錄(/data/data/packageName/files),或者存儲到SDCard上面去(需要寫入權(quán)限)
7.3 生成stack trace
這個操作和其他平臺一樣。
- 從設(shè)備上獲取minidumps文件
- 使用dump_syms工具生成so的symbol files
dump_symc $PROJECT_PATH/obj/local/$ABI/libfoo.so > libfoo.so.sym
- 創(chuàng)建symbol files專有目錄結(jié)構(gòu)
// MODULE Linux x86_64 6EDC6ACDB282125843FD59DA9C81BD830 test
$PROJECT_PATH/symbols/libfoo.so/6EDC6ACDB282125843FD59DA9C81BD830/libfoo.sym
- 使用
minidump_stackwalk
來生成stack trace:
minidump_stackwalk $MINIDUMP_FILE $PROJECT_PATH/symbols