iOS Crash日志分析必备:符号化系统库方法

4,735 阅读17分钟

如果你有过分析iOS崩溃日志的经验,一定经常看到日志里出现很多<redacted>的字段。这篇文章就是帮助开发者将这些字段符号化为对应的系统库方法名。

如果你已经掌握了这方面的知识,就直接去这里iOS-System-Symbols,下载我整理好的系统库符号文件吧。

符号化的作用

当获取到app的crash日志时,第一步就是将其符号化。作用是把日志堆栈中的方法调用显示出来,对于来自app内部的方法,还能直接给出方法对应.m文件的所在行。

符号化前(这里项目的Build Settings里的Strip Style设为了Debugging Symbols,所以这里能直接看到MyApp的方法名。更多和symbol相关的设置,请看这里):

Thread 7:
0   libsystem_kernel.dylib          0x000000018efb416c 0x18efb3000 + 4460 (mach_msg_trap + 8)
1   libsystem_kernel.dylib          0x000000018efb3fdc 0x18efb3000 + 4060 (mach_msg + 72)
2   MyApp                           0x000000010091e558 0x1000bc000 + 8791384 (ksmachexc_i_handleExceptions + 148)
3   libsystem_pthread.dylib         0x000000018f097860 0x18f094000 + 14432 (<redacted> + 240)
4   libsystem_pthread.dylib         0x000000018f097770 0x18f094000 + 14192 (_pthread_start + 284)

符号化后:

Thread 7:
0   libsystem_kernel.dylib          0x000000018efb416c mach_msg_trap + 8
1   libsystem_kernel.dylib          0x000000018efb3fdc mach_msg + 72
2   MyApp                           0x000000010091e558 ksmachexc_i_handleExceptions (KSCrashSentry_MachException.c:233)
3   libsystem_pthread.dylib         0x000000018f097860 _pthread_body + 240
4   libsystem_pthread.dylib         0x000000018f097770 _pthread_body + 0

可以发现,frame 3里libsystem_pthread.dylib<redacted>变成了_pthread_body,frame 2里MyAppksmachexc_i_handleExceptions变成了更为完整的ksmachexc_i_handleExceptions (KSCrashSentry_MachException.c:233),直接给出了方法及其所在文件和行数。

详细的符号化方法,请参考符号化iOS Crash文件的3种方法。这里就不重复了。

需要注意的是,很多时候,crash日志里并不会有MyApp的调用,而全都是系统库的调用:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x000000018b816f30 0x18b7fc000 + 110384 (objc_msgSend + 16)
1   UIKit                           0x0000000192e0a79c 0x192c05000 + 2119580 (<redacted> + 72)
2   UIKit                           0x0000000192c4db48 0x192c05000 + 297800 (<redacted> + 312)
3   UIKit                           0x0000000192c4d988 0x192c05000 + 297352 (<redacted> + 160)
4   QuartzCore                      0x00000001900d6404 0x18ffc5000 + 1119236 (<redacted> + 260)
5   libdispatch.dylib               0x000000018bc551c0 0x18bc54000 + 4544 (<redacted> + 16)
6   libdispatch.dylib               0x000000018bc59d6c 0x18bc54000 + 23916 (_dispatch_main_queue_callback_4CF + 1000)
7   CoreFoundation                  0x000000018cd79f2c 0x18cc9d000 + 905004 (<redacted> + 12)
8   CoreFoundation                  0x000000018cd77b18 0x18cc9d000 + 895768 (<redacted> + 1660)
9   CoreFoundation                  0x000000018cca6048 0x18cc9d000 + 36936 (CFRunLoopRunSpecific + 444)
10  GraphicsServices                0x000000018e729198 0x18e71d000 + 49560 (GSEventRunModal + 180)
11  UIKit                           0x0000000192c80628 0x192c05000 + 505384 (<redacted> + 684)
12  UIKit                           0x0000000192c7b360 0x192c05000 + 484192 (UIApplicationMain + 208)
13  MyApp                           0x0000000100016e54 0x100004000 + 77396 (_mh_execute_header + 77396)
14  libdyld.dylib                   0x000000018bc885b8 0x18bc84000 + 17848 (<redacted> + 4)

这时候就必须符号化系统库了。符号化后的日志:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x000000018b816f30 objc_msgSend + 16
1   UIKit                           0x0000000192e0a79c -[UISearchDisplayController _sendDelegateDidBeginDidEndSearch] + 72
2   UIKit                           0x0000000192c4db48 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
3   UIKit                           0x0000000192c4d988 -[UIViewAnimationState animationDidStop:finished:] + 160
4   QuartzCore                      0x00000001900d6404 CA::Layer::run_animation_callbacks(void*) + 260
5   libdispatch.dylib               0x000000018bc551c0 _dispatch_client_callout + 16
6   libdispatch.dylib               0x000000018bc59d6c _dispatch_main_queue_callback_4CF + 1000
7   CoreFoundation                  0x000000018cd79f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
8   CoreFoundation                  0x000000018cd77b18 __CFRunLoopRun + 1660
9   CoreFoundation                  0x000000018cca6048 CFRunLoopRunSpecific + 444
10  GraphicsServices                0x000000018e729198 GSEventRunModal + 180
11  UIKit                           0x0000000192c80628 -[UIApplication _run] + 684
12  UIKit                           0x0000000192c7b360 UIApplicationMain + 208
13  MyApp                           0x0000000100016e54 main (main.m:15)
14  libdyld.dylib                   0x000000018bc885b8 start + 4

可以看出是UISearchControllerdelegate导致的问题,检查一下就发现UISearchDisplayControllerdelegateassign的,是由于点击搜索条的同时pop了界面导致的crash。

从这里可以发现,符号化系统库是很有必要的。

##如何符号化系统库

符号化自己app的方法名,需要编译ipa时生成的dySYM文件。而要将系统库的<redacted>符号化为完整的方法名,也需要系统库的符号文件。

1. 匹配对应的符号文件版本

系统库符号文件不是通用的,而是对应crash所在设备的系统版本和CPU型号的。

crash日志中有这样两个信息:

Code Type:       ARM-64
OS Version:      iOS 10.2 (14C82)

Code Type表示此设备的CPU为armv7armv7s还是arm64

OS Version表示此设备的系统版本号,括号中的字符串代表了此系统的build号。可以在这里查找build号:iOS SDKiOS version history

2. 将对应版本的符号文件放到指定目录

这时候,把获取到的对应版本的符号文件放到Mac的~/Library/Developer/Xcode/iOS DeviceSupport目录下,再使用符号化iOS Crash文件的3种方法里提到的Xcode自带的符号化工具symbolicatecrash进行符号化。这个工具会自动搜索系统库符号文件。

获取系统符号文件的4个方法

从真机上获取

大部分系统库符号文件只能从真机上获取,苹果也没有提供下载。 当你用Xcode第一次连接某台设备进行真机调试时,会看到Xcode显示Processing symbol files,这时候就是在拷贝真机上的符号文件到Mac系统的/Users/xxx/Library/Developer/Xcode/iOS DeviceSupport目录下。

目录下的10.2(14C82)这样的文件夹就是对应的符号文件,通常都有1-3GB的大小,很占用空间,动不动就累积成3、40GB。很多讲清理Mac垃圾文件的教程都会说要删除这个目录下的文件,真是坑爹。正确做法是做成压缩包保存到外部硬盘里,需要符号化的时候再重新解压到此目录。

寻找苹果官方的下载地址

之前watch的调试出现bug时,有人找出过几个watch的符号文件下载地址。见No symbols for paired Apple Watch。 但是我尝试按照对应的命名格式,并没有找到iOS符号文件的下载地址。

从已解密的固件中提取符号文件

某些已经被破解的固件可以直接提取系统文件,但是未破解的固件(较新的固件和arm64的固件),无法用这种方式获取。

固件解密步骤:

1.下载对应版本的.ipsw固件,直接解压,解压后里面有几个.dmg格式的镜像文件,最大的.dmg文件就是系统镜像。
2.从Firmware_Keys找到对应固件的解密key(页面上Root Filesystem字段的key)。
3.用一个dmg工具进行解密,下载地址。使用方式:cd到解压后的ipsw文件夹,执行./dmg extract xxx-xxxx-xxx.dmg dec.dmg -k <key>extract后面跟两个参数,分别是系统镜像dmg的名字和解密后的文件名,-k 后面填写第2步获取到的key,如果key不对,解密会失败。
4.等待。最终会生成一个dec.dmg文件,双击打开即可加载系统镜像。

提取符号文件方法: 参考聊聊从iOS固件提取系统库符号中的二、系统库符号提取部分。

update:目前大多数固件都能解密了,而且iOS 10之后苹果不再进行加密,因此之后都可以用这个方式获取符号文件。

下载旧版本Xcode,提取SDK

旧版本的Xcode里包含了对应的iPhoneSDK,可以从中获得符号文件。 路径是/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/。里面的System/Library里就可以看到framework,而且同时包含了armv7,armv7s,arm643个平台的版本。

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/version.plist可以查看是哪个版本。把iPhoneOS.sdk文件夹的名字改成对应的CFBundleVersion (ProductBuildVersion)格式,然后在里面加一层Symbols子文件夹,把System,Library,usr都放进Symbols里,就可以和其他符号文件一样使用了。

但是当iOS版本只包含了bug修复,而没有改变API,Xcode就不会有附带对应的SDK,还是需要从真机上获取。而且从Xcode7开始,苹果用tbd文件代替了真机符号文件,所以这个方法也失效了。 参考:Xcode software image for user iOS in order to symbolicate iOS calls, Missing iOS symbols at “~/Library/Developer/Xcode/iOS DeviceSupport”

获取符号文件的难题

这个时候,游戏就开始了:

  • 很多时候crash日志只给出了系统的调用栈,不能直接定位到自己app的代码,因此需要符号化系统库。
  • 用户的crash来自各种系统版本,需要对应版本的系统符号文件才能符号化。
  • 系统库符号文件只能从真机上获取,苹果没有提供下载。
  • iOS 7.0(11A465)iOS 10.2(14C92)一共有50个build版本,公司的测试机是不会覆盖到这么变态的完整度的。
  • 同一个版本,有时候会给iPhone和iPad、甚至6和6s提供不同build的固件。
  • 某些版本是某些机子的特供版,例如10.0.3(14A551)是iPhone 7和 7 Plus独有的,这就更加大了收集难度。
  • 同一个iOS版本可能有多个build,例如10.1(14B72)10.1(14B72c),苹果觉得更新幅度太小,就没有提升版本号。
  • 除了build号的区别,符号文件在不同CPU平台上也有区别,意味着来自4s(armv7)和6s(arm64)的符号文件,即便build号是一样的,也无法通用。所以50个build号又要翻倍,达到了88个,所以精确来说我只是收集了(63/88)的进度。幸运的是,arm64机型的系统库里附带了armv7s

规则好厉害的收集游戏啊。收集品其实还有稀有度的区别,其中最厉害的应该是10.0,这是iPhone 7和7 Plus独有的出厂系统,而且没有固件可以下载,因此即便有iPhone 7也不能通过刷机来得到10.0

其实我一直很奇怪为什么很少见到开发者抱怨找不到系统符号文件,从而召集大家进行收集并分享,猜测可能的原因是:

  • 懒。遇到无法符号化的问题,没有去解决。
  • 有些公司可能很早就开始对crash日志自动符号化了,因此很早就开始收集符号文件。只要一直跟着苹果的每一个版本升级,收集起来还是挺简单的。而这些资源,开发者并不会注意到可以共享出来。
  • crash收集和符号化使用的是第三方服务,第三方平台会帮助符号化系统库。

但是我找了一下,没有找到一家明确声明了能够符号化所有系统库的第三方平台。从腾讯的Bugly论坛里也能发现,有些系统方法并没有符号化出来,系统库是缺失的。(update:找到了一个国外的平台,在stack overflow上说能符号化所有版本的系统库,但是是收费服务,我也没有测试)

在公司小组里,大部分时候都是我来分析crash日志,所以当遇到缺少系统符号文件的情况,就会十分无奈。很多时候,没有符号化的crash日志根本无法提供有用信息。

系统符号文件下载地址

暴力收集

收集不全一直让我很不爽。之前搜刮了组内的测试机,只收集到了有限的几个(感谢无私提供iPhone 7让我刷机降级的同事)。终于,这周末特意跑去了一趟二手机市场寻找旧版本的设备来拷贝,总算是收集得完整了一点,但是也花了我121块钱。

心情总算愉悦了。

其中大部分都是拷贝自arm64设备的,armv7的符号文件收集我是放弃了。有哪位大侠有兴趣把这个游戏玩通关的吗?还有 tvOS 和 watchOS的符号,我也放弃了。 (update:又花了些时间从Xcode的SDK和固件里提取了armv7sarmv7的固件,现在只剩下几个arm64的版本了)。

整理了一下传到了网盘,地址见下方,有需要的去下载吧。

目前还缺少的符号文件

通过各种方式,目前已经收集得差不多了,只剩下最后一个10.0(14A346)版本没有得到。如果哪位小伙伴有这个版本,可以分享一下。

分享可以直接贴下载地址,也可以提交到这个github项目iOS-System-Symbols。如果我得到了新的符号文件,也会在这个项目里更新。

缺失符号的版本 缺失的CPU版本 描述
10.0(14A346) arm64 iPhone 7 和 7 Plus独占,出厂自带系统

网盘下载地址

下载地址请见这个项目:iOS-System-Symbols。如果我得到了新的符号文件,会在这个项目里更新。

我把里面的那几个dyld_shared_cache_xxxx大文件单独拿出来了,目的是减小压缩包大小。如果只是符号化的话,用不到这几个文件,只是在真机调试的时候才需要。

符号文件版本列表

这些是我已经收集到的符号文件,包括了对应的CPU信息。iOS10系统开始不支持armv7的机器。但是iOS9以下还是支持armv7的。

查看是否带有对应CPU架构的符号文件的方式:到10.2 (14C92)/Symbols/System/Library/Caches/com.apple.dyld这样的目录下,会有对应的dyld_shared_cache_arm64dyld_shared_cache_armv7sdyld_shared_cache_armv7文件,如果缺失了,就说明对应的CPU架构符号文件还不存在。(经过试验,这几个大文件并不影响符号化,只是在真机调试的时候才有用,因此如果嫌太占用空间,可以删去)。

版本 已收集到的CPU版本 描述
11.2.6 (15D100) arm64
11.2.5 (15D60) arm64
11.2.2 (15C202) arm64
11.2.1 (15C153) arm64
11.2 (15C114) arm64
11.2 (15C113) none 苹果在发布了15C114之后,又发布了一个低版本的15C113,之后又移除了15C113的下载地址,因此这个固件目前无法下载。应该只是误发布,可以忽略这个版本
11.1.2 (15B202) arm64
11.1.1 (15B150) arm64
11.1 (15B101) arm64 iPad Pro2 12.9-inch and iPad Pro 10.5-inch only
11.1 (15B93) arm64
11.0.3 (15A432) arm64
11.0.2 (15A421) arm64
11.0.1 (15A403) arm64 iPhone 8 and 8 Plus only
11.0.1 (15A402) arm64
11.0 (15A372) arm64
10.3.3 (14G60) arm64,armv7s
10.3.2 (14F91) arm64,armv7s iPad mini4(Cellular) only
10.3.2 (14F90) arm64,armv7s iPad (5th gen) only
10.3.2 (14F89) arm64,armv7s
10.3.1 (14E304) arm64,armv7s
10.3 (14E277) arm64,armv7s
10.2.1 (14D27) arm64,armv7s
10.2 (14C92) arm64,armv7s
10.1.1 (14B150) arm64,armv7s
10.1.1 (14B100) arm64,armv7s
10.1 (14B72c) arm64,armv7s
10.1 (14B72) arm64,armv7s
10.0.3 (14A551) arm64,armv7s
10.0.2 (14A456) arm64,armv7s
10.0.1 (14A403) arm64,armv7s
10.0(14A346) none iPhone 7 和 7 Plus独占,出厂自带系统
9.3.5 (13G36) arm64,armv7s,armv7
9.3.4 (13G35) arm64,armv7s,armv7
9.3.3 (13G34) arm64,armv7s,armv7
9.3.2(13F72) arm64,armv7s iPad Pro 9.7寸独占,修复了变砖的问题
9.3.2 (13F69) arm64,armv7s,armv7
9.3.1 (13E238) arm64,armv7s,armv7
9.3(13E237) armv7s,armv7 5s和更旧机型独占,修复了不能激活的问题
9.3(13E236) armv7 iPad2独占,修复了不能激活的问题
9.3(13E234) arm64,armv7s 6s, 6s Plus and iPad Pro 9.7寸独占
9.3 (13E233) arm64,armv7s,armv7
9.2.1 (13D20) arm64,armv7s iPhone 6 和更新的机型独占
9.2.1 (13D15) arm64,armv7s,armv7
9.2 (13C75) arm64,armv7s,armv7
9.1 (13B143) arm64,armv7s,armv7
9.0.2(13A452) arm64,armv7s,armv7
9.0.1(13A404) arm64,armv7s,armv7
9.0 (13A344) arm64,armv7s,armv7
8.4.1 (12H321) arm64,armv7s,armv7
8.4 (12H143) arm64,armv7s,armv7
8.3 (12F70) arm64,armv7s,armv7 iPhone独占
8.3 (12F69) arm64,armv7s,armv7 iPad独占
8.2 (12D508) arm64,armv7s,armv7
8.1.3 (12B466) arm64,armv7s,armv7
8.1.2 (12B440) arm64,armv7s,armv7
8.1.1 (12B436) arm64,armv7s iPhone 6 和更新的机型独占
8.1.1 (12B435) armv7s,armv7 5s和更旧机型独占
8.1 (12B411) arm64,armv7s,armv7 iPhone独占
8.1 (12B410) arm64,armv7s,armv7 iPad独占
8.0.2 (12A405) arm64,armv7s,armv7
8.0.1(12A402) armv7s,armv7 8.0.1有导致变砖的bug,发布后很快就停止推送了
8.0 (12A366) arm64,armv7s 6 Plus独占
8.0 (12A365) arm64,armv7s,armv7
7.1.2 (11D257) armv7s,armv7
7.1.1 (11D201) arm64,armv7s,armv7
7.1 (11D167) arm64,armv7s,armv7
7.0.6 (11B651) arm64,armv7s,armv7
7.0.4 (11B554a) arm64,armv7s,armv7
7.0.3 (11B511) arm64,armv7s,armv7
7.0.2(11A501) armv7s,armv7
7.0.1(11A470a) armv7s 5s 和 5c 独占
7.0(11A465) arm64,armv7s,armv7

机型对应CPU架构

CPU 机型
armv6 iPhone, iPhone2, iPhone3G, iPod Touch 1 and 2
armv7 iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini,iPod Touch 3G, iPod Touch4, iPod Touch5
armv7s iPhone5, iPhone5C, iPad4(iPad with Retina Display)
arm64 iPhone5S, iPad Air, iPad mini2(iPad mini with Retina Display), iPhone6, iPhone6s, iPhone7, iPhone7s and any new device in the future

结束语

最后再次呼吁一下,如果谁有上面缺失的符号文件,就请共享一下吧。虽然只是做一点微小的工作,但是能够有很大帮助。

不觉得填满上面那个列表会很爽吗?

update:现在基本上已经收集完了。

额外提示

其实这些符号文件就是真机上的系统库,包括了完整的系统库二进制文件。有时候要反编译某些系统framework,但是模拟器SDK里没有对应的framework(比如只有真机上才有的CoreMotion.framework),就可以在这些真机上的系统库里找到了。

参考