Android Native報錯定位(addr2line工具的使用)

Android Native報錯定位

今天調試Android stagefright模塊,修改MediaCodec.cpp文件時,一不小心在代碼裏寫了個空指針進去。

於是得到了下面這個報錯日誌:

--------- beginning of crash
libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 5104 (MediaCodec_loop)
DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
DEBUG   : Build fingerprint: '*******************************************'
DEBUG   : Revision: '0'
DEBUG   : ABI: 'arm'
DEBUG   : pid: 5050, tid: 5104, name: MediaCodec_loop  >>> com.google.android.exoplayer2.demo <<<
DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
DEBUG   : Cause: null pointer dereference
DEBUG   :     r0 ********  r1 ********  r2 ********  r3 ********
DEBUG   :     r4 ********  r5 ********  r6 ********  r7 ********
DEBUG   :     r8 ********  r9 ********  sl ********  fp ********
DEBUG   : 
DEBUG   : backtrace:
DEBUG   :     #00 pc 00018e90  /system/lib/libc.so (__memcpy_base+185)
DEBUG   :     #01 pc 000446e9  /system/lib/libc.so (__sfvwrite+310)
DEBUG   :     #02 pc 00044943  /system/lib/libc.so (fwrite+122)
DEBUG   :     #03 pc 000dc169  /system/lib/libstagefright.so (_ZN7android10MediaCodec21onReleaseOutputBufferERKNS_2spINS_8AMessageEEE+1432)
DEBUG   :     #04 pc 000d7a59  /system/lib/libstagefright.so (_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE+156)

想着平時分析日誌時,經常會遇到這樣的打印,因爲看不懂,通常都是直接繞過。索性今天悠閒,清掃一下知識上的戰爭迷霧吧。

源碼部分:

status_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) {
	// 這是我添加的代碼 也是報空指針的代碼,代碼在MediaCodec.cpp文件中的3077行
	size_t count = fwrite(heliBuf, sizeof(heliBuf) , 1, fp);
	// *****************
}

日誌分析

簡單看看logcat,能夠提取到Cause: null pointer dereference

我們知道,是一個空指針異常導致了crash。

那麼這個空指針是在哪一行代碼調用的呢?

可以看到backtrace:下面有crash時的調用棧信息。格式爲:

#編號 pc 異常地址 共享庫路徑 (函數名)

例1.0:

#00 pc 00018e90 /system/lib/libc.so (__memcpy_base+185)

所以,示例1.0,可以翻譯爲:在共享庫 /system/lib/libc.so的*__memcpy_base*函數中報了一個空指針。

logcat中打印的調用順序,都是從下到上看的。例如,這裏是先調用#04這一行,再調用#03… ,最後調用#00。所以,嚴格的說,#00這一行,是本次報錯的代碼。

但通常,我們編碼都會直接或間接的使用系統現有庫,而這些庫久經沙場,基本不會有什麼問題,如果#00編號的調用指向的是一個系統庫,不要猶豫,一定是你非法調用了它。

線索一

回到backtrace,#00、#01、#02行的共享庫都是libc.so,這就是一個系統庫。在#02行的函數名中,能夠得知是調用fwrite時出現了異常。

線索二

#03 pc 000dc169 /system/lib/libstagefright.so (_ZN7android10MediaCodec21onReleaseOutputBufferERKNS_2spINS_8AMessageEEE+1432)

從這裏,終於看到了我修改的代碼所屬模塊:libstagefright.so

MediaCodec.cpp 代碼目錄就位於:/frameworks/av/media/libstagefright目錄下

這裏能給我們提供的信息有:

  1. 異常出現在libstagefright.so庫中。

  2. 異常出現在_ZN7android10MediaCodec21onReleaseOutputBufferERKNS_2spINS_8AMessageEEE函數中。

從第二點那串亂碼中,大致已經可以看到發生異常的函數名稱了。但爲什麼是亂碼!!!

google了一把,原來編譯器在編譯時,對函數名做了一定優化。

既然是編譯器的事情,那一定是有一定規則,想想這規則應該也不會簡單。有沒有簡單的方法,能把這堆亂碼還原?

當然有,國外有大兄弟做了個網站專門“翻譯”這堆亂碼:https://demangler.com/

DEMANGLE 了一下,得到了這串亂碼的實際函數名:android::MediaCodec::onReleaseOutputBuffer(android::sp<android::AMessage> const&)

在這裏插入圖片描述)

直白的表明了,這是MediaCodec.cpp中的onReleaseOutputBuffer函數。xia! xia!

這個時候,我選擇去茶水間,衝一杯…好吧,我沒有咖啡。

讓我們來理一理:

  • 異常出現在libstagefright.so庫中

  • 異常是一個空指針。

  • 發生在fwrite調用中。

  • fwrite又被onReleaseOutputBuffer調用。

離真相已經很近了。我去代碼裏看了一下onReleaseOutputBuffer函數,居然只有我插入的代碼裏調用了fwrite。

爲了讓文檔繼續下去,我們來假裝onReleaseOutputBuffer函數中,有無數的fwrite調用、或者報錯的是一個第三方庫,我們並不知道源碼。怎麼知道報錯的代碼,具體在文件的哪一行呢?

addr2line使用

從名字上就可以看出,這玩意兒是將一個地址轉換成行號的工具。

位置

如果你有在用Android Studio,那麼恭喜你,可以很方便在自帶NDK目錄下找到它。

比如我的是這樣:

\Android\Sdk\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line.exe

沒有裝Android Studio的同學,可以自行到官網下載NKD,解壓後就可以看到了,路徑類似。

https://developer.android.com/ndk/downloads?hl=zh-cn

使用

arm-linux-androideabi-addr2line [option(s)] [addr(s)]

簡單來說就是arm-linux-androideabi-addr2line + 可選項 + 異常地址

先來看看可選項都有哪些:

[option(s)] 介紹
@ 從文件中讀取options(暫時不研究)
-a 在結果中顯示地址addr(ps:沒啥用,因爲這個地址本來就是我們輸入命令中的)
-b 設置二進制文件的格式(暫不研究)
-e 設置輸入文件(常用:選項後面需要跟報錯的共享庫,用於addr2line程序分析)
-i unwind inline function(暫不研究)
-j Read section-relative offsets instead of addresses(暫不研究)
-p 讓輸出更易讀(親測,沒啥區別,可能其它選項已經很易懂了)
-s 在輸出中,剝離文件夾名稱(這個還不錯,前面有一長串目錄確實噁心)
-f 顯示函數名稱(也比較有用,可以直觀的看出是在哪個函數中報的錯)
-C(大寫的) 將輸出的函數名demangle(類似於demangler.com這個網站的功能)
-h 輸出幫助
-v 輸出版本信息

那麼多選項,在這裏能用上(也是常用)的,也就是 -e、 -s、-C、-f了。

實戰addr2line出現“??:?”

鋪墊了那麼多,是時候試驗一下了:

C:\SuperLi>arm-linux-androideabi-addr2line -e \system\lib\libstagefright.so -s -f -C 000dc169
_ZN7android10MediaCodec21onReleaseOutputBufferERKNS_2spINS_8AMessageEEE
??:?

eum~ 出現:_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE,加個-C選項再試試:

C:\SuperLi>arm-linux-androideabi-addr2line -e \system\lib\libstagefright.so -s -f -C 000dc169
android::MediaCodec::onReleaseOutputBuffer(android::sp<android::AMessage> const&)
??:?

??:? 蝦米,看到這個問號,我不由想起黑人問號的表情包。

只能google了:

在使用 addr2line 過程中經常會遇到 “??:?” 或 “??:0” 這種情況,原因就是一般 C/C++ SDK 都會進行添加 map 混淆,在編譯配置選項中不生成符號表 symbolic 信息,不過 AndroidStudio 會默認爲 so 文件添加符號表。

也就是說,我使用了一個不帶符號表的庫。

google說了,如果是aosp編譯的話,在out/target/product/[productname]/symbols/system/lib/****.so下面會自動生成帶了符號表的共享庫。找了找,真的有。於是我改了一下addr2line輸入文件的位置:

C:\SuperLi>arm-linux-androideabi-addr2line -e \symbols\system\lib\libstagefright.so -s -f -C 000dc169
fwrite(void const*, unsigned int pass_object_size0, unsigned int, __sFILE*)
stdio.h:375

Bingo!!!成功的去掉了討厭的黑人問號(??:?)。但是爲什麼函數名稱不一樣了?(哪位大神知道原因,求解惑啊)索性把和libstagefright.so相關的addr都加進來看看吧:

C:\SuperLi>arm-linux-androideabi-addr2line -e \symbols\system\lib\libstagefright.so -s -f -C 000dc169
fwrite(void const*, unsigned int pass_object_size0, unsigned int, __sFILE*)
stdio.h:375
android::MediaCodec::onMessageReceived(android::sp<android::AMessage> const&)
MediaCodec.cpp:2508

結果中可以看出,報錯的地方在MediaCodec.cpp文件的2508行。

推了推鼻樑上不存在的眼鏡,嘴角扯出一抹弧度:小樣,我找到你了。

然鵝,在MediaCodec.cpp文件中ctrl + g 了一把。我擦,2508行代碼居然只是這個:

 我是2508行:   status_t err = onReleaseOutputBuffer(msg);

而我們真實報錯的地方是在3077行。

因爲fwrite在onReleaseOutputBuffer函數中只有一處調用,我們能通過閱讀onReleaseOutputBuffer函數的代碼,將異常鎖定在3077行,但這只是妥協的方法。如果onReleaseOutputBuffer函數中有無數的fwrite怎麼搞?暫時能想到的只有加log一步步調試了。

好吧,目前也只能做到這一步了。

小結

1、通過實驗addrline + symbolic版本的so,只能將異常定位到庫中的函數級別,而不能具體到行號

2、要精準定位,還需要結合上下文才能確定。

那麼,所有的庫都需要這麼麻煩嗎?

準備使用Android studio來測試一下。

android studio編譯的庫

打開Android studio,通過File > New > New Project > Native C++ … 創建一個Test 工程。

在自動生成的native-lib.cpp 文件的Java_com_dali_myapplication_MainActivity_stringFromJNI函數中,故意加入了一個a = b / c的除零異常。

native-lib.cpp 代碼如下:

#include <jni.h>
#include <string>
using namespace std;

extern "C" JNIEXPORT jstring JNICALL
Java_com_dali_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    string hello = "Hello from C++";
    int b = 3, c = 0;
    int a = b / c;
    return env->NewStringUTF(hello.c_str());
}

Activity代碼如下:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
    public native String stringFromJNI();
}

運行後,立馬報錯,關鍵日誌如下:

 DEBUG   : signal 8 (SIGFPE), code -6 (SI_TKILL), fault addr -------- 
 DEBUG   :     r0 00000000  r1 000016aa  r2 00000008  r3 8efbcbe4 
 DEBUG   :     r4 8efbcbe4  r5 00000001  r6 00000000  r7 0000010c
 DEBUG   :     r8 0000004e  r9 8dc10000  sl 9eadb790  fp 8dc10000
 DEBUG   :     ip 9eadb768  sp 9eadb6c8  lr 79a1ee9b  pc 8ef4c964  cpsr 00000010 
 DEBUG   :                                       
 DEBUG   : backtrace:
 DEBUG   :     #00 pc 0004a964  /system/lib/libc.so (tgkill+12) 
 DEBUG   :     #01 pc 0001ce97  /data/app/com.dali.myapplication-H8qoqUb6cjv0hUDfGYRtRQ==/lib/arm/libnative-lib.so
 DEBUG   :     #02 pc 000064e1  /data/app/com.dali.myapplication-H8qoqUb6cjv0hUDfGYRtRQ==/lib/arm/libnative-lib.so (Java_com_dali_myapplication_MainActivity_stringFromJNI+108)                  

使用addr2line運行一下:

C:\SuperLi>arm-linux-androideabi-addr2line -e libnative-lib.so 000064e1 -s 
native-lib.cpp:11

可以看到,Android studio中編譯出來的so,完全沒有各種困擾,很直接的指出的異常發生的行號。

原因是,Android studio會在編譯時,自動生成附帶有符號表的so。

其它

在對應庫的編譯目錄下的Android.mk文件中,增加gcc警告和調試標誌:

LOCAL_CFLAGS := -Wl,-Map=test.map -g  

可能有不一樣的效果,爲啥說不一樣的效果呢?因爲俺們沒驗證,暫時感覺沒需求。

總結

1、使用addr2line需要附帶符號表的so文件。

2、addr2line工具並不是一定能將異常位置定位到行,還需要其它調試手段輔助。

3、如果c/c++庫程序是使用Android studio開發,因爲生成的so自帶符號表,所以使用addr2line工具可以直接定位。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章