有赞在基础保障平台的实践中完成了 Crash平台 的建设,但是iOS的崩溃日志未经符号化,排查问题比较困难。为了降低iOS App的crash率,快速排查线上crash,疑难crash的跟踪处理,符号化崩溃日志显得尤为重要!
1.手机上直接看,在隐私-分析与改进 -分析数据,可以找到所有崩溃日志,未符号化。
2.连接电脑,通过“音乐”同步到本地 ~/Library/Logs/CrashReporter/MobileDevice/xxx的 iPhone. 缺点:日志没有符号化,需要自己手动符号化
3.连接电脑,打开Xcode-window-Diveces and Simulators。
Xcode会尝试在本地查找符号表文件,自动符号化。
以上3种方法都局限于拿得到设备的情况。
4.查看别人手机上的crash日志 Xcode-Window-Organizer。
这种方式找符号表会有2种途径
缺点:这种方式也只能收集在手机设置中打开了上传crash开关,以及TestFlight用户的crash日志。企业分发或 AdHoc 安装,需要自行获取崩溃日志。信息不全,线程信息不够。
5.自己收集crash日志,比如接入KSCrash、plcrashreporter等,但是要自己做符号化。
日志可以分成4个部分,基本信息,崩溃的原因,所有线程调用,Binary Images (二进制文件列表)。
crash日志符号化通常是通过 atos
和 symbolicatecrash
这两个工具来完成。
atos
是苹果提供的符号化工具,在Mac OS系统下默认安装,他的缺点是只能一个地址一个地址逐个翻译。我们看下这个工具的使用说明:
使用方法:
atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>
需要传入这几个信息:arch 架构、dSYM路径、binary image 载入内存的初始地址、崩溃的地址。
参数内容可以从crash日志中取得,如下图所示:
example
$ atos -arch arm64 -o TheElements.App.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]
symbolicatecrash
是 Xcode
自带的一个程序,他是对 atos
的封装,可以翻译整个crash文件,有赞就是选择这个工具来进行 crash
符号化的。
具体的路径可以通过以下命令搜索出来:
find /Applications/Xcode.App -name symbolicatecrash -type f
使用方法:
export DEVELOPER_DIR="/Applications/Xcode.App/Contents/Developer"
<path of symbolicatecrash>/symbolicatecrash <Path to dSYM file crash log>
例子:
symbolicatecrash log.crash > result.log
// dSYM可以跟多个
symbolicatecrash log.crash -d TheElement.App.dSYM >result.log
下文会对此工具做一个详细的原理分析。
通过网上找的教程来看,一般是把对应版本的crash日志,dSYM文件,App文件都放进一个目录,然后执行一下命令来进行符号化:
symbolicatecrash log.crash -d TheElement.App.dSYM >result.log
但是我有几个疑问:
针对以上这些问题,我们来做下源码分析一探究竟。
官方没有开源,但是网上有类似的实现,是用perl实现的一个脚本。
首先,一个基本原则是需要确保你的电脑上有每个 image
对应的 uuid
的符号表文件,这样crash文件才能被正确解析和符号化出来。
然后我们看下符号化一个crash文件的流程:
这是crash日志中的Binary Image格式
0x1cd997000 - 0x1cea7bfff UIKitCore arm64 <40a93e939f8635c1905c7b947c7c2305> /System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
转换为如下格式
'UIKitCore' =>
{
'extent' => '0x1cea7bfff',
'plus' => '',
'bundlename' => 'UIKitCore',
'uuid' => '40a93e939f8635c1905c7b947c7c2305',
'base' => '0x1cd997000',
'path' => '/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore',
'arch' => 'arm64',
'nextID' => ''
}
把每一个Binary Image都存储为以上形式的对象。
Binary Image的作用是建立UIKitCore与uuid的关系,当需要符号化一个UIKitCore的地址时,会找到对应的uuid,并从文件系统中查找到这个符号表。这也解释了上面第6个问题。
8 TheElement 0x00000001044dcfc0 0x104058000 + 4739008
转换为如下格式
'0x00000001044dcfc0 0x104058000 + 4739008' =>
{
'raw_address' => '0x00000001044dcfc0',
'bundle' => 'TheElement',
'address' => '0x00000001044dcfc0'
}
把所有堆栈存储为以上形式的对象。
这是crash日志中的Last Exception Backtrace
Last Exception Backtrace:
(0x1a1a9127c 0x1a0c6b9f8 0x1a19adab8 0x1a1a96ac4 0x1a1a9875c 0x10566d498 0x10423ab84 0x1ce255040 0x1cdcfe1c8 0x1cdcfe4e8 0x1cdcfd554 0x1ce28c304 0x1ce28d52c 0x1ce26d59c 0x10437fd20 0x1ce333714 0x1ce335e40 0x1ce32f070 0x1a1a23018 0x1a1a22f98 0x1a1a22880 0x1a1a1d7bc 0x1a1a1d0b0 0x1a3c1d79c 0x1ce253978 0x104283158 0x1a14e28e0)
翻译为:
0 libsystem_kernel.dylib 0x00000001a162e0dc 0x1a160b000 + 143580
1 libsystem_pthread.dylib 0x00000001a16a7094 0x1a16a5000 + 8340
2 libsystem_c.dylib 0x00000001a1587f4c 0x1a152d000 + 372556
3 libsystem_c.dylib 0x00000001a1587eb4 0x1a152d000 + 372404
4 libc++abi.dylib 0x00000001a0c54788 0x1a0c53000 + 6024
5 libc++abi.dylib 0x00000001a0c54934 0x1a0c53000 + 6452
6 libobjc.A.dylib 0x00000001a0c6be00 0x1a0c66000 + 24064
7 TheElement 0x0000000104babb18 0x104058000 + 11877144
8 TheElement 0x00000001044dcfc0 0x104058000 + 4739008
9 libc++abi.dylib 0x00000001a0c60838 0x1a0c53000 + 55352
10 libc++abi.dylib 0x00000001a0c60434 0x1a0c53000 + 54324
11 libobjc.A.dylib 0x00000001a0c6bbc8 0x1a0c66000 + 23496
12 CoreFoundation 0x00000001a1a1d11c 0x1a1979000 + 672028
13 GraphicsServices 0x00000001a3c1d79c 0x1a3c13000 + 42908
14 UIKitCore 0x00000001ce253978 0x1cd997000 + 9161080
15 TheElement 0x0000000104283158 0x104058000 + 2273624
16 libdyld.dylib 0x00000001a14e28e0 0x1a14e1000 + 6368
这里为什么可以翻译,因为第一步已经把所有Binary Image存储起来,上面的每一个地址,都可以找到对应的Binary Image,从而获得Binary Image的名称,基地址,以及偏移量。
因为crash日志把App用到的所有Binary Image都列举出来了,而崩溃堆栈中只用到了一小部分,所以这里把没有用到的Binary Image删除。后续要遍历所有images,去找到每个二进制对应的dSYM,这样做提高了效率。
'0x00000001044dcfc0 0x104058000 + 4739008' =>
{
'symbolled' => 'CPPExceptionTerminate() (SentryCrashMonitor_CPPException.cpp:179)',
'raw_address' => '0x00000001044dcfc0',
'bundle' => 'TheElement',
'address' => '0x00000001044dcfc0'
}
逐行开始替换
比如将’0x00000001044dcfc0 0x104058000 + 4739008’替换为’CPPExceptionTerminate() (SentryCrashMonitor_CPPException.cpp:179)’
通过上面的原理分析,我们基本掌握了 crash
符号化的步骤,下面介绍下我们有赞是如何做符号化的。
首先,进行符号化必不可少的一个文件就是 dSYM
符号表,我们需要保存每次正式发布的App版本对应的符号表文件。如下图所示:
MBD
,业务方在 MBD
上发起打包构建任务后系统会根据算法分配到不同的打包机上。更多关于有赞移动 CI/CD
我们在之前做过一次技术沙龙,详细内容见这里。dSYM符号表已经保存下来了,接下来就是crash的上报和解析,crash上报大致流程见下图:
更多关于crash平台的建设我们近期也发表过一篇文章,详情见 这里。
步骤二中已经上报了crash信息并展示在了我们的内部平台中,接下来我们需要对此crash文件结合对应的dSYM进行符号化解析,具体流程如下:
至此,我们完成了crash文件的符号化解析工作,但是使用过程中暴露出了一些问题:
至此,我们了解了如何收集crash日志,明白了crash日志中每个部分的意思,符号化的工具,以及如何对crash日志进行符号化。已经可以解答出来上面提出的问题,对符号化的原理有了非常清晰的认识。
我们的符号化方案对于有赞多台打包机环境而言,非常合适,下线一台或者新增一台打包机,可以无缝支持。另外,整套方案非常轻量,能够快速集成符号化功能,符号化链路清晰。
Crash平台拥有符号化crash日志的能力后,极大的提高了大家排查、解决线上问题的效率,提升了App的稳定性。
本文转载自公众号有赞coder(ID:youzan_coder)。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货