最近在重新學(xué)習(xí)極客時間Android開發(fā)高手課,張紹文老師推薦了Breakpad開源庫來采集native 的crash日志,參照老師的講解、Demo和同學(xué)做的封裝庫Android_Breakpad,寫了一個DemoBreakPadDemo
。該庫主要是在發(fā)生native crash的時候生成文件保存到sd卡,然后分析文件,定位問題。 在我們開發(fā)過程中Android JNI層Crash問題是個比較頭疼的問題。 相對Java層來說,由于c/c++造成的crash沒有輸出如同Java的Exception Strace,所以定位問題是個比較艱難的事情。 Google Breakpad是一套完整的工具集,從crash的捕獲到crash的dump,都提供了相對應(yīng)的工具。 Breakpad是一個庫和工具套件可以讓你發(fā)布的應(yīng)用程序(把編譯器提供的調(diào)試信息剝離掉的)給用戶,記錄了崩潰緊湊的“dump”文件,發(fā)送回您的服務(wù)器,并從這些minidump產(chǎn)生C和C++堆棧蹤跡。Breakpad可以根據(jù)請求使沒有崩潰的程序也可以寫出minidump Breakpad有三個主要組件:

- 客戶端:是一個庫,包含在您的應(yīng)用程序中。 它可以獲取當(dāng)前線程的狀態(tài)和當(dāng)前加載的可執(zhí)行文件和共享庫的ID寫轉(zhuǎn)儲文件。您可以配置客戶端發(fā)生了崩潰時寫入一個minidump時,或明確要求時。
- 符號卸載器:是一個程序,讀取由編譯器產(chǎn)生的調(diào)試信息,并生成一個使用Breakpad格式的符號文件 。
- 處理器(minidump processor):是一個程序,讀取一個minidump文件,找到相應(yīng)的版本的符號文件的(可執(zhí)行文件和共享庫的轉(zhuǎn)儲提到的),并產(chǎn)生了一個人可讀的C / C + +堆棧跟蹤。 Breakpad是一個跨平臺的開源庫,我們也可以在 Breakpad Github下載自己編譯。
編譯
接下來就是講解如何下載編譯的過程(其實基本上按照說明做就好,我是mac系統(tǒng),python、ndk之類的環(huán)境之前就已配好),
- 安裝depot_tools并配置環(huán)境變量,注意:這個步驟需要翻墻環(huán)境。這個倉庫中有一個關(guān)于android平臺如何引用的說明可以參照 Android_README 其中主要是要注意“Android.mk”文件依賴關(guān)系。我采用的是“CMakeLists.txt”和“Android.mk”大同小異,只是語法問題,具體可以參照NDK。 整個編譯過程相對來說算復(fù)雜的,因為中間涉及到很多知識點需要去掌握和查看,特別是對jni開發(fā)掌握不夠的程序員,不過也可以趁機了解學(xué)習(xí)一下jni相關(guān)知識。
- 下載breakpad
mkdir breakpad && cd breakpad
fetch breakpad
cd src
- 構(gòu)建
./configure && make
這一步就有可能出各種問題了,尤其是各種環(huán)境配置方面的
- 測試(可選)
make check
- 安裝(可選)
install the built libraries
- 注:如果要重新build,需要先執(zhí)行
make distclean指令
下面解析下封裝庫

可以看出是一個普通項目和library。我們先看libary中g(shù)radle指向的CMakeLists.txt:
CMakeLists.txt指向編譯配置文件,查看該文件
cmake_minimum_required(VERSION 3.4.1)
project(breakpad-core)
set(ENABLE_INPROCESS ON)
set(ENABLE_OUTOFPROCESS ON)
set(ENABLE_LIBCORKSCREW ON)
set(ENABLE_LIBUNWIND ON)
set(ENABLE_LIBUNWINDSTACK ON)
set(ENABLE_CXXABI ON)
set(ENABLE_STACKSCAN ON)
if (${ENABLE_INPROCESS})
add_definitions(-DENABLE_INPROCESS)
endif ()
if (${ENABLE_OUTOFPROCESS})
add_definitions(-DENABLE_OUTOFPROCESS)
endif ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ")
# breakpad
include_directories(external/libbreakpad/src external/libbreakpad/src/common/android/include)
add_subdirectory(external/libbreakpad)
list(APPEND LINK_LIBRARIES breakpad)
add_library(breakpad-core SHARED
breakpad.cpp)
target_link_libraries(breakpad-core ${LINK_LIBRARIES}
log)
關(guān)于cmake相關(guān)的語法可以網(wǎng)上查得。對于只想使用該項目的來說指導(dǎo)這里就基本上可以了解到整個項目的流程了,其他的不做深入講解。
使用在項目中應(yīng)用該libary或者引用該libary打包出來的aar即可,進(jìn)行初始化就行。如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.android.zone"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(":breakpad-build")
}
在application中初始化就行
BreakpadInit.initBreakpad(externalReportPath.getAbsolutePath());
測試的代碼具體可以查看github上的項目BreakpadDemo。
生成crash之后導(dǎo)出對應(yīng)的dump文件,采用上文編譯出的工具minidump_stackwalk來解析該文件。 也就是本文tools下的工具,不同平臺需要自行編譯。
- 分析dump文件
./breakpad/src/src/processor/minidump_stackwalk ***.dmp >***/crashLog.txt
得出日志結(jié)果過長,大體如下:
Operating system: Android
0.0.0 Linux 3.10.73-gd7193540482 #1 SMP PREEMPT Wed Dec 6 22:19:12 UTC 2017 aarch64
CPU: arm64
8 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libnative-lib.so + 0x6fe0
x0 = 0x0000007e4d8cb1c0 x1 = 0x0000007fd17ceb14
x2 = 0x0000007fd17ceba0 x3 = 0x0000007e4d43ea74
x4 = 0x0000007fd17ceda0 x5 = 0x0000007ecef7aa58
x6 = 0x0000007fd17ce990 x7 = 0x0000007e4dcbe34c
x8 = 0x0000000000000001 x9 = 0x0000000000000000
x10 = 0x0000000000430000 x11 = 0x0000007e4d7d97a8
x12 = 0x0000007ed1f3f790 x13 = 0x697871c6dea5b553
x14 = 0x0000007ed2036000
······
根據(jù)文章Android 平臺 Native 代碼的崩潰捕獲機制及實現(xiàn) 的介紹,我們可知“Crash reason: SIGSEGV /SEGV_MAPERR ”代表哪種類型的錯誤:
SIGSEGV 是當(dāng)一個進(jìn)程執(zhí)行了一個無效的內(nèi)存引用,或發(fā)生段錯誤時發(fā)送給它的信號。
Thread 0 (crashed) //crash 發(fā)生時候的線程
0 libnative-lib.so + 0x6fe0 //!!!發(fā)生 crash 的位置和寄存器信息
有了具體的寄存器信息,我們進(jìn)行符號解析: 符號解析,可以使用 ndk 中提供的addr2line來根據(jù)地址進(jìn)行一個符號反解的過程,該工具在 $NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line 輸出結(jié)果如下:
arm-linux-androideabi-addr2line -f -C -e sample/build/intermediates/transforms/mergeJniLibs/debug/0/lib/armeabi-v7a/libcrash-lib.so 0x77e
//輸出結(jié)果如下
Crash()
得出該崩潰為Crash方法所致,接下來就需要去修改該方法是后話了。