音視頻開發之旅(59)- 捕獲收集、定位分析 Native崩潰

目錄

  1. Native崩潰有哪些類型
  2. 如何捕獲收集Native崩潰
  3. 如何分析定位Native崩潰
  4. 資料
  5. 收獲

我們知道Java崩潰是在Java代碼中出現了未捕獲異常,導致程序異常退出,常見的異常有:NPE、OOM、ArrayIndexOutOfBoundsException、IllegalStateException、ConcurrentModificationException等等。
還有一類崩潰,也是我們不得不關注,那就是Native層崩潰,這類崩潰不像Java層崩潰那樣比較清晰的看出堆棧信息以及具體的崩潰。每當遇到是都要查找分析,寫這篇的目的是幫助自己做下記錄,也希望能幫到有類似困擾的你,下面我們開始一起學習實踐吧。
本文學習實踐的demo以張紹文《Android開發高手課》中的例子進行。

一、 Native崩潰有哪些類型

先來造一個Native崩潰,來看下Native的崩潰信息


圖片來自: 刀鋒鐵騎:常見Android Native崩潰及錯誤原因
我們可以看到有三個相關信息
Signal xx: 代表錯誤類型,我們可以先從錯誤類型上初步判斷是哪種類型的崩潰,常見的Native崩潰如下。其中 SIGSEGV時遇到的機率基本上最高的。

接下來是寄存器快照,這個直接看不出來問題,而fault addr是比較關鍵的一個信息,我們后續再分析定位時會用到它。

再接下來時調用堆棧,這個也非常重要,可以直接幫助我們看出Crash的堆棧信息,但是需要有符號表的so才能轉為對應的函數名和行數,否則也是比較難看懂。

二、如何捕獲收集Native崩潰

常見的Native崩潰捕獲工具:Chromium的BreakPad、騰訊的bugly
我們來通過學習實踐Breakpad來進行收集Natvie崩潰。Breakpad是一個跨平臺的開源項目,這一小節我們來學習實踐下如何編譯使用.

2.1 我們先來看下Breakpad的工作原理

圖片來自: 學會這個絕招,讓 C++ 崩潰無處可逃!

2.2 編譯安裝過程如下

  1. 下載[Breakpad]源碼(https://chromium.googlesource.com/breakpad/breakpad/+/master)
  2. 下載配置depot_tools
  3. Breakpad依賴LSS,下載它(https://github.com/adelshokhy112/linux-syscall-support)并把 LSS 中的 linux_syscall_support.h 文件放至breakpad/src/third_party/lss/ 目錄下;
  4. 編譯Breakpad ./configure && make && sudo make install

編譯安裝成功后可以看到生成的生成的/usr/local/bin/minidump_dump和/usr/local/bin/minidump_stackwalk工具,這些命令工具我們在后面定位分析時會用到

2.3 將Breakpad集成到Android項目中

將 google-breakpad 源代碼里面的src文件夾拷貝到項目的src/main/cpp目錄下;
配置cmake或者makefile,這里我們使用cmake
cmake_minimum_required(VERSION 3.4.1)

#設置breakpad根路徑
set(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})

#設置頭文件的路徑
include_directories(${BREAKPAD_ROOT}/src ${BREAKPAD_ROOT}/src/common/android/include)

#歸類要編譯的cpp代碼的文件 
file(GLOB BREAKPAD_SOURCES_COMMON
        ${BREAKPAD_ROOT}/src/client/linux/crash_generation/crash_generation_client.cc
        ${BREAKPAD_ROOT}/src/client/linux/dump_writer_common/thread_info.cc
        ${BREAKPAD_ROOT}/src/client/linux/dump_writer_common/ucontext_reader.cc
        ${BREAKPAD_ROOT}/src/client/linux/handler/exception_handler.cc
        ${BREAKPAD_ROOT}/src/client/linux/handler/minidump_descriptor.cc
        ${BREAKPAD_ROOT}/src/client/linux/log/log.cc
        ${BREAKPAD_ROOT}/src/client/linux/microdump_writer/microdump_writer.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/linux_dumper.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/minidump_writer.cc
        ${BREAKPAD_ROOT}/src/client/minidump_file_writer.cc
        ${BREAKPAD_ROOT}/src/common/convert_UTF.c
        ${BREAKPAD_ROOT}/src/common/md5.cc
        ${BREAKPAD_ROOT}/src/common/string_conversion.cc
        ${BREAKPAD_ROOT}/src/common/linux/elfutils.cc
        ${BREAKPAD_ROOT}/src/common/linux/file_id.cc
        ${BREAKPAD_ROOT}/src/common/linux/guid_creator.cc
        ${BREAKPAD_ROOT}/src/common/linux/linux_libc_support.cc
        ${BREAKPAD_ROOT}/src/common/linux/memory_mapped_file.cc
        ${BREAKPAD_ROOT}/src/common/linux/safe_readlink.cc

        )
#歸類要編譯的匯編文件
file(GLOB BREAKPAD_ASM_SOURCE ${BREAKPAD_ROOT}/src/common/android/breakpad_getcontext.S
        )

set_source_files_properties(${BREAKPAD_ASM_SOURCE} PROPERTIES LANGUAGE C)

#設置生成靜態庫所需編譯的文件
add_library(breakpad STATIC ${BREAKPAD_SOURCES_COMMON} ${BREAKPAD_ASM_SOURCE})
#鏈接
target_link_libraries(breakpad log)

2.4 添加breakpad的回調

java層的未捕獲的異??梢酝ㄟ^UncaughtExceptionHandler 處理,那么使用Breakpad如何捕獲Native層的異常吶?

 google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1);

具體如下:

#include <stdio.h>
#include <jni.h>

#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/minidump_descriptor.h"


void onNativeCrash(const char* info) {
    JNIEnv *env = 0;
    jclass crashPinClass = env->FindClass(
            "com/test/crash/TestCrash");
    if (crashPinClass == NULL){
        return;
    }
    jmethodID crashReportMethod = env->GetStaticMethodID(crashPinClass,
            "onNativeCrash", "(Ljava/lang/String;)V");
    if (crashReportMethod == NULL) {
      return;
    }
    jstring crashInfo = env->NewStringUTF(info);
    env->CallStaticVoidMethod(crashPinClass, crashReportMethod, crashInfo);
}

//崩潰回調
bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
                  void *context,
                  bool succeeded) {
    onNativeCrash("");
    return succeeded;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sample_breakpad_BreakpadInit_initBreakpadNative(JNIEnv *env, jclass type, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);

    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1);

    env->ReleaseStringUTFChars(path_, path);
}

然后加載so設置崩潰后生成的dmp文件的存儲路徑即可。
收集到了崩潰,我們該如何分析吶?下面小節我們繼續學習實踐。

三、如何分析定位Native崩潰

在講解幾種常用的分析工具之前,我們先來了解下編譯生成帶符號表的so和不帶符號表的so的區別。


我們可以通過file命令來查看他們之間的區別

file cmake/debug/obj/arm64-v8a/libcrash-lib.so
 ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=c776759b526457b5777fc8833f7fc0fcc46055cc, with debug_info, not stripped -->沒有剝去debug信息,即帶符號表
file transforms/stripDebugSymbol/debug/0/lib/arm64-v8a/libcrash-lib.so
ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=c776759b526457b5777fc8833f7fc0fcc46055cc, stripped -->剝去debug信息,即沒符號表

如果是我們自己開發編譯的so,在發布時要把帶符號表的so進行備份或者上傳,方便分析定位native崩潰。
需要特別注意的是:不同機器打出來的so的md5是不同的,所以發版后要保存下對應的帶符號表的so(obj目錄下的不同架構的的so)

下面我們來一起學習下,常用的幾種工具

3.1 minidump_stackwalk 導出崩潰堆棧信息

就是上面一小節中我們編譯產生的命令工具。用法如下:

minidump_stackwalk fd311404-a968-4ce0-17d5fa8a-61a8fdf1.dmp libcrash-lib.so >crash.log

生成的crash.log如下

CPU: arm64 8 CPUs

GPU: UNKNOWN

Crash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available

Thread 0 (crashed)
 0  libcrash-lib.so + 0x5e0 -->出錯的地址
     x0 = 0x0000007b14ac4e00    x1 = 0x0000007fedde9894
     x2 = 0x0000000000000000    x3 = 0x0000007b14a56c00
     x4 = 0x0000007feddeaa00    x5 = 0x0000007a7e1a7965
@
"crash.log" 2226L, 114492B

我這我們需要下個一工具繼續分析,addr2line可以把地址轉為對應的函數名和行數。

3.2 addr2line

基本用法如下

Usage: aarch64-linux-android-addr2line [option(s)] [addr(s)]
 Convert addresses into line number/file name pairs.
 If no addresses are specified on the command line, they will be read from stdin
 The options are:

  -e --exe=<executable>  Set the input file name (default is a.out) 指定輸入文件
  -f --functions         Show function names    顯示函數名稱

而addr2line命令所在的路徑如下,可以根據崩潰信息中的設備的cpu架構來選擇對應的addr2line。

arm: $NDK_PATH/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin
arm64: $NDK_PATH/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin

示例
我們看到3.1節中我們拿到的dump中的崩潰信息是 arm64 ,崩潰地址是0x5e0,下嗎我們使用add2line來進行分析下

/Users/yangbin/Library/Android/android-ndk-r16b/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line -f -e /Users/yangbin/work/avwork/thirdparty/nativecrash/Chapter01/sample/build/intermediates/cmake/debug/obj/arm64-v8a/libcrash-lib.so 0x5e0
_Z5Crashv
/Users/yangbin/work/avwork/thirdparty/nativecrash/Chapter01/sample/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/crash.cpp:10

可以看到輸出了對應的錯誤類和行數,再結合錯誤原因SIGSEGV即可以快速的分析出具體的原因。

3.3 將上述過程腳本化

新建一個腳本 dumptool.sh,內容如下:
用法如下:dumptool.sh ./test /tmp/fd311404-a968-4ce0-17d5fa8a-61a8fdf1.dmp crash.log
腳本來自:學會這個絕招,讓 C++ 崩潰無處可逃!

#!/bin/bash

if [ $# != 3 ] ; then
echo "USAGE: $0 TARGET_NAME DMP_NAME OUTPUT_NAME"
echo " e.g.: $0 test fd311404-a968-4ce0-17d5fa8a-61a8fdf1.dmp crash.log"
exit 1;
fi

#獲取輸入參數
target_file_name=$1
dmp_file_name=$2
output_file_name=$3


getStackTrace() {
echo "@getStackTrace: start get StackTrace"
sym_file_name=$target_file_name'.sym'

#獲取符號文件中的第一行
line1=`head -n1 $sym_file_name`

#從第一行字符串中獲取版本號
OIFS=$IFS; IFS=" "; set -- $line1; aa=$1;bb=$2;cc=$3;dd=$4; IFS=$OIFS

version_number=$dd

#創建特定的目錄結構,并將符號文件移進去
mkdir -p ./symbols/$target_file_name/$version_number
mv $sym_file_name ./symbols/$target_file_name/$version_number

#將堆棧跟蹤信息重定向到文件中
minidump_stackwalk $dmp_file_name ./symbols > $output_file_name
}

main() {
getSymbol
if [ $? == 0 ]
then
getStackTrace
fi
}

3.4 ndk-stack

ndk-stack也是非常有用的工具,它需要結合崩潰時的Tombstone(墓碑文件)進行分析。

ndk-stack用法如下

usage: ndk-stack.py [-h] -sym SYMBOL_DIR [-i INPUT]

Symbolizes Android crashes.

optional arguments:
  -h, --help            show this help message and exit
  -sym SYMBOL_DIR, --sym SYMBOL_DIR
                        directory containing unstripped .so files
  -i INPUT, -dump INPUT, --dump INPUT
                        input filename

ndk-stack -sym $PROJECT_PATH/obj/local/armeabi-v7a -dump tombstone.txt

墓碑文件的獲取可以通過 adb bugreport來進行獲取。
下面我們看下通過命令adb bugreport來拿下墓碑文件,然后結合ndk-stack分析的過程

adb bugreport .
unzip bugreport-OnePlus5T-QKQ1.191014.012-2021-11-28-14-49-22.zip
cd FS/data/tombstones
可以看到多個墓碑文件,我們拿最近的一個進行分析

ndk-stack -sym /Users/yangbin/work/avwork/thirdparty/nativecrash/Chapter01/sample/build/intermediates/cmake/debug/obj/arm64-v8a -dump tombstone_09

3.5 IDA Pro

如果沒有符號表的so怎么辦,可以嘗試使用ida這個so逆向分析工具分析定位分析,比如我們用ida打開不帶符號表的libcrash-lib.so然后通過錯誤地址來查詢問題

具體駛入如下,我們先用ida打開帶符號表的libcrash-lib.so,然后跳轉對地址為0x5e0處


我們再用不帶符號表的libcrash-lib.so,查看下


可以看到同樣也可以定位到對應的類。不過都是一些匯編語言,需要了解下。同樣通過另外一個工具objdump也可以同樣的找對應的匯編信息,進而繼續分析。

這篇基本上就到這里了,文章斷更了兩個月,這兩個月面臨崗位變更熟悉,更重要的原因是目標實現了突然放松了,其實這才是起點,通過這兩個月工作了解熟悉,音視頻涉及的知識和應用真的非常廣泛,編解碼、渲染、傳輸、協議、播放器、圖形學、AI等等。加油吧少年,下一篇開始我們進入ffmpeg源碼解析的系列。盡量做到每周至少一篇,一起學習吧

四、資料

  1. 崩潰優化(上):關于“崩潰”那些事兒
  2. Android 平臺 Native 代碼的崩潰捕獲機制及實現
  3. 學會這個絕招,讓 C++ 崩潰無處可逃!
  4. Android使用Google Breakpad進行崩潰日志管理
  5. Android NDK&JNI開發之Native崩潰日志分析方法
  6. 異常處理 - Native 層的崩潰捕獲機制及實現
  7. Android NDK Tombstone/Crash 分析
  8. 安卓Native崩潰定位
  9. Android NDK墓碑/崩潰分析
  10. 如何分析、定位Android Native Crash
  11. 干貨|安卓APP崩潰捕獲方案——xCrash
    對應的開源項目—》[https://github.com/iqiyi/xCrash]
  12. Bugly-Android 平臺 Native 代碼的崩潰捕獲機制及實現
  13. 刀鋒鐵騎:常見Android Native崩潰及錯誤原因

五、收獲

通過本篇的學習,了解熟悉了如何進行native崩潰的捕獲和分析??偨Y如下:

  1. 學習實踐了通過breakpad進行native崩潰的捕獲收集
  2. 實踐了minidump_stackwalk 把breakpad生成的dump文件轉為native崩潰信息文件,然后結合使用add2line和帶符號表的對應的so,解析出崩潰的類以及對應的行數
  3. 實踐了墓碑文件的獲取以及結合ndk_stack進行natvie崩潰堆棧解析
  4. 實踐了通過IDA pro分析無符號表的so

感謝你的閱讀

下一篇我們再次進入ffmpeg系列,結合源碼層面學習解析。歡迎關注公眾號“音視頻開發之旅”,一起學習成長。

歡迎交流

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容