当前位置: 代码网 > it编程>App开发>Android > Android解读Native崩溃栈信息的方法详解

Android解读Native崩溃栈信息的方法详解

2024年05月19日 Android 我要评论
大部分的 android 开发者使用的主要语言都是 kotlin / java,他们的崩溃栈信息非常清晰,也非常好定位到问题,如果是线上的崩溃通常还会对类名进行混淆,还需要一个混淆文件对堆栈翻译一下就

大部分的 android 开发者使用的主要语言都是 kotlin / java,他们的崩溃栈信息非常清晰,也非常好定位到问题,如果是线上的崩溃通常还会对类名进行混淆,还需要一个混淆文件对堆栈翻译一下就能够得到源码中的类名。
但是很多人对 c/c++ 的崩溃栈就无能为力了,今天这篇文章就来扒一扒 native 的崩溃栈信息。

native 崩溃栈信息

我们经常能够看到有类似下面的 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 中的崩溃是通过系统信号来实现的,比如我们上面的异常信号就是 sigabrtandroid 的进程在启动时就会添加一个 signalcatcher,来捕获信号,不同的信号有不同的处理方式,sigabrt 就是会直接退出程序,也就是我们常说的崩溃,android 中还有一个非常重要的信号就是 sigquit,在 android 中表示发生了 anr,默认的处理逻辑是 dump 栈信息和内存 gc 相关的信息到本地文件。
好了这里说得有点远了,回到上面的问题,我们刚开始看到上面的数据可能有点懵逼,pc 后面有一串 16 进制的数字表示程序计数器的位置 (简单理解就是执行的机器码对应的位置),后面的文件表示崩溃的栈中相关的 .so 动态链接库。但是你又要说了,这一串地址谁能够看出什么问题啊?🤔️ 你先不要急,通常线上的用户崩溃看到的栈是这样的,因为 androidrelease 包默认会抹掉一部分叫做符号表的东西,如果你看过我上面的文章你就会豁然开朗,这个符号表描述了指令地址和对应方法或者变量的映射(方法名,全局变量名都是符号),通常我们用的别人的 .so 包也会抹掉符号表(这可能就是不想让你看,起到了一个混淆作用),少了一个表在线上的运行中性能会更加好(至少这部分内存不用消耗了)。

通常我们自己打的 debug 包就没有抹掉符号表,如果是有符号表信息,我们看到的上面异常信息通常是下面这样的:

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)

你看这里就有调用所对应的方法了,因为我这里的 decode() 方法是递归调用的,所以你看到上面的栈中有多个,方法后面还有一个 +532 表示该地址离方法开始的地址的偏移量,如果你的 .so 文件里面还有 debug 信息,这个 +532 能够定位到某一行 c \ c++ 源码,其实就是每条指令都映射了某一行代码。

android 的打包过程中如果你希望 release 包也不要移除符号表信息,可以通过在 build.gradle 中添加以下配置来避免符号表被移除:

// ...
packagingoptions {
    donotstrip "*/arm64-v8a/*.so"
    donotstrip "*/armeabi-v7a/*.so"
    donotstrip "*/x86/*.so"
    donotstrip "*/x86_64/*.so"
}
// ...

如果符号表被移除了那我们不是永远都不知道崩溃的方法是什么了?当然不是,被移除的符号会被保存到另外的文件中,线上的崩溃可以通过这个文件再次翻译成对应的方法。以下就是符号文件对应的路径:

它解压后如下:

他们也是 elf 格式的文件,每个都对应了一个 .so 库。

那么我们怎么判断一个 .so 文件是不是有符号表呢?可以通过 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 就表示有符号表。

libtmediaplayer.so: elf 64-bit lsb shared object, arm aarch64, version 1 (sysv), dynamically linked, buildid[sha1]=58ab2061a06db613d9c3ca66a214872ad88636f7, stripped

stripped 就表示没有符号表。

tips: 如果你上架的应用没有这个符号表,google play 还会提醒你上传,google play 是可以帮你捕获 native 崩溃的,它需要符号表解析这些地址信息。

符号表

我们了解 elf 文件就知道,我们上面说的符号表就是本地符号表对应的就是 .symtab section,这个表对我们的程序运行没有任何的影响,我们调用本地方法都是通过地址直接跳转,而不需要本地符号表。
还有一个符号表是 .dynsym,它是动态链接的符号表,这个表是供 ld.so 使用的,因为这里面的符号都是暴露给其他的程序调用的,ld.so 需要通过这个表去查询暴露给其他程序的符号的地址,所以不能删除。

我们可以通过 readelf -s [elf file] 读取符号表:

以下是有符号表的数据:

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
  // ...

如果没有本地符号表就只有以下信息(少了 .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
   // ...

我们再来看看上面的那个崩溃栈地址 000000000001bd2c,我在 反编译 .text 代码(.text section 就是用来存储代码的,通过 objdump --dissassemble --section=.text [elf file] 命令可以反编译机器码到汇编) 后找到了这个地址所在的方法:

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>

方法的指令有点长,我省略了大部分,我们看到 1bd2c 处调用了 abort() 方法,这个方法就是用来发送 sigabort 的,这是我测试时添加的,我们再来看看 1bb5c 在符号表中对应的是哪个符号,正好就是 _zn19tmediaplayercontext29parsedecodevideoframetobufferep18tmediadecodebuffer 方法,哈哈。

最后

本篇文章介绍了符号表,还通过崩溃栈中的地址,在符号表中去查询到了我们对应的方法,希望你对 native 的崩溃信息和符号表有一个全新的认识。

以上就是android解读native崩溃栈信息的方法详解的详细内容,更多关于android解读native崩溃栈信息的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com