Crash log符号化与调试信息

这篇文章主要整理了crash log的符号化解析和调试信息与配置相关的一些内容。

对于做移动App开发的来说,质量和体验都是很重要的。一个客户端应用如果经常“闪退”,是产品质量很差的一个体现,用户体验就更不用提了。所以开发一个优秀的App,首先是保证自身的技术质量,尽量杜绝“闪退”,也就是“Crash”。但客户端上线后,偶尔出现一个隐藏很深的bug也在所难免。我们所能做的就是尽可能的收集问题相关的信息,争取在将来的新版本中解决和改进。

0. Crash

一个App启动之后,用着用着就突然被iOS系统关闭,或者干脆就起不来,在打开的一瞬间关闭,这就是Crash,俗称“闪退”“崩溃”。

iOS上的App闪退有各种各样的原因,手机过热、响应超时、内存过低都是有可能的crash原因。但更多情况下是App程序自身的运行逻辑存在问题、缺陷。比如调用用了Objective-C对象根本不支持的方法(发送消息),非法内存访问,数组越界,参数不符合要求等。

这些问题在调试阶段,我们都可以很容易的通过断点和console中提供的信息快速定位并解决。

但对于已发布的App,如果想重现并利用上述办法来解决,恐怕会比较费时费事。

最有帮助最直接的办法就是根据出现问题时的闪退日志,分析和判断crash的原因,快速准确的定位和解决。

1. Crash log

在iOS上运行的App出现crash的时候,通常会生成一个crash log,记载问题发生时的具体状况。开发者可以在iTunes Connect(相当于App Store后台)中特定App下找到收集上来的crash log。不过客户端用户可以选择不发送诊断信息,这样收集上来的信息就不一定是全面的。

不过开发者可以对exception和signal设置自定义的handler做额外处理,以收集现场信息。现在也有很多第三方的工具很流行,比如Crashlytics,国内的友盟等。

闪退日志里面包含了Crash发生的App、运行软硬件环境、发生时间、错误类型、方法调用异常栈、各线程状态、寄存器和内存信息。

而其中对我们开发人员来说意义最为重大的,可能就是异常线程的调用栈,例如:

Last Exception Backtrace:
0   CoreFoundation                	0x18517e950 __exceptionPreprocess + 132
1   libobjc.A.dylib               	0x1916841fc objc_exception_throw + 60
2   CoreFoundation                	0x185085910 -[__NSDictionaryM setObject:forKey:] + 900
3   CrashDebugInfoTest            	0x1000c2b90 0x1000bc000 + 27536
4   CrashDebugInfoTest            	0x1000c28dc 0x1000bc000 + 26844
5   UIKit                         	0x1881bc55c -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 316
6   UIKit                         	0x1881bbf08 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1564
7   UIKit                         	0x1881b59ec -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 772
8   UIKit                         	0x1881498cc -[UIApplication handleEvent:withNewEvent:] + 3316
9   UIKit                         	0x188148ad0 -[UIApplication sendEvent:] + 104
10  UIKit                         	0x1881b5044 _UIApplicationHandleEvent + 672
11  GraphicsServices              	0x18ad63504 _PurpleEventCallback + 676
12  GraphicsServices              	0x18ad63030 PurpleEventCallback + 48
13  CoreFoundation                	0x18513e890 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 56
14  CoreFoundation                	0x18513e7f0 __CFRunLoopDoSource1 + 444
15  CoreFoundation                	0x18513ca14 __CFRunLoopRun + 1620
16  CoreFoundation                	0x18507d6d0 CFRunLoopRunSpecific + 452
17  UIKit                         	0x1881b41c8 -[UIApplication _run] + 784
18  UIKit                         	0x1881aefdc UIApplicationMain + 1156
19  CrashDebugInfoTest            	0x1000c2c5c 0x1000bc000 + 27740
20  libdyld.dylib                 	0x191c77aa0 start + 4

其中从第二列来看,很多是开发库中的调用,而关键在于其间我们自己的App方法调用。可惜有些时候,这关键的信息竟然全是16进制的数据,我们很难看懂。比如:

3 CrashDebugInfoTest 0x1000c2b90 0x1000bc000 + 27536

那么要从十六进制的地址码,得到我们代码中对应的方法调用,就需要结合调试信息对crash log进行符号化。

2. 符号化的各种方法

符号化的方法多种多样,从网上社区论坛和个人经验看来,至少有如下办法:

  • 使用开发工具库中自带的symbolicatecrash
  • 使用atos
  • 使用dwarfdump

更有牛人,自己写了个复杂的脚本来解决这个问题。下面我介绍我常使用的两种方法,一个是利用atos,一个是充分利用Xcode自带的工具。其它的大家都可以到网上参看相关文章,一搜一大筐。

atos,就是address to symbol,把地址翻译成符号。上面那段我提到了,要想把十六进制的地址翻译为符号,需要调试信息。最好用的调试信息就是我们在每次给App打包时生成的dSYM文件。而atos最好用的方式就是:

atos -o XXX.app.dSYM/Contents/Resources/DWARF/XXX -l address0 targetAddress

其中:

  • XXX是AppName
  • address0是当前进程在内存中加载的起始地址,至于为什么需要这个,那就有必要去了解下ASLR
  • targetAddress就是你想要符号化的地址了,比如0x1000c2b90

除了atos外,我想介绍的另一个办法就是使用Xcode自带的crash log分析工具,在老版本的Xcode中是在Organizer里,在新版本里是在Devices中。

有的朋友可能会说,那里面显示的可还是十六进制的地址啊!那是因为它“没看到”App和dSYM文件啊。那怎么办?简单:

把App和dSYM放在一个目录中,并用mdimport把目录加入到Spotlight的索引中即可。

怎么样,这招是不是更快更好用?symbolicatecrash神马的就不需要了吧!

3. 针对framework静态库的crash定位和调试选项设置

之前本人曾经以framework(iOS Universal Framework)的方式开发了好多SDK供别人用。可当使用了framework库的App闪退了的时候,即使是SDK中的逻辑问题,异常栈中显示的也是App的名字。

更重要的是,默认情况下,异常栈的最右一列根本没法符号化。

这是因为framework实际上是一种静态库,在Build App时,它已经完全“融入”了,静态链接到App产物中。而在framework生成的时候,调试信息已经被抽取掉了。

我们打开SDK的工程文件,在Build Settings里搜索Strip,会发现有好几个选项:

  • Strip Debug Symbol During Copy
  • Strip Linked Product
  • Strip Style
  • Use Separate Strip

对于这个问题,我们只要在Strip Linked Product一项中选择No就行了。这样在Build出的SDK framework中,包的体积会变大,因为它容纳了本要去除掉的调试信息。

按我在之前的Blog的办法,我们看看在Mach-O文件中多了什么:

Debug Info

Debug Info

是的,正是DWARF格式的数据。DWARF是一种通用的调试信息格式,可以认为是Debugging With Attributed Records Format的缩写。感兴趣的可以前往:

http://www.dwarfstd.org

这样,关于Crash问题的解决方案和原理我就解释清楚了,欢迎大家拍砖!

相关文章:

此条目发表在 iOS, iOS开发基础, 开发, 计算机技术 分类目录,贴了 , , , , , 标签。将固定链接加入收藏夹。
  1. Pingback: Crash log符号化与调试信息 | 网站采集

  2. 有一种情况是,app是直接真机调试的,有时候断开跟xcode的连接单独跑,然后就crash了,拿到了在xcode的device里面拿到了log,但是没法翻译,这种情况下怎么办呢