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

如有帮助,请多多点赞支持。

猜你喜欢

转载自blog.csdn.net/u012534831/article/details/106628421