一、为什么要符号化?
对应线上app
闪退日志,闪退的堆栈都是以下格式
这种信息开发肯定无法找到具体的报错的地方的,本文就是这将这些转成下面这种可读的形式,方便查找问题的
二、获取DSYM
文件
2.1 为什么不能符号化
不是发包的开发的电脑是无法符号化崩溃信息的原因是没有一个叫.DSYM
的文件,这是个Macho
格式的文件,打release
包的时候会生成xxx.xcarchive
文件,文件内容如下
1 | . |
而函数的地址和函数名称的对应关系就保存在xxx.app.dSYM
中,而这个文件只要发包的电脑才有。
2.2 debug环境如何获取dsym
发release
版本的时候会生成xxx.app.dSYM
,但是debug
则没有生成,这完全是因为xcode
的配置的原因,若想debug
版本也会生成xxx.app.dSYM
文件,按下图做就可以了。
运行后~/Library/Developer/Xcode/DerivedData/Dsym_demo-cydxdahtbniagjaawvdbciptpkpb/Build/Products/Debug-iphoneos
,会出现
1 | ├── Dsym_demo.app |
2.3 从ipa
包内提取出dsym
从已有的可执行文件中提取DSYM
1 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil ~/Dsym_demo.app/Dsym_demo -o manual.DSYM |
三、手动分析奔溃.crash文件
3.1 认识.crash文件
将手机上的设置
-> 隐私
-> 分析
中的.ips
文件改为.crash
即可,文件内容如下
1 | {"app_name":"Dsym_demo","timestamp":"2019-01-16 18:07:48.82 +0800","app_version":"1.0","slice_uuid":"e0cffa85-f688-305e-b85d-4ed9a320f8c7","adam_id":0,"build_version":"1","bundleID":"com.dianrong.Dsym-demo2","share_with_app_devs":true,"is_first_party":false,"bug_type":"109","os_version":"iPhone OS 12.0.1 (16A404)","incident_id":"6FE3A02C-A9D0-433C-B855-741684067E51","name":"Dsym_demo"} |
很多字段都是见面知意的,介绍下几个重要的
Incident Identifier
: 对奔溃文件的唯一key值;CrashReporter Key
: 设备号的唯一值,同一个是手机的所有.ips
文件中的这个值是相同的;Identifier
:bundleID
;Thread xx name: xxx
: 奔溃线程的编号和名称;Thread 0 crashed with ARM Thread State (64-bit)
: 当时的寄存器的值;Binary Images
: 模块地址
单独解释下这个0x100fd8000 - 0x100fdffff Dsym_demo arm64 <e0cffa85f688305eb85d4ed9a320f8c7> /var/containers/Bundle/Application/112179B6-F1BB-4C6A-9097-7C39AD59A84C/Dsym_demo.app/Dsym_demo
0x100fd8000 - 0x100fdffff
: 这个是Dsym_demo
ASLR
后的开始和结束地址,通过该地址可以计算出函数在安装包中的地址;Dsym_demo
: 应用的名称arm64
: 应用的架构e0cffa85f688305eb85d4ed9a320f8c7
:uuid
的值,这个用来和dysm
一一对应;/var/containers/Bundle/Application/112179B6-F1BB-4C6A-9097-7C39AD59A84C/Dsym_demo.app/Dsym_demo
:应用的安装路径。
3.2 如何保证dsym
和crash
文件的一致性
dsym
和crash
是通过UUID
来保持一致的,在3.1
中已经介绍了如何查看uuid
,下面介绍下如何在dsym
中获取UUID
的值
otool
1 | ➜ Crash otool -l Dsym_demo.app.DSYM/Contents/Resources/DWARF/Dsym_demo | grep uuid |
dwarfdump
1 | ➜ Crash dwarfdump --uuid Dsym_demo.app.DSYM |
MachOView
只要文件crash
和dsym
的uuid
相等2者就是一直的。
3.3 获取崩溃信息在二进制包中的地址
现在已经找到了crash
和dsym
了,下面来分析下如何通过地址来找到具体的崩溃点
拿下面这个Dsym_demo
的0x0000000100fde814
地址为例,看看到底是哪个函数的地址
1 | 19 UIKitCore 0x0000000211f04bc8 0x211621000 + 9321416 |
先介绍下每列的含义
- 第一列:是序号
- 第二列:是实际运行的时候崩溃的地址
- 第三列+第四列的值就是第二列
我之前的写的介绍ASLR
的文章中有介绍,对于arm64
结构如果没有ASLR
的话开始地址是0x100000000
,而从3.1
的介绍中可以知道,运行内存的开始地址是0x100fd8000
,所以偏移了0xFD8000 = 0x100fd8000 - 0x100000000
;
所以0x0000000100fde814
在二进制包中的地址为0x100006814 = 0x0000000100fde814 - 0xFD8000
;
3.4 通过崩溃地址在二进制包中找到对应的函数
使用hopper
来查看dsym
,搜索我们3.3
计算出的地址0x100006814
这个是在方法main
下面的
所以找到了0x0000000100fde814
是崩溃在main
函数里面的。
3.5 简单介绍下DSYM
文件
Symbols
存放着方法的存在哪里,开始地址是什么,比如-[ViewController viewDidLoad]
方法就是存放在_TEXT
segment中的__test
section中,开始地址为0x00000001000066A4
,偏移量为0
,而-[ViewController testFunc]
的偏移量为104
,也就是0x00000001000066A4 + 0x68(104) = 0x10000670C
,所以-[ViewController testFunc]
的开始地址为0x10000670C
,而-[ViewController viewDidLoad]
地址范围就是0~0x10000670b
section64(_DWARF,__debug_line)
存放在具体的行号信息,DWARF
是一种存储格式,行号就是以这种形式存储的。
要想读取出具体的行号信息有以下方式
dwarfdump
1 | ➜ Crash dwarfdump --arch arm64 Dsym_demo.app.DSYM --lookup 0x100006814 |
atos
1 | ➜ Crash atos -o Dsym_demo.app.DSYM/Contents/Resources/DWARF/Dsym_demo -arch arm64 0x100006814 // 这个地址是真实的在二进制安装包中的地址 |
注意:上面的arm64
需要自己选择对应的架构
四、符号化DSYM
手段
上面分析了如何分析一个DSYM
,知道不管是命令还是图形工具背后的原理,其实只要算出地址和行数就搞定了,自己都可以写脚本或者工具了,下面就开始介绍轮子了。
将之前的crash
和.dsym
文件放到任意文件夹下
1 | . |
方式一 symbolicatecrash
symbolicatecrash
这个命令是xcode
安装包内的,先看下这个工具的放在哪的
1 | ➜ ~ find /Applications/Xcode.app -name 'symbolicatecrash' |
有很多个,我这里用的是/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
复制到Crash
文件夹下
1 | cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash ~/Desktop/Crash |
使用
1 | ➜ Crash ./symbolicatecrash Dsym_demo-2019-01-16-180748.ips Dsym_demo.app.DSYM > tmp.crash |
甚至还可以这样
1 | ➜ Crash ./symbolicatecrash Dsym_demo-2019-01-16-180748.ips > tmp.crash |
因为symbolicatecrash
实际是用uuid
做索引来查找.dsym
文件,只要spotlight
能搜到对应uuid
的.dsym
文件就会拿来解析
spotline
的命令版本就是mdfind
1 | ➜ Crash mdfind e0cffa85f688305eb85d4ed9a320f8c7 | grep DSYM |
还是蛮好用的
方式二 dwarfdump
分两步,获取奔溃的方法是哪个
1 | dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF > info-e.txt |
这里的内容完全可以描述一个函数
1 | 0x0004005f: function [122] * |
这里可以定位到具体的方式名称testFunc
,接下来要找出崩溃发生在改方法的那一行
1 | dwarfdump -e --debug-line Dsym_demo.app.DSYM > line-e.txt |
1 | 0x00000001000066a4 17 /Users/fangshufeng/Desktop/demo_project/Dsym_demo/Dsym_demo/ViewController.m |
这样就可以找到行数
当然你也可以一步到位上面3.5
介绍过了。
看来.dsym
中包含的信息可真多啊,这个和symbolicatecrash
比起来对于符号化来说感觉差了些,但是如果是分析的话还是很好用的。
方式三 atos
这个在3.5
也介绍过了,好处是可以不用手动计算地址
就符号化而言个人最喜欢的还是symbolicatecrash
方式四、Xcode
自动符号化
脚本的话一般是做一些自动化的时候用的,最简单的还是xcode
的图像工具,没有.dsym
的可以让别人发给你就好了。