LuckyHu Blog

Home MyGame MyApp About

iOS 理解Crash Log

16 Nov 2012

作为从Android转到iOS的程序员,我觉得最不适应的地方可能就是对bug的定位。在Android中,由于使用Java跑在Java虚拟机上,所以当程序出错时(一般是不会直接操作内存的,所以一般不会有有直接的内存错误),往往是抛出一个异常,关于这个异常的一系列栈信息都能完整的拿到,这对于查找问题的出处来说,就变得非常简单了。

但是在iOS上,由于使用Objective-c 和 c ,直接执行二进制指令,自己管理内存,会出现访问错误内存的情况出现。这时,系统会直接把你的进程干掉,iOS会给你生成一个Crash Log(如果是调试状态,通常会断在某个地址,基本上没办法判断出错的地方)。以前我基本上不读Crash Log,因为读不懂....iOS的Crash Log不像Android的错误日志,直接告诉你为什么错了,错误在哪个文件第几行(在某些情况下,iOS的CrashLog也能提供这些信息)。最近被逼得没办法了,好好研究了以下Crash Log。主要是看了2010WWDC的**Understand Crash Log On iPhone OS**,在stackOverFlow上查阅了相关的一些信息,总结如下(有些部分还是没太搞清楚,以后继续完善):

如何得到一个可读的Crash Report,关于这个,网上有很多,如何symbolicate Crash Log。而且现在Xcode的Orgernizer使用起来非常方便,一般只需要你把Crash Log拖到Orgernizer中,它会自动地给你的Crash Log做Symbolicate。它大概的原理是(我记不得在哪看到的了,但我感觉应该是这样,it make sence),你的每一个crash log是相对应于一个.app版本的,在你的.app中有一个.dSYM文件,当你发布的时候,使用xcode 的build and archive进行发布,这个发布会纪录到你的xcode(或者orgernizer)中,当你把crash log拖到orgernizer时,它会自动去匹配响应版本的.dSYM文件来进行转换,得到各个函数名和在文件中的位置。需要特别强调的是在Xcode中有两个编译选项会特别地影响到Crash Report的生成: 1.**Generate Debug Symbols**.当你把这个选项设为YES的时候,生成的Crash Report才能根据.dSYM文件,在出错时,定位到你自己代码的调用信息,否则就只有纯粹的函数地址和偏移量,打开这个设置,会导致你的二进制文件大增变大(据说20%~30%),应该也会营销效率(具体能影响到什么程度我也没有一个量化的概念,但我自己的感觉是区别不是很大)。 2.**Strip Debug Symbols During Copy**.这个选项主要是影响在你发布和Debug的时候是否剥去Debug symbols。 所以,如果希望能得到非常详细的错误信息,应该把前者设为YES,后者设为NO.

总的来说Crash Report分为四大种类型(我的理解主要是通过exception type和exception code来进行判断是哪一种): 1.WatchDog Timeout 一般是在启动,恢复等时候,在主线程中做了非常耗时的工作,导致UI阻塞,系统就会把你干掉并声称一个这种类型的Crash Report。exception type:0x000..020 exception code:0x8badf00d. 2.User Force-quit 用户主动操作把你的程序干掉了。excption code:0xdeadfa11 3.Low memory termination 由于内存不足被干掉。Low memory所产生的crash log和其他的crash log很不一样,很容易分辨。 4.Application bugs 本身代码的bug导致的crash。 另外还有一些exception code在苹果的这个文档中有相应的纪录。

Crash Report中比较重要的一些信息: Incident Identifier 每个崩溃报告拥有一个唯一的标识。 CrashReporter Key 主要反应这个崩溃报告来自哪一台设备,如果你一系列的崩溃报告都来自同一个CrashReporter Key,那你可以仔细留意以下这台设备的情况。 Hardware Model 说明这个崩溃报告产生自哪一类型的设备。 Date/Time 崩溃报告产生的时间。 OS Version 系统版本。 Exception Type 异常类型。 Exception Code 异常码。 Crashed Thread 崩溃的线程。 Highlighted Thread 有的时候,这里不是Crash Thread,而是Highlighted Thread。区别? Application Specific Information 一些特殊信息,在某些情况下有。比如由于占用系统资源导致被杀掉,这里有可能告诉你是占用了什么系统资源。

在Thread BackTrace中: 第一列 这是一列数字,官方貌似叫frame number,我的理解就是最后的调用栈的index. 第二列 应该是只这个调用所在的库或者framework,具体的名字我没听清楚。 第三列 函数地址。 第四列 没听明白,但我感觉这一列是才是函数地址,最后能被symbolicate转换为函数名。后面的+xx是一个偏移量,能定位到在文件中的行数。

在以上的信息中,又有两个特别的重要的,能帮助我们识别问题大概出在哪。 1.Exception Type。 -1- EXC_BAD_ACCESS (SIGSEGV) 这个类型的Exception的意思是,你没有权限访问你所要访问的内存。一般都是由于访问了已经被release的object导致的,或者把一个object release了两次(我的理解这和前面的情况是一样的)。甚至当你访问超出数组长度的内容时,也有可能出现这种类型的错误。它的意思应该是段错误。这个SIGSEGV不是objective-c的excption,而是更底层的C部分的信号。 -2- EXC_CRASH (SIGKILL)或者(SIGABRT) 这个类型的Exception比较特别,你需要认真查看后面所有Thread的BackTrace才能找到最终原因,因为有时候它所写的Crash Thread并不是真正引起崩溃的原因,在其中你也找不到什么有用的信息。(SIGABRT)一般是由于系统捕获到一个异常,然后把你的应用终结掉了,你可以在下面的栈信息中寻找有abort信息的那一个thread,能找到真正的原因。(SIGKILL)目前还没在自己的App中遇到过。

2.Exception Code。 一般如果是由于有未捕获异常的话,都是0x000000000。如果exception type是0x0000020,那么这个数值可能指代某种具体类型的错误,在上面的文档中有。如果是内存问题,一般会有两种,一种是KERN_INVALID_ADDRESS at 0x…….,一种是KERN_PROTECTION_FAILURE at 0x……。具体区别在这里有人讲了下,但我没太明白实际的区别。