背景
有赞在基础保障平台的实践中完成了 Crash平台 的建设,但是 iOS 的崩溃日志未经符号化,排查问题比较困难。为了降低 iOS App 的 crash 率,快速排查线上 crash,疑难 crash 的跟踪处理,符号化崩溃日志显得尤为重要!
一、crash 日志的收集与分析
1.1 如何收集 crash 日志
1.手机上直接看,在隐私-分析与改进 -分析数据,可以找到所有崩溃日志,未符号化。
2.连接电脑,通过“音乐”同步到本地 ~/Library/Logs/CrashReporter/MobileDevice/xxx 的 iPhone. 缺点:日志没有符号化,需要自己手动符号化
3.连接电脑,打开 Xcode-window-Diveces and Simulators。
Xcode 会尝试在本地查找符号表文件,自动符号化。
以上 3 种方法都局限于拿得到设备的情况。
4.查看别人手机上的 crash 日志 Xcode-Window-Organizer。
这种方式找符号表会有 2 种途径
上传 AppStore 的时候会让你勾选上传符号表「Include App symbols for your Application…」,如果上传了,苹果自动帮你在云端做解析。
如果没有上传,Xcode 尝试在本地找符号表文件进行符号化。
缺点:这种方式也只能收集在手机设置中打开了上传 crash 开关,以及 TestFlight 用户的 crash 日志。企业分发或 AdHoc 安装,需要自行获取崩溃日志。信息不全,线程信息不够。
5.自己收集 crash 日志,比如接入 KSCrash、plcrashreporter 等,但是要自己做符号化。
1.2 crash 日志的结构
日志可以分成 4 个部分,基本信息,崩溃的原因,所有线程调用,Binary Images (二进制文件列表)。
1.2.1 基本信息
1.2.2 崩溃原因
线程
Binary Images
二、如何进行 crash 日志符号化
crash 日志符号化通常是通过 atos
和 symbolicatecrash
这两个工具来完成。
2.1 atos
atos
是苹果提供的符号化工具,在 Mac OS 系统下默认安装,他的缺点是只能一个地址一个地址逐个翻译。我们看下这个工具的使用说明:
使用方法:
需要传入这几个信息:arch 架构、dSYM 路径、binary image 载入内存的初始地址、崩溃的地址。
参数内容可以从 crash 日志中取得,如下图所示:
example
2.2 symbolicatecrash
symbolicatecrash
是 Xcode
自带的一个程序,他是对 atos
的封装,可以翻译整个 crash 文件,有赞就是选择这个工具来进行 crash
符号化的。
具体的路径可以通过以下命令搜索出来:
使用方法:
下文会对此工具做一个详细的原理分析。
三、symbolicatecrash 符号化原理分析
通过网上找的教程来看,一般是把对应版本的 crash 日志,dSYM 文件,App 文件都放进一个目录,然后执行一下命令来进行符号化:
但是我有几个疑问:
如果 App 打包出来多个 dSYM 怎么办?
发现把目录中的 App 文件删了,dSYM 删了(源文件还在),执行命令的时候也没传他们,竟然也可以符号化,这怎么做到的?
怎么样知道 crash 日志,dSYM,App 是正确的,可以正确做符号化,如果发现某个 crash 日志没有被正确符号化,怎么查这个问题?
把 dSYM 丢了,相同代码再去编译一次把 dSYM 拿出来可以用吗?
我们执行完后发现系统库也都符号化了,系统的 dSYM 在哪里,难道已经包含在 App 的 dSYM 中吗?
崩溃日志最下面的 Binary Images 是干嘛的?
针对以上这些问题,我们来做下源码分析一探究竟。
3.1 symbolicatecrash 源码分析
官方没有开源,但是网上有类似的实现,是用 perl 实现的一个脚本。
首先,一个基本原则是需要确保你的电脑上有每个 image
对应的 uuid
的符号表文件,这样 crash 文件才能被正确解析和符号化出来。
然后我们看下符号化一个 crash 文件的流程:
3.1.1 解析所有的 Binary Image
把每一个 Binary Image 都存储为以上形式的对象。
Binary Image 的作用是建立 UIKitCore 与 uuid 的关系,当需要符号化一个 UIKitCore 的地址时,会找到对应的 uuid,并从文件系统中查找到这个符号表。这也解释了上面第 6 个问题。
3.1.2 解析所有线程
把所有堆栈存储为以上形式的对象。
3.1.3 翻译 Last Exception Backtrace
这里为什么可以翻译,因为第一步已经把所有 Binary Image 存储起来,上面的每一个地址,都可以找到对应的 Binary Image,从而获得 Binary Image 的名称,基地址,以及偏移量。
3.1.4 删除不需要的 image
因为 crash 日志把 App 用到的所有 Binary Image 都列举出来了,而崩溃堆栈中只用到了一小部分,所以这里把没有用到的 Binary Image 删除。后续要遍历所有 images,去找到每个二进制对应的 dSYM,这样做提高了效率。
3.1.5 查找 Binary Image 的符号表
符号表的类型
App 编译出来的 dSYM ( 一般输入命令时指定在哪里,如果没有会自动去查找)
系统库的符号表 (自动查找),这也解释了第五个问题,系统符号表和 APP 符号表是分开的。在 ~/Library/Developer/Xcode/iOS DeviceSupport/os/Symbols 这个路径再拼上 image 中的 path,就是完整路径 比如 ~/Library/Developer/Xcode/iOS DeviceSupport/os/Symbols/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
从 search path 中找 (包括命令行输入的几个目录 和 系统符号表所在目录)
mdfind 搜索 uuid 相同的符号表,这就解释了上面第 1 个和第 2 个问题,会使用 uuid 去查找,所以命令行中不传也没关系。
如果还没找到 返回空 并删除这个 image,与这个 image 相关的都不能被符号化
判断匹配的条件
lipo -info 判断架构是否一致
otool 命令打出来 macho 信息,找到 uuid 并 判断是否一致,这解答了上面第 3 个和第 4 个问题,只有 uuid 相同,才可以被符号化出来。相同代码重新打一个包出来也不能符号化,因为 uuid 不同。
3.1.6 执行 atos 进行符号化
遍历所有线程
取到每一条的 bundle 还有地址 在 images 中找到符号表路径
执行命令 并记录符号化后的内容
3.1.7 字符串替换 生成最终的报告
逐行开始替换
比如将’0x00000001044dcfc0 0x104058000 + 4739008’替换为’CPPExceptionTerminate() (SentryCrashMonitor_CPPException.cpp:179)’
四、有赞符号化方案
通过上面的原理分析,我们基本掌握了 crash
符号化的步骤,下面介绍下我们有赞是如何做符号化的。
4.1 dSYM 符号表保存
首先,进行符号化必不可少的一个文件就是 dSYM
符号表,我们需要保存每次正式发布的 App 版本对应的符号表文件。如下图所示:
打包机(gitlab runner):有赞目前有自己的持续构建平台
MBD
,业务方在MBD
上发起打包构建任务后系统会根据算法分配到不同的打包机上。更多关于有赞移动CI/CD
我们在之前做过一次技术沙龙,详细内容见这里。项目打包完成后会执行一个保存符号表的脚本,会保存符号表到本地,并且上传到云端做备份。备份完成后调用 MBD 接口,上报符号表 uuid,bundleId,版本号,build 号,打包机唯一标识。
由于有多台打包机导致每次打包产出的符号表分布在不同的打包机上,我们需要建立 dSYM 文件与打包机的关系。第一步中的保存符号表脚本会上报信息到 MBD,MBD 把 dSYM 符号表 uuid 和打包机唯一标识做一个映射关系。
当发生一个 crash 时,crash 日志中包含符号表 uuid,通过 uuid 查表,就能定位到执行构建的打包机。
4.2 crash 上报
dSYM 符号表已经保存下来了,接下来就是 crash 的上报和解析,crash 上报大致流程见下图:
crash 信息通过 SDK 上报到埋点平台,我们通过 Flink 监听到 crash 信息的上报,并把它写入数据库。
Flink 是实时计算平台提供的用来实时消费上报的数据的程序,支持大并发量的数据。
更多关于 crash 平台的建设我们近期也发表过一篇文章,详情见 这里。
4.3 crash 文件符号化
步骤二中已经上报了 crash 信息并展示在了我们的内部平台中,接下来我们需要对此 crash 文件结合对应的 dSYM 进行符号化解析,具体流程如下:
在 Crash 前端页面,点击符号化按钮会发起 MBD 的一次符号化构建,并将 crash 的信息传递给 MBD。
MBD 把 crash 的 uuid 拿出来,根据 uuid 去查 dSYM 文件所在的 打包机,并把任务给到这个打包机。
打包机运行脚本,这个脚本的作用是使用 symbolicatecrash 程序符号化 crash 日志,并把符号化后的结果通知到 MBD。
MBD 把符号化结果写入数据库,并通知 Crash 后端。
Crash 前端页面收到通知后刷新页面,展示符号化后的结果。
至此,我们完成了 crash 文件的符号化解析工作,但是使用过程中暴露出了一些问题:
目前每次打包都会产生 dSYM 文件并直接保存在打包机上,MBD 每天的打包任务有很多,导致占用空间浪费资源。我们计划只维护符号表的 cdn 链接,用到时再去下载符号表。
这种方案下线一台打包机后,会造成一部分 crash 日志无法符号化,目前我们正在优化,计划统一把符号表放到一台打包机上,这样就能解决这个问题。
系统符号表的维护也是一个问题,我们需要在每台打包机上都要加上系统符号表,而且每次苹果发布新版都需要拿新的系统符号表过来,维护起来挺麻烦的。目前的解决方案是人工放到打包机上。
总结
至此,我们了解了如何收集 crash 日志,明白了 crash 日志中每个部分的意思,符号化的工具,以及如何对 crash 日志进行符号化。已经可以解答出来上面提出的问题,对符号化的原理有了非常清晰的认识。
我们的符号化方案对于有赞多台打包机环境而言,非常合适,下线一台或者新增一台打包机,可以无缝支持。另外,整套方案非常轻量,能够快速集成符号化功能,符号化链路清晰。
Crash 平台拥有符号化 crash 日志的能力后,极大的提高了大家排查、解决线上问题的效率,提升了 App 的稳定性。
本文转载自公众号有赞 coder(ID:youzan_coder)。
原文链接:
评论 1 条评论