在iOS开发和运维过程中,时常会遇到应用程序崩溃的问题。面对这个问题,单靠NSLog日志作用不大,我们往往需要其他的方式来分析和定位崩溃问题,例如Crash日志。而获取应用程序的Crash日志以及符号表就变成了尤为地重要。
那如何获取Crash日志?又怎么结合符号表呢?下面分别进行说明。
一、如何获取Crash日志
对于Crash日志来说,一般分为两类:
- 苹果系统收集的Crash日志。
- 应用程序收集的Crash日志。
苹果系统收集的Crash日志
在收集苹果系统的Crash日志之前,我们需要在手机里面设置Crash日志生成方式,具体就是在手机设置->隐私->分析与改进里,打开“共享iPhone分析”和“与App开发者共享”。
获取苹果系统的Crash日志,一般有以下几种:
方式一:
将手机连接Mac电脑,在Xcode -> Window -> Organizer -> Crashes 里查找文件名带有App名称的Crash日志。
方式二:
将手机连接Mac电脑,在Xcode->Window->Devices and Simulators->View Device Logs里查找文件名带有App名称的Crash日志。
方式三:
从手机“设置->隐私->分析与改进->分析数据”中,查找文件名带有App名称的Crash日志。
应用程序收集的Crash日志
应用收集Crash日志的方式又细分为如下两种:
1.使用第三方开源库收集
例如,集成PLCrashReporter、KSCrash等客户端SDK进行收集,然后上报到后台。收集到的Crash日志在后台已经被符号化。
2.接入APM产品
APM,即应用性能管理(Application Performance Management & Monitoring),市面上比较流行的如Bugly、EMAS、mPaas、phabricator之类的产品,我们集成它们,并且在它的门户网站上查看Crash日志,分析和确定崩溃问题。
当然,不管使用第三方开源库或接入APM产品,好处就是:不需要符号化,Crash日志已经显示调用的类名、方法名。
而对于苹果系统收集的Crash日志,就显示一堆十六进制内存地址,这样就可读性差了,并且未符号化或者部分符号化的崩溃日志对闪退问题的解决几乎毫无帮助。那么,如何符号化苹果系统收集的Crash日志?
要想符号化,就需要用到符号表。什么是符号表?所谓符号表,就是包含函数地址和对应的类、方法(文件、行号)的映射关系。
二、如何获取符号表
在iOS平台中,dSYM文件是保存符号表的目标文件,文件名通常为:xxx.app.dSYM。要想在构建版本的时候生成对应的dSYM文件,需要在Xcode项目的工程文件里作如下设置:
(1)Build Settings -> Build Options -> Debug Information Format = DWARF with dSYM
(2)XCode -> Build Settings -> Apple Clang - Code Generation -> Generate Debug Symbols -> Yes
通过以上两步后,若是本地运行工程,则在项目工程里面的 Products 文件的 xxx.app 文件里面可以找到 xxx.app.dSYM 文件。
若是发布版本,则在XCode -> Window -> Organizer -> 找到打包好的文件(Show in Finder)-> 选中文件(右键显示包内容)-> dsYMs文件夹下就是了。
这里需要注意的是:
无论是否修改代码或者配置项,每次重新编译运行项目,都会生成新的xxx.app.dSYM,其生成的UUID都会不同。
其次如果App开启了Bitcode,需要发布到AppStore,则需要从网上下载AppStore生成的xxx.app.dSYM,而不是本地生成的xxx.app.dSYM。因为AppSotre会根据BitCode,重新编译App生成真正的xxx.app.dSYM。
那么,如何确定Crash日志、App版本包、dSYM是否匹配呢?如果上述三者中的UUID一致,则三者是匹配的。
三、判定Crash日志、App、符号表三者UUID是否一致
首先,读取Crash日志的UUID。在Crash日志里,搜索“Binary Images”,紧接着的下一行第一列是地址值,第二列是镜像文件名,第三列为运行镜像的CPU架构(通常手机上是arm64),再下一列用尖括号包裹的即是镜像的UUID。
其次,读取App的UUID。把App包的后缀名ipa改为zip,并解压,得到后缀名为app的二进制文件。使用命令
dwarfdump -uuid xxx.app/xxx
查看App所包含的CPU架构的UUID,找到对应CPU架构的UUID。
最后,读取dSYM文件的UUID,方法与读取App的UUID类似。使用命令xxx.app.dSYM查看dSYM所包含的CPU架构的UUID,找到对应CPU架构的UUID。
如果以上三者的UUID一致,那么就可以使用该dSYM文件将Crash日志符号化。
四、如何符号化Crash日志
这里只介绍对苹果系统收集到的Crash日志符号化。通常用到如下几种方法
1.使用symbolicatecrash
symbolicatecrash是Xcode提供的一个符号化Crash日志的命令行工具,该工具通常在Mac机的如下目录
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
符号化时,将后缀名为ips的Crash日志改为.crash后缀,拷贝Crash日志、symbolicatecrash、app、dSYM到同一个目录,执行命令:
./symbolicatecrash crashlog.crash xxx.app.dSYM xxx.app > crashsymbol.crash
执行命令时,如果出现错误,
Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash
则需要通过如下命令,设置DEVELOPER_DIR:
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
然后再执行symbolicatecrash命令即可。
使用symbolicatecrash工具符号化,在iOS14及以下系统收集的Crash日志尚可,但是对于iOS 15及以上系统收集的Crash日志,symbolicatecrash工具就无能为力了。
iOS 15及以上系统收集的Crash日志,内容格式有调整,不再是之前的行式表示,而是以json格式记录。这时需要用到Xcode13里的
CrashSymbolicator.py脚本来解析。
将要符号化的Crash日志,dSYM文件都拷贝到如下目录
/Applications/Xcode.app/Contents/SharedFrameworks/CoreSymbolicationDT.framework/Versions/A/Resources
然后,再执行如下命令
python3 /Applications/Xcode.app/Contents/SharedFrameworks/CoreSymbolicationDT.framework/Versions/A/Resources/CrashSymbolicator.py xxx.ips -d xxx.dSYM -o
最后,得到解析后的日志地址
这里需要注意,解析之后的结果也是json格式的,跟iOS 14及以下系统解析出来的结果格式不一样,读起来不是很直观,但还是有规律和技巧快速提取定位问题的有效信息。限于篇幅,这里不做详细介绍,如果读者感兴趣可以关注我,后续有机会将详细介绍。当然了,如果使用Mac OS 13系统的读者,可以用系统默认的控制台打开解析之后的结果,系统控制台会自动将其格式化成传统的行式结构,可读性好很多。
2.使用atos
通常来说,第一种方法不能符号化,那问题就变得难解了,使用atos工具只能是殊死一搏,且在实践中发现,往往符号化的结果不是那么靠谱,但还是给大家介绍一下命令格式,
atos -arch [Binary Architecture] [Binary Image dSYM] -l [Load Address] [Address (to symbolicate)]
// Load Address为Crash日志里,
// “Binary Images”关键字的下一行第一个地址,
// Address (to symbolicate)为堆栈中需要符号化的地址。
当然了,如果大家对dSYM文件理解得更透彻,可以尝试使用如下命令,根据计算出来的dSYM地址,符号化对应的堆栈地址
dwarfdump -arch arm64 xxx.app.dSYM --lookup xxx地址
符号化Crash日志的方法,今天就介绍到这。更多更深入的原理,欢迎大家交流探讨。