您现在的位置是:首页 > 文章详情

Android APP native 崩溃分析之令人困惑的 backtrace

日期:2019-06-01点击:447

原文地址:https://caikelun.io/post/2019-06-01-android-app-native-crash-confusing-backtrace/

完美无缺的代码逻辑,一定能产生完美无缺的程序吗?答案是否定的。从软件的层面来看,也许只有二进制才永远不会欺骗你。

现象

近期,业务方反馈了一个奇怪的崩溃问题,认为信息不足,无法解决。

Signal: 11 (SIGSEGV), Code: 1 (SEGV_MAPERR), fault addr 0x1 r0 993ff520 r1 dc3170c4 r2 00000000 r3 dabe3e08 r4 993ff520 r5 00000005 r6 00000290 r7 000007ac r8 e83253a0 r9 00006aba r10 bf921e39 r11 e83253a0 ip bfa3a9e0 sp 993ff494 lr bf88a71d pc bf96c31c #00 pc 001a731c /data/data/com.package.name/files/download/libmcto_media_player.so #01 pc 0020b7e5 /data/data/com.package.name/files/download/libmcto_media_player.so #00 993ff494 0000022c 993ff498 adcfd000 [anon:libc_malloc] 993ff49c bf88a71d /data/data/com.package.name/files/download/libmcto_media_player.so 993ff4a0 ffffffff 993ff4a4 ffffffff 993ff4a8 bf9d07e7 /data/data/com.package.name/files/download/libmcto_media_player.so #01 993ff4ac 00000000 993ff4b0 00000000 993ff4b4 00000000 993ff4b8 00000000 993ff4bc 00000000 993ff4c0 adcfd234 [anon:libc_malloc] 993ff4c4 00000000 993ff4c8 0000006e 993ff4cc 00000000 993ff4d0 adcfdf6c [anon:libc_malloc] 993ff4d4 00000000 993ff4d8 00000000 993ff4dc 00000000 993ff4e0 00000000 993ff4e4 00000000 993ff4e8 00000000 

第一感觉这肯定是动态库的业务逻辑有 bug,导致了段错误。backtrace 确实是不完整的,但一下也看不出为什么不完整,确实需要协助分析一下。

分析

backtrace 不完整的常见原因

backtrace 不完整的情况时有发生,常见的原因有:

  • 崩溃时 stack 内存被大量误写。如果崩溃点附近的逻辑正在处理的外部输入随机性很大,情况就更加糟糕,往往会看到大量离散的不完整 backtrace。举例:
#00 pc 00000ffb <anonymous:c34fe000> #01 pc 0009a885 /data/app/com.package.name-1/lib/arm/libjsc.so #02 pc 0003ff93 /data/app/com.package.name-1/lib/arm/libjsc.so #03 pc 0011f60f /data/app/com.package.name-1/lib/arm/libjsc.so #04 pc fffffffb <unknown> 
#00 pc 000092fe <anonymous:c15d0000> #01 pc 00099ec3 /data/app/com.package.name-1/lib/arm/libjsc.so #02 pc 00003ffe <anonymous:bf140000> 
#00 pc 00000ffb <anonymous:ef304000> 
  • 调用路径上的某些 ELF 文件的 unwind table 不完整。比如某些系统的 odex/oat,还有系统的 WebView Chromium,都属于此类。举例:
#00 pc 00d12bcc /system/lib/libwebviewchromium.so 
#00 pc 01a0cf72 /system/app/WebViewGoogle/WebViewGoogle.apk!libwebviewchromium.so (offset 0x46da000) 
#00 pc 00006fde /data/app/com.package.name-1/lib/arm/libcros.so #01 pc 00007007 /data/app/com.package.name-1/lib/arm/libcros.so #02 pc 00007023 /data/app/com.package.name-1/lib/arm/libcros.so #03 pc 00007037 /data/app/com.package.name-1/lib/arm/libcros.so #04 pc 000070d1 /data/app/com.package.name-1/lib/arm/libcros.so #05 pc 000049bf /data/app/com.package.name-1/lib/arm/libcros.so #06 pc 000092e3 /data/app/com.package.name-1/oat/arm/base.odex 
#00 pc 00013792 /system/lib/libc.so (__futex_wait_ex+49) #01 pc 00013b21 /system/lib/libc.so (pthread_mutex_lock+310) #02 pc 00028351 /system/lib/libc.so (dlfree+48) #03 pc 0000ef33 /system/lib/libc.so (free+10) #04 pc 0000a367 /system/lib/libjavacrypto.so #05 pc 0000bc4d /system/lib/libjavacrypto.so #06 pc 022fd081 /system/framework/arm/boot.oat 
  • 调用路径上的某些 ELF 文件本身损坏了,或被移除了。另外,如果崩溃点本身位于损坏的 ELF 中,有时收到的信号会是 SIGBUS。举例:
#00 pc 5d9840f2 <unknown> #01 pc 4008ab6c <unknown> 
#00 pc 00392fd0 /system/lib/egl/libGLES_mali.so #01 pc 0002ab7b /system/lib/libgui.so (_ZN7android10GLConsumer22bindTextureImageLockedEv+182) #02 pc 0002b3a9 /system/lib/libgui.so (_ZN7android10GLConsumer14updateTexImageEv+208) #03 pc b3317c6c <unknown> 
  • 执行的指令位于 SharedMemory 中,此时读取到的 ELF 内容可能是不可靠的,为了避免误导,一般都会选择主动终止 unwind。举例:
#00 pc 0007a010 /dev/ashmem/dalvik-jit-code-cache (deleted) 
#00 pc 00019e64 /system/lib/libssl.so (SSL_clear+19) #01 pc 000103b5 /system/lib/libjavacrypto.so (_ZL25NativeCrypto_SSL_shutdownP7_JNIEnvP7_jclassxP8_jobjectS4_+156) #02 pc 00027a7d /system/framework/arm/boot-conscrypt.oat (com.android.org.conscrypt.NativeCrypto.SSL_shutdown+156) #03 pc 00032a03 /system/framework/arm/boot-conscrypt.oat (com.android.org.conscrypt.OpenSSLSocketImpl.shutdownAndFreeSslNative+138) #04 pc 0003330b /system/framework/arm/boot-conscrypt.oat (com.android.org.conscrypt.OpenSSLSocketImpl.close+434) #05 pc 003e0931 /system/lib/libart.so (art_quick_invoke_stub_internal+64) #06 pc 003e4ea3 /system/lib/libart.so (art_quick_invoke_stub+226) #07 pc 000ac2d9 /system/lib/libart.so (_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+140) #08 pc 001f27fb /system/lib/libart.so (_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameEPNS_6JValueE+238) #09 pc 001edd71 /system/lib/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+576) #10 pc 003cce3d /system/lib/libart.so (MterpInvokeVirtualQuick+504) #11 pc 003d6994 /system/lib/libart.so (ExecuteMterpImpl+29972) #12 pc 001d5351 /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadEPKNS_7DexFile8CodeItemERNS_11ShadowFrameENS_6JValueEb+340) #13 pc 001da6a3 /system/lib/libart.so (_ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameEPNS_6JValueE+142) #14 pc 001edd5b /system/lib/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+554) #15 pc 003cb927 /system/lib/libart.so (MterpInvokeStatic+322) #16 pc 003d2d94 /system/lib/libart.so (ExecuteMterpImpl+14612) #17 pc 001d5351 /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadEPKNS_7DexFile8CodeItemERNS_11ShadowFrameENS_6JValueEb+340) #18 pc 001da6a3 /system/lib/libart.so (_ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameEPNS_6JValueE+142) #19 pc 001ee931 /system/lib/libart.so (_ZN3art11interpreter6DoCallILb1ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+420) #20 pc 003cc9eb /system/lib/libart.so (MterpInvokeDirectRange+294) #21 pc 003d3014 /system/lib/libart.so (ExecuteMterpImpl+15252) #22 pc 001d5351 /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadEPKNS_7DexFile8CodeItemERNS_11ShadowFrameENS_6JValueEb+340) #23 pc 001da6a3 /system/lib/libart.so (_ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameEPNS_6JValueE+142) #24 pc 001ee931 /system/lib/libart.so (_ZN3art11interpreter6DoCallILb1ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+420) #25 pc 003cc9eb /system/lib/libart.so (MterpInvokeDirectRange+294) #26 pc 003d3014 /system/lib/libart.so (ExecuteMterpImpl+15252) #27 pc 001d5351 /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadEPKNS_7DexFile8CodeItemERNS_11ShadowFrameENS_6JValueEb+340) #28 pc 001da6a3 /system/lib/libart.so (_ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameEPNS_6JValueE+142) #29 pc 001edd5b /system/lib/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+554) #30 pc 003cce3d /system/lib/libart.so (MterpInvokeVirtualQuick+504) #31 pc 003d6994 /system/lib/libart.so (ExecuteMterpImpl+29972) #32 pc 001d5351 /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadEPKNS_7DexFile8CodeItemERNS_11ShadowFrameENS_6JValueEb+340) #33 pc 001da5f1 /system/lib/libart.so (_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadEPKNS_7DexFile8CodeItemEPNS_11ShadowFrameE+92) #34 pc 003c0fbd /system/lib/libart.so (artQuickToInterpreterBridge+944) #35 pc 003e46f1 /system/lib/libart.so (art_quick_to_interpreter_bridge+32) #36 pc 000a5511 /dev/ashmem/dalvik-jit-code-cache (deleted) 

崩溃位置的初步分析

回到问题本身。先看一下崩溃位置 001a731c

.text:001A7310 STMFD SP!, {R4,R5,LR} .text:001A7314 LDR R5, [R1] .text:001A7318 MOV R4, R0 .text:001A731C LDR R3, [R5,#-4] ;崩溃发生在这里 .text:001A7320 SUB SP, SP, #0xC .text:001A7324 CMP R3, #0 .text:001A7328 SUB R0, R5, #0xC .text:001A732C BLT loc_1A7350 .text:001A7330 LDR R3, =(dword_2759D4 - 0x1A733C) .text:001A7334 ADD R3, PC, R3 ; dword_2759D4 .text:001A7338 CMP R0, R3 .text:001A733C BNE loc_1A7364 .text:001A7340 loc_1A7340 .text:001A7340 STR R5, [R4] .text:001A7344 MOV R0, R4 .text:001A7348 ADD SP, SP, #0xC .text:001A734C LDMFD SP!, {R4,R5,PC} .text:001A7350 ADD R1, SP, #0x18+var_14 .text:001A7354 MOV R2, #0 .text:001A7358 BL sub_1A6EA8 .text:001A735C MOV R5, R0 .text:001A7360 B loc_1A7340 .text:001A7364 MOV R1, #1 .text:001A7368 ADD R0, R0, #8 .text:001A736C BL sub_1C2CAC .text:001A7370 B loc_1A7340 

这是一个相对比较短的完整的调用过程。先压栈了 R4R5LR,然后开始执行。执行到 LDR R3, [R5,#-4] 时发生了段错误,R5 的当前值是 0x5,几乎不用查 maps 也能知道 0x1 (0x5 - 0x4 = 0x1) 这个虚拟内存地址肯定是非法的,发生段错误是正常的。Signal code SEGV_MAPERR 和 fault addr 0x1 也完全符合预期。

由于只有两行 backtrace,我们继续看下一行,在 0020b7e5:

............ .rodata:0020B795 DCB "try_count_=%d",0 .rodata:0020B7E3 asc_20B7E3 DCB "://",0 .rodata:0020B7E7 aCdn DCB "CDN",0 ............ 

出乎意料的是 0020b7e5 位于 .rodata 中,但这也正好解释了为什么 unwind 过程中断了(backtrace 不完整)。

可疑之处

再次回看崩溃位置附近的指令,确实发现了可疑之处:

.text:001A7310 STMFD SP!, {R4,R5,LR} ............ .text:001A731C LDR R3, [R5,#-4] ;崩溃发生在这里 .text:001A7320 SUB SP, SP, #0xC ............ .text:001A7348 ADD SP, SP, #0xC .text:001A734C LDMFD SP!, {R4,R5,PC} 

在这个相对较短的调用过程中,一共才使用了 24 字节的栈内存空间,但是 SP 居然并没有一次性移动到位,这是很不寻常的。

unwind table

看一下 unwind table:

$ arm-linux-androideabi-readelf -u ./libmcto_media_player.so ............ 0x1a7268: 0x80b108ab Compact model index: 0 0xb1 0x08 pop {r3} 0xab pop {r4, r5, r6, r7, r14} 0x1a7310: 0x8002a9b0 Compact model index: 0 0x02 vsp = vsp + 12 0xa9 pop {r4, r5, r14} 0xb0 finish 0x1a7424: 0x8001a8b0 Compact model index: 0 0x01 vsp = vsp + 8 0xa8 pop {r4, r14} 0xb0 finish ............ 

崩溃位置 001a731c 匹配了起始 offset 为 1a7310 的 unwind 信息码 0x8002a9b0,根据这里的信息,unwind 时 SP 的值一共需要增加 24 个字节(出栈 24 个字节的数据)。但是从前述的汇编指令可以看出,当崩溃发生时(执行到 001a731c),SP 的值只减少了 12 个字节(STMFD SP!, {R4,R5,LR}),这就是问题所在了。

再看 stack

根据 stack 中的数据:

#00 993ff494 0000022c 993ff498 adcfd000 [anon:libc_malloc] 993ff49c bf88a71d /data/data/com.package.name/files/download/libmcto_media_player.so 993ff4a0 ffffffff 993ff4a4 ffffffff 993ff4a8 bf9d07e7 /data/data/com.package.name/files/download/libmcto_media_player.so #01 993ff4ac 00000000 993ff4b0 00000000 993ff4b4 00000000 ............ 

我们看到 unwind 过程其实是严格按照 unwind table 中的信息进行的,于是就被误导了,SP 比实际需要的多移动了 12 个字节,真正的 LR 保存在内存地址 993ff49c 中,它的值是 bf88a71d,根据 maps 信息,我们计算出了这个绝对地址相对于当前 ELF 的 offset,可惜由于业务方动态库的逻辑比较复杂,调用层次也比较深,而 unwind 过程过早的终止了,仅凭现有的寄存器、stack 和 memory 等信息,已经不足以帮助业务方定位问题。

001a731c 处究竟是什么?

这种 “unwind table 信息” 与 “对应的汇编指令序列” 相矛盾的情况并不常见。001a731c 处究竟是什么函数?为什么会有这样的指令序列 ?

从业务方那里拿到了带调试符号的动态库文件:

$arm-linux-androideabi-addr2line -f -e ./libmcto_media_player.so 001a731c _ZNSsC1ERKSs libgcc2.c:? $arm-linux-androideabi-c++filt -n _ZNSsC1ERKSs std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) 

原来是 std::basic_string 的构造函数。

这个问题很有可能是 NDK 的 bug 造成的。从业务方那里了解到,他们用的 NDK 版本是 r9d。

了解你使用的 NDK

开发和维护一套跨平台的交叉编译工具绝非易事。相对于 C 语言,编译器需要付出很多额外的努力,才能确保 C++ 的各种语法特性在运行时能够如预期的那样工作,C++ 标准库长久以来也是各种不同版本共存的局面。要支持 Android 新版本对底层的改动,又要维护向后的兼容性。NDK 其实并不如我们预想的那样稳定可靠。可以到 NDK 的 github 官方 issues 了解一下现状。

NDK 从 r11 开始才在 Changelog 里明确的列出了重要的 Known Issues。

我们看到在 r11 Changelog 的 Known Issues 中赫然写到:

Exception handling will often fail when using c++_shared on ARM32. The root cause is incompatibility between the LLVM unwinder used by libc++abi for ARM32 and libgcc. This is not a regression from r10e.

r12 Changelog 的 Known Issues 中写到:

Exception unwinding with c++_shared still does not work for ARM on Gingerbread or Ice Cream Sandwich.

我们知道 C++ 的异常处理机制在运行时也是依赖于 unwind 的。问题应该就出在这里了。

结论

业务方使用较新版本的 NDK 重新编译了动态库,我们检查了 std::basic_string 对应的汇编指令,发现这次 SP 在函数开头处一次性移动到位了。应该没有问题了。业务方上线重新编译的动态库,拿到完整的 backtrace 后,就可以定位并修复这个段错误问题了。

因此,这次 backtrace 不完整问题的原因是:低版本 NDK 的 bug,导致生成的动态库在某些情况下无法正确的执行 unwind

根据前述 Known Issues 中的描述,不但崩溃后的 backtrace 获取有时会受到影响,业务逻辑自身用到 C++ 异常机制的地方,也有可能会受到影响,具体来说就是:抛出异常后,可能无法如代码逻辑预期的那样去逐层的执行异常捕获逻辑。如果真的存在这样的隐藏问题,希望这次的 NDK 版本升级也能把它们一起修复了。

关于崩溃捕获工具

最后又到了我们的广告时间。

上述所有的线上崩溃信息,都是使用我们自己开发的 Android APP 崩溃捕获工具 xCrash 捕获的。

原文链接:https://my.oschina.net/nomagic/blog/3057227
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有1条评论来说两句吧...

优秀的个人博客,低调大师

theredg 说:

低版本 NDK 的 bug,导致生成的动态库在某些情况下无法正确的执行 unwind。- > 多少算是低版本NDK呢?

2021-10-29

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章