作者:vbppn65853 | 来源:互联网 | 2023-07-27 15:49
Xcode支持崩溃日志自动符号化,前提是本地有当时Build/Archive生成的dSYM文件,iOS崩溃日志符号化后,可以帮助开发者更好的定位问题,但如果dSYM文件丢失或拿到的崩溃日志不是标准的crash log,如何定位crash呢,笔者结过尝试发现一样可以定位到具体函数。
在无dSYM文件情况下,之所以无法解析出崩溃地址对应的函数名,是因为Xcode在导出ipa时会去除Symbol Table(符号表)的非系统符号部分。这时address无法对应函数名,所以无法确定是在哪个函数或block中出了问题。因此解析日志的关键是要恢复符号表,国内已有大神做过研究 杨君的小黑屋,本文基于此完成解析目标。
我们以测试程序CrashTest的崩溃为例,介绍一下具体解析步骤
如图,
我们拿到了崩溃日志,这是一个arm64架构的崩溃日志,从最后的backtrace我们知道程序在访问数组元素时异常终止,但由于本地没有对应的dSYM文件,Xcode没有将红框内3,4两行符号化为具体的函数名,本文的工作就是要将这2行符号化
开始之前,先解释一下这几行地址的含义
栈:左边这一列是崩溃时的调用栈地址(虚拟内存地址)
基址:基址指向的地址是CrashTest这个模块加载到内存中的起始地址
偏移:左边栈地址 = 基址 + 偏移 (注意是10进制的)
什么?你拿到的崩溃日志不是这样的! (老司机请绕过)
像这样,只有一堆地址
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Triggered by Thread: 0Last Exception Backtrace:
(0x186e9de48 0x1975dc0e4 0x186d83a54 0x10000c00c 0x10000bf7c 0x18b6810f8 0x18b66a22c 0x18b680a94 0x18b680720 0x18b679c74 0x18b64d38c 0x18b8ec1b4 0x18b64b8f4 0x186e560e8 0x186e5538c 0x186e5343c 0x186d811f4 0x18ff0f5a4 0x18b6b2784 0x10000c574 0x197c4aa08)Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x0000000197d63270 0x197d48000 + 111216
1 libsystem_pthread.dylib 0x0000000197e01224 0x197dfc000 + 21028
2 libsystem_c.dylib 0x0000000197cdab14 0x197c78000 + 404244
3 libc++abi.dylib 0x0000000196dad414 0x196dac000 + 5140
4 libc++abi.dylib 0x0000000196dccb88 0x196dac000 + 134024
5 libobjc.A.dylib 0x00000001975dc3bc 0x1975d4000 + 33724
6 libc++abi.dylib 0x0000000196dc9bb0 0x196dac000 + 121776
7 libc++abi.dylib 0x0000000196dc9738 0x196dac000 + 120632
8 libobjc.A.dylib 0x00000001975dc290 0x1975d4000 + 33424
9 CoreFoundation 0x0000000186d812a0 0x186d78000 + 37536
10 GraphicsServices 0x000000018ff0f5a0 0x18ff04000 + 46496
11 UIKit 0x000000018b6b2780 0x18b63c000 + 485248
12 CrashTest 0x000000010000c570 0x100004000 + 34160
13 libdyld.dylib 0x0000000197c4aa04 0x197c48000 + 10756
没有关系,其实和上面是一样的,我们来找找基址和偏移
往下找到 Binary Images段,这里显示的就是崩溃程序当时加载的所有库的快照
Binary Images:
0x100004000 - 0x10000ffff CrashTest arm64 <5fc8820b297631d087e5e665b261ed0c> /var/mobile/Containers/Bundle/Application/D8F09771-5B65-4403-A19C-CE77DAF32623/CrashTest.app/CrashTest // 这里第一行便是我们要找的
0x120070000 - 0x120097fff dyld arm64 /usr/lib/dyld
0x185678000 - 0x18580bfff AVFoundation arm64 <0c542593e3613f82b7e860cb5beeeed6> /System/Library/Frameworks/AVFoundation.framework/AVFoundation
0x18580c000 - 0x185870fff libAVFAudio.dylib arm64 /System/Library/Frameworks/AVFoundation.framework/libAVFAudio.dylib
0x1858b4000 - 0x1858b4fff Accelerate arm64 /System/Library/Frameworks/Accelerate.framework/Accelerate
0x1858cc000 - 0x185aebfff vImage arm64 /System/Library/Frameworks/Accelerate.framework/Frameworks/vImage.framework/vImage
0x185aec000 - 0x185b93fff libBLAS.dylib arm64 /System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/libBLAS.dylib
然后找到我们的应用 CrashTest
0x100004000 - 0x10000ffff CrashTest arm64 <5fc8820b297631d087e5e665b261ed0c> /var/mobile/Containers/Bundle/Application/D8F09771-5B65-4403-A19C-CE77DAF32623/CrashTest.app/CrashTest
我们看到行首有个地址区间 0x100004000 - 0x10000ffff &#xff0c; 这便是崩溃程序的内存区&#xff0c;起始地址&#xff08;基址&#xff09;为 0x100004000&#xff0c;OK基址找到了。
然后我们再看看崩溃时的栈
Last Exception Backtrace:
(0x186e9de48 0x1975dc0e4 0x186d83a54 0x10000c00c 0x10000bf7c 0x18b6810f8 0x18b66a22c 0x18b680a94 0x18b680720 0x18b679c74 0x18b64d38c 0x18b8ec1b4 0x18b64b8f4 0x186e560e8 0x186e5538c 0x186e5343c 0x186d811f4 0x18ff0f5a4 0x18b6b2784 0x10000c574 0x197c4aa08)
其中位于地址区间 0x100004000 - 0x10000ffff 的&#xff0c;有2个 0x10000c00c 0x10000bf7c&#xff0c;这就是崩溃程序的调用栈&#xff0c;其余地址为系统库函数调用栈。
OK开始动手
1. 下载符号恢复工具
修改权限
chmod a&#43;x restore-symbol
2. 恢复符号
我们拿到的崩溃日志来自arm64机器&#xff0c;所以先将二进制文件 CrashTest.app/CrashTest 瘦身 &#xff08;必须正确选择目标CPU架构类型&#xff0c;否则解析出来也是错的&#xff09;
lipo -thin arm64 CrashTest -output CrashTest-arm64
接着用工具恢复符号表
./restore-symbol -o CrashTest-symbol CrashTest-arm64
现在我们得到了一个恢复了符号表的二时制文件 CrashTest-symbol
3. 使用苹果自带命令行工具atos&#xff0c;将崩溃地址解析成具体函数
atos -arch arm64 -o CrashTest-symbol -l 0x100030000 0x100034340 0x1000342b0
# 简单解释一下这个命令&#xff0c;atos -arch CPU架构 -o 进制文件 -l 起始地址 ...一系列内存地址
# -l 后面跟的是模块的起始地址&#xff0c;再后面可以罗列很多地址&#xff0c;该命令会依次解析出具体函数
得到如下输出
-[ViewController getChild:] (in CrashTest-symbol) &#43; 64
-[ViewController crashOnFunc:] (in CrashTest-symbol) &#43; 44
至此&#xff0c;完成了解析。
本篇涉及的崩溃是普通函数中的崩溃&#xff0c;如果崩溃发生在block中&#xff0c;则需要借住反编译工具&#xff0c;请参考 恢复二进制文件中的block符号表
PS&#xff1a;再多说一点&#xff0c;上面的解析输出我们看到在函数后面有一个 &#43; 64&#xff0c; 这个&#43; 64、&#43; 44是什么意思呢&#xff0c;我开始也不太明白&#xff0c;仔细观察Hopper/IDA解析出来的的汇编代码&#xff0c;才明白原来这也是个偏移&#xff0c;是指调用地址相对于函数的起始地址的偏移&#xff0c;并非.m文件中的代码行数。
附件&#xff1a;
本篇使用的DEMO
参考 & 感谢
杨君的小黑屋 http://blog.imjun.net/posts/restore-symbol-of-iOS-app/