Android解讀Native崩潰棧信息的方法詳解
大部分的 Android 開發(fā)者使用的主要語言都是 Kotlin / Java,他們的崩潰棧信息非常清晰,也非常好定位到問題,如果是線上的崩潰通常還會(huì)對(duì)類名進(jìn)行混淆,還需要一個(gè)混淆文件對(duì)堆棧翻譯一下就能夠得到源碼中的類名。
但是很多人對(duì) C/C++ 的崩潰棧就無能為力了,今天這篇文章就來扒一扒 Native 的崩潰棧信息。
Native 崩潰棧信息
我們經(jīng)常能夠看到有類似下面的 Native 崩潰信息:
Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 17356 (tMediaPlayerDec), pid 15253 (ediaplayer.demo) pid: 15253, tid: 17356, name: tMediaPlayerDec >>> com.tans.tmediaplayer.demo <<< #01 pc 000000000001bd2c /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #02 pc 000000000001ba98 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #03 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #04 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #05 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #06 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #07 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so #08 pc 000000000001cf08 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (Java_com_tans_tmediaplayer_tMediaPlayer_decodeNative+52) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #11 pc 000000000000952c [anon:dalvik-classes5.dex extracted in memory from /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/base.apk!classes5.dex] (com.tans.tmediaplayer.tMediaPlayer.decodeNativeInternal$tmediaplayer_debug+0) #13 pc 00000000000057f6 [anon:dalvik-classes5.dex extracted in memory from /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/base.apk!classes5.dex] (com.tans.tmediaplayer.tMediaPlayerDecoder$decoderHandler$2$1.dispatchMessage+850)
Native 中的崩潰是通過系統(tǒng)信號(hào)來實(shí)現(xiàn)的,比如我們上面的異常信號(hào)就是 SIGABRT,Android 的進(jìn)程在啟動(dòng)時(shí)就會(huì)添加一個(gè) SignalCatcher,來捕獲信號(hào),不同的信號(hào)有不同的處理方式,SIGABRT 就是會(huì)直接退出程序,也就是我們常說的崩潰,Android 中還有一個(gè)非常重要的信號(hào)就是 SIGQUIT,在 Android 中表示發(fā)生了 ANR,默認(rèn)的處理邏輯是 dump 棧信息和內(nèi)存 GC 相關(guān)的信息到本地文件。
好了這里說得有點(diǎn)遠(yuǎn)了,回到上面的問題,我們剛開始看到上面的數(shù)據(jù)可能有點(diǎn)懵逼,pc 后面有一串 16 進(jìn)制的數(shù)字表示程序計(jì)數(shù)器的位置 (簡單理解就是執(zhí)行的機(jī)器碼對(duì)應(yīng)的位置),后面的文件表示崩潰的棧中相關(guān)的 .so 動(dòng)態(tài)鏈接庫。但是你又要說了,這一串地址誰能夠看出什么問題?????? 你先不要急,通常線上的用戶崩潰看到的棧是這樣的,因?yàn)?Android 的 Release 包默認(rèn)會(huì)抹掉一部分叫做符號(hào)表的東西,如果你看過我上面的文章你就會(huì)豁然開朗,這個(gè)符號(hào)表描述了指令地址和對(duì)應(yīng)方法或者變量的映射(方法名,全局變量名都是符號(hào)),通常我們用的別人的 .so 包也會(huì)抹掉符號(hào)表(這可能就是不想讓你看,起到了一個(gè)混淆作用),少了一個(gè)表在線上的運(yùn)行中性能會(huì)更加好(至少這部分內(nèi)存不用消耗了)。
通常我們自己打的 Debug 包就沒有抹掉符號(hào)表,如果是有符號(hào)表信息,我們看到的上面異常信息通常是下面這樣的:
Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 17356 (tMediaPlayerDec), pid 15253 (ediaplayer.demo) pid: 15253, tid: 17356, name: tMediaPlayerDec >>> com.tans.tmediaplayer.demo <<< #01 pc 000000000001bd2c /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::parseDecodeAudioFrameToBuffer(tMediaDecodeBuffer*)+464) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #02 pc 000000000001ba98 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+1076) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #03 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+532) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #04 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+532) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #05 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+532) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #06 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+532) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #07 pc 000000000001b878 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (tMediaPlayerContext::decode(tMediaDecodeBuffer*)+532) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #08 pc 000000000001cf08 /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/lib/arm64/libtmediaplayer.so (Java_com_tans_tmediaplayer_tMediaPlayer_decodeNative+52) (BuildId: 58ab2061a06db613d9c3ca66a214872ad88636f7) #11 pc 000000000000952c [anon:dalvik-classes5.dex extracted in memory from /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/base.apk!classes5.dex] (com.tans.tmediaplayer.tMediaPlayer.decodeNativeInternal$tmediaplayer_debug+0) #13 pc 00000000000057f6 [anon:dalvik-classes5.dex extracted in memory from /data/app/~~fIT1aTQ88Pxdg1-7Ax4AXQ==/com.tans.tmediaplayer.demo-8WVpgXB0od_2YBBfM4FnZQ==/base.apk!classes5.dex] (com.tans.tmediaplayer.tMediaPlayerDecoder$decoderHandler$2$1.dispatchMessage+850)
你看這里就有調(diào)用所對(duì)應(yīng)的方法了,因?yàn)槲疫@里的 decode() 方法是遞歸調(diào)用的,所以你看到上面的棧中有多個(gè),方法后面還有一個(gè) +532 表示該地址離方法開始的地址的偏移量,如果你的 .so 文件里面還有 debug 信息,這個(gè) +532 能夠定位到某一行 C \ C++ 源碼,其實(shí)就是每條指令都映射了某一行代碼。
在 Android 的打包過程中如果你希望 Release 包也不要移除符號(hào)表信息,可以通過在 build.gradle 中添加以下配置來避免符號(hào)表被移除:
// ...
packagingOptions {
doNotStrip "*/arm64-v8a/*.so"
doNotStrip "*/armeabi-v7a/*.so"
doNotStrip "*/x86/*.so"
doNotStrip "*/x86_64/*.so"
}
// ...
如果符號(hào)表被移除了那我們不是永遠(yuǎn)都不知道崩潰的方法是什么了?當(dāng)然不是,被移除的符號(hào)會(huì)被保存到另外的文件中,線上的崩潰可以通過這個(gè)文件再次翻譯成對(duì)應(yīng)的方法。以下就是符號(hào)文件對(duì)應(yīng)的路徑:

它解壓后如下:

他們也是 ELF 格式的文件,每個(gè)都對(duì)應(yīng)了一個(gè) .so 庫。
那么我們?cè)趺磁袛嘁粋€(gè) .so 文件是不是有符號(hào)表呢?可以通過 file 命令查看:
libtmediaplayer.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=58ab2061a06db613d9c3ca66a214872ad88636f7, with debug_info, not stripped
not stripped 就表示有符號(hào)表。
libtmediaplayer.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=58ab2061a06db613d9c3ca66a214872ad88636f7, stripped
stripped 就表示沒有符號(hào)表。
Tips: 如果你上架的應(yīng)用沒有這個(gè)符號(hào)表,Google Play 還會(huì)提醒你上傳,Google Play 是可以幫你捕獲 Native 崩潰的,它需要符號(hào)表解析這些地址信息。
符號(hào)表
我們了解 ELF 文件就知道,我們上面說的符號(hào)表就是本地符號(hào)表對(duì)應(yīng)的就是 .symtab Section,這個(gè)表對(duì)我們的程序運(yùn)行沒有任何的影響,我們調(diào)用本地方法都是通過地址直接跳轉(zhuǎn),而不需要本地符號(hào)表。
還有一個(gè)符號(hào)表是 .dynsym,它是動(dòng)態(tài)鏈接的符號(hào)表,這個(gè)表是供 ld.so 使用的,因?yàn)檫@里面的符號(hào)都是暴露給其他的程序調(diào)用的,ld.so 需要通過這個(gè)表去查詢暴露給其他程序的符號(hào)的地址,所以不能刪除。
我們可以通過 readelf -s [elf file] 讀取符號(hào)表:
以下是有符號(hào)表的數(shù)據(jù):
Symbol table '.dynsym' contains 447 entries: Num: Value Size Type Bind Vis Ndx Name // ... 441: 000000000001c494 40 FUNC GLOBAL DEFAULT 15 Java_com_tans_tmediaplayer_tMediaPlayer_durationNative 442: 000000000001cac4 136 FUNC GLOBAL DEFAULT 15 Java_com_tans_tmediaplayer_tMediaPlayer_getVideoFrameUBytesNative // ... Symbol table '.symtab' contains 2470 entries: Num: Value Size Type Bind Vis Ndx Name // ... 2067: 000000000001ae20 2116 FUNC GLOBAL DEFAULT 15 _ZN19tMediaPlayerContext29parseDecodeVideoFrameToBufferEP18tMediaDecodeBuffer // ... 2086: 000000000001bef4 36 FUNC WEAK DEFAULT 15 _ZN18tMediaDecodeBufferC2Ev 2087: 000000000001bf18 28 FUNC WEAK DEFAULT 15 _ZN17tMediaAudioBufferC2Ev 2088: 000000000001bf34 80 FUNC WEAK DEFAULT 15 _ZN17tMediaVideoBufferC2Ev 2089: 000000000001bf84 360 FUNC GLOBAL DEFAULT 15 _Z16freeDecodeBufferP18tMediaDecodeBuffer 2090: 000000000001c0ec 336 FUNC GLOBAL DEFAULT 15 _ZN19tMediaPlayerContext7releaseEv // ...
如果沒有本地符號(hào)表就只有以下信息(少了 .symtab):
Symbol table '.dynsym' contains 447 entries: Num: Value Size Type Bind Vis Ndx Name // ... 441: 000000000001c494 40 FUNC GLOBAL DEFAULT 15 Java_com_tans_tmediaplayer_tMediaPlayer_durationNative 442: 000000000001cac4 136 FUNC GLOBAL DEFAULT 15 Java_com_tans_tmediaplayer_tMediaPlayer_getVideoFrameUBytesNative // ...
我們?cè)賮砜纯瓷厦娴哪莻€(gè)崩潰棧地址 000000000001bd2c,我在 反編譯 .text 代碼(.text Section 就是用來存儲(chǔ)代碼的,通過 objdump --dissassemble --section=.text [elf file] 命令可以反編譯機(jī)器碼到匯編) 后找到了這個(gè)地址所在的方法:
000000000001bb5c: 1bb5c: ff 83 01 d1 sub sp, sp, #96 // ... 1bd2c: 11 7a 00 94 bl 0x3a570 <abort@plt> 1bd30: 44 79 00 94 bl 0x3a240 <__stack_chk_fail@plt>
方法的指令有點(diǎn)長,我省略了大部分,我們看到 1bd2c 處調(diào)用了 abort() 方法,這個(gè)方法就是用來發(fā)送 SIGABORT 的,這是我測(cè)試時(shí)添加的,我們?cè)賮砜纯?1bb5c 在符號(hào)表中對(duì)應(yīng)的是哪個(gè)符號(hào),正好就是 _ZN19tMediaPlayerContext29parseDecodeVideoFrameToBufferEP18tMediaDecodeBuffer 方法,哈哈。
最后
本篇文章介紹了符號(hào)表,還通過崩潰棧中的地址,在符號(hào)表中去查詢到了我們對(duì)應(yīng)的方法,希望你對(duì) Native 的崩潰信息和符號(hào)表有一個(gè)全新的認(rèn)識(shí)。
以上就是Android解讀Native崩潰棧信息的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Android解讀Native崩潰棧信息的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中CountDownTimer 實(shí)現(xiàn)倒計(jì)時(shí)功能
本篇文章主要介紹了Android中CountDownTimer 實(shí)現(xiàn)倒計(jì)時(shí)功能,CountDownTimer 是android 自帶的一個(gè)倒計(jì)時(shí)類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語言切換的方法
本篇文章主要介紹了Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語言切換的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02
android顯示TextView文字的倒影效果實(shí)現(xiàn)代碼
這篇文章主要介紹了android顯示TextView文字的倒影效果實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-02-02
Android抽屜導(dǎo)航Navigation Drawer實(shí)例解析
這篇文章主要為大家詳細(xì)介紹了Android抽屜導(dǎo)航NavigationDrawer實(shí)例,感興趣的小伙伴們可以參考一下2016-05-05
Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入
這篇文章主要為大家詳細(xì)介紹了Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Android學(xué)習(xí)項(xiàng)目之簡易版微信為例(一)
這篇文章主要以簡易版微信為例,為大家介紹了Android簡易版微信項(xiàng)目的基礎(chǔ)知識(shí),感興趣的小伙伴們可以參考一下2016-06-06
flutter TextField換行自適應(yīng)的實(shí)現(xiàn)
這篇文章主要介紹了flutter TextField換行自適應(yīng)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Android使用onCreateOptionsMenu()創(chuàng)建菜單Menu的方法詳解
這篇文章主要介紹了Android使用onCreateOptionsMenu()創(chuàng)建菜單Menu的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android基于onCreateOptionsMenu創(chuàng)建菜單的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-11-11
談?wù)凙ndroid中的Divider是個(gè)什么東東
在Android應(yīng)用開發(fā)中會(huì)經(jīng)常碰到一個(gè)叫divider的東西,就是兩個(gè)View之間的分割線,本文主要給大家介紹android中的divider相關(guān)知識(shí),需要的朋友可以參考下2016-03-03

