NDK撩妹三部曲(續)—NDK 開發如何優雅的定位 Native 異常,看這篇就夠了

從何說起?

  上週拿出 1/10 的本領教會妹子入門了 NDK之後,妹子QQ上留言給我說她還不滿足。一瞬間我有點懷疑自己了,“年少有爲,血氣方剛,8塊腹肌,一頭濃髮都滿足不了妹子,難道我沒有自己想的那麼猛嗎”?臥槽跑題了,咳咳。
  被妹子這麼一說我肯定不舒服,就跑去問妹子了。“小愛,上週我們學的東西你都消化啦”?“嗯嗯,Q哥,我已經學得差不多了,你快教我一點新東西嘛~”,雖然聽的我是一身的雞皮疙瘩,但是本着助人爲樂,共同學習,分享快樂的宗旨,我決定再從我已經不多的庫存中忍痛割愛,好歹也要對的起妹子8塊錢的“奈雪の茶”。

摘要

  喏,不管你是 Android 新手,或者說已經是職場老司機,如果你還不知道怎麼定位工作學習中遇到的 Native 異常,別怕,看完這篇後就再也不怕定位不到問題了。
  在 Android 開發中我們常常碰見程序閃退的情況,應用層的異常最常見的就是 java.lang.NullPointerException,java.lang.IndexOutOfBoundsException,java.lang.NumberFormatException 等等,這些問題都很好定位,日誌也全。而假如某次使用第三方 so 庫的時候報錯了,那就很讓人抓狂了。因爲 so 庫一般都是 C 或 C++ 寫的,對內存管理不好的同學,就會莫名其妙的出現野指針錯誤、內存訪問錯誤、越界錯誤等等。那今天,學會下面這幾個方法後,你就可以找到 so 庫的開發,高傲的吐槽一波他們了(狗頭)。

  其實 NDK 早已經幫我們想到了,在它的安裝目錄下有3款工具:arm-linux-androideabi-addr2line.exe ,arm-linux-androideabi-objdump.exe,ndk-stack.exe。前兩個工具的前綴 “arm”根據不同的 ABI 平臺不同,比如我們項目打的 armeabi-v8a 包,就是 aarch64-linux-android-addr2line.exe 和 aarch64-linux-android-objdump.exe。

  • aaddr2line 是標準 GNU tools 工具家族中的一部分,常用來將指令的地址和可執行映像轉換成文件名、函數名和源代碼行數。
  • objdump 是 linux 下的反彙編工具,常常用來反彙編二進制文件以分析其中的附加信息。
  • ndk-stack 從 ndk r6版本就已經引入,從 ndk r20 開始不再已 exe的形式存在,而是藉助 python 腳本的形式存在,ndk-stack 可以幫助開發者 過濾 adb logcat 的堆棧跟蹤信息,並可以把不認識的內存地址信息轉換成可讀的信息。

有了這3個神器,接下來就讓我們跟着 Demo 實操一波。

案例實操

aaddr2line

下面的代碼中我們定義了一個簡單的test方法,在第三行有一個空指針 pvalue ,然後在第4行去訪問這個空指針的下標,然後導出 so 庫,到 android 下執行。

1 #include"test.h"
2 #include<iostream>
3 using namespace std;
4  int TESTSHARED_EXPORT test()
5  {
6      int *pvalue = nullptr;
7      int temp = value[5];
8     return temp;
9  }

很明顯這是一個空指針錯誤,不出意外會看到下面的錯誤信息:

06-09 10:19:07.202 27963-27963/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Build fingerprint: 'Xiaomi/umi/umi:10/QKQ1.191117.002/V11.0.24.0.QJBCNXM:user/release-keys'
    Revision: '0'
    ABI: 'arm64'
    Timestamp: 2020-06-09 10:19:07+0800
    pid: 27929, tid: 27929, name: com.qht.jnatest  >>> com.qht.jnatest <<<
    uid: 10227
    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x16
    Cause: null pointer dereference
        x0  0000000000000014  x1  00000000000001f4  x2  0000000000000000  x3  0000000000000000
        x4  0000000000000000  x5  0000000000000000  x6  0000000000000000  x7  0000000000000000
        x8  0000000000000002  x9  86bc442885ebe4dc  x10 0000000000430000  x11 000000000000001a
        x12 000000000c25b618  x13 000000000048ed40  x14 0000000000000006  x15 ffffffffffffffff
        x16 0000007c7defdf70  x17 0000007d6cb81440  x18 0000007d6f318000  x19 0000007ff52e9278
        x20 0000007ff52e9700  x21 0000007ff52e9290  x22 0000007ff52e9278  x23 0000000000000000
        x24 0000007c7dea05cc  x25 0000000000000010  x26 00000000000000c9  x27 0000007c7defe218
        x28 0000007d6eaccb80  x29 0000007ff52e9200
        sp  0000007ff52e91b0  lr  0000007c7dee7e74  pc  0000007c7dea05dc
06-09 10:19:07.516 27963-27963/? A/DEBUG: backtrace:
          #00 pc 00000000000005dc  /data/app/com.qht.jnatest-olKAAEfG2oMHNZvfzn8-SQ==/lib/arm64/libsoTest.so (test+16) (BuildId: 11d09b93a1d89acbc83015d06a5fca2c3bc72adf)
          #01 pc 000000000000fe70  /data/app/com.qht.jnatest-olKAAEfG2oMHNZvfzn8-SQ==/lib/arm64/libjnidispatch.so (ffi_call_SYSV+96)
          #02 pc 000000000000f660  /data/app/com.qht.jnatest-olKAAEfG2oMHNZvfzn8-SQ==/lib/arm64/libjnidispatch.so (ffi_call+292)
          #03 pc 0000000000005b80  /data/app/com.qht.jnatest-olKAAEfG2oMHNZvfzn8-SQ==/lib/arm64/libjnidispatch.so
          #04 pc 00000000000079ec  /data/app/com.qht.jnatest-olKAAEfG2oMHNZvfzn8-SQ==/lib/arm64/libjnidispatch.so (Java_com_sun_jna_Native_invokeInt+32)
          #05 pc 0000000000140350  /apex/com.android.runtime/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 112fa750f6a9adbd7b599e735b27a900)
          #06 pc 00000000001375b8  /apex/com.android.runtime/lib64/libart.so (art_quick_invoke_static_stub+568) (BuildId: 112fa750f6a9adbd7b599e735b27a900)

其實從上面的信息中已經能看到 Cause: null pointer dereference 字眼,但是卻無法知道錯誤代碼的行數。
平臺是“arm64”,SIGSEGV 信號是 linux 系統發出的,錯誤碼 11 一般代表了 空指針引用或者多次釋放,linux 錯誤信號查看方法:

vim /usr/include/asm-generic/errno-base.h

雖然我們知道 libsoTest.so 發生了錯誤,但還是不知道出錯在哪行,什麼函數。

找到你安裝ndk中 addr2line 的路徑,打開 cmd 輸入下面的命令(aarch64-linux-android-addr2line.exe --help 查看幫助):

D:/Java/android-ndk-r20/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line.exe -e libsoTest.so 00000000000005dc

-e:出錯 so 庫的路徑
00000000000005dc: so 庫出錯的彙編地址,上面以 0000000000 開頭的就是,後面都會用到這個地址
按下回車後,
在這裏插入圖片描述
addr2line 已經很明確的告訴我們出錯在第 7 行了。
另外建議將 aarch64-linux-android-addr2line.exe 的絕對路徑添加到環境變量,這樣以後就不用去找這個東西了,

objdump

找到 objdump 的目錄,和 addr2line 在同級目錄下,cmd 輸入如下指令(aarch64-linux-android-objdump.exe --help查看幫助):

D:/Java/android-ndk-r20/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-objdump.exe -S libsoTest.so > libsoTest.txt

在這裏插入圖片描述
  在上面輸出的彙編指令中找到出錯so庫的彙編地址 00000000000005dc,可以看到出錯地址介於空指針引用錯誤和 return 語句之前。
  我們這個案例屬於比較簡單的情況,大多數生產環境的代碼比這要複雜的多,因此生成的彙編代碼比較難懂,但是宗旨就是找出錯的彙編地址,找到後,離具體的方法名和函數就不遠了。

ndk-stack

ndk-stack 的目的是幫助我們將晦澀難懂的彙編內存信息轉換成能看懂的文件名+類名+函數名+行號信息。
ndk-stack 有兩種用法:

1、假設我們已經通過 adb logcat 拿到了程序崩潰的日誌信息。

這種情況一般是在開發中,或者是在測試過程中測試同學幫助我們保存了 logcat 日誌。

D:/Java/android-ndk-r16-windows-x86_64/android-ndk-r16/prebuilt/windows-x86_64/bin/ndk-stack --sym  D:/WorkSoftware/AndroidWorkSpace/jnatest/app/libs/arm64-v8a --dump log1.txt

在這裏插入圖片描述
可以看到出錯的文件名和行號。

2、我們沒有 logcat 日誌,但是有錯誤堆棧

比如線上環境,是沒有 logcat 日誌的,但是假如集成了 bugly 等工具,也是可以拿到錯誤堆棧的。然後將堆棧信息複製到 txt 文件中,並在文件的開頭加上:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

因爲 ndk-stack 會在開始解析 logcat 輸出時查找第一行星號。

集成了騰訊 bugly 的 Native 項目線上崩潰了怎麼辦

我們的項目集成了騰訊的 bugly ,在線上版本發生崩潰後會上報堆棧信息到頁面,但和本地一樣,某些堆棧信息根本看不出來問題出到哪兒了。

bugly 支持 so 庫符號表上傳,具體參考:
Bugly Android 符號表配置

1、查看線上版本 so庫的 UUID
在這裏插入圖片描述
2、找到本地對應版本的 so 庫,並通過 bugly 提供的工具查看 UUID 是否對應
這兒有個前提條件,就是每次發版本前都要把我們打包的 so 庫的 debug 版本保存一份,這樣以後出現問題才能定位到問題在哪兒,否則 so 庫版本不對應符號表就查不到了。

3、下載上圖中的符號表工具,並解壓,然後在 setting.txt 中配置 bugly 的 ID 和 Key。
在這裏插入圖片描述
4、cmd 輸入如下指令:

C:\Users\HiWin10\Downloads\buglySymbolAndroid2.6.3>buglySymbolAndroid.bat -i E:\xxxx\arm64-v8a\libsoTest.so -o E:\xxxx\arm64-v8a\libsoTest.zip

上面的命令是生成 so 庫對應的符號表文件,解壓生成的 zip 後得到後綴是 .symbol 的文件,即爲 bugly要求的符號表文件。然後使用文本打開此文件,可以看到這個 so 庫的 UUID。
在這裏插入圖片描述
5、如果 UUID 和 bugly 頁面上的 UUID 對應,即代表此 so 庫版本爲線上的版本。
然後將上面生成的 zip 文件上傳到 bugly 符號表頁面:
在這裏插入圖片描述
完成稍等一會兒應用成功後,再次打開我們的崩潰信息,可以看到已經可以定位到具體的類和行數了。
在這裏插入圖片描述


csdn地址:http://blog.csdn.net/u012534831
github地址:https://github.com/qht1003077897

如有幫助,請多多點贊支持。

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