crash来源
iOS设备上的应用闪退时,操作系统会生成一个崩溃报告,也叫崩溃日志,保存在设备上。
崩溃日志上有很多有用的信息,包括应用是什么情况下闪退的。通常,上面有每个正在执行线程的完整堆栈跟踪信息,所以你能从中了解到闪退发生时各线程都在做什么,并分辨出闪退发生在哪个线程上。
crash的产生来源于:
- 应用违反操作系统规则
- 应用中有Bug
引用违反操作系统规则
违反iOS规则包括在启动、恢复、挂起、退出时watchdog超时、用户强制退出和低内存终止。
Watchdog 超时机制
从iOS 4.x开始,退出应用时,应用不会立即终止,而是退到后台。但是,如果你的应用响应不够快,操作系统有可能会终止你的应用,并产生一个崩溃日志。
通常系统允许一个App从启动到可以相应用户事件的时间最多为5S,如果超过了5S,App就会被系统终止掉。在Launch,resume,suspend,quit时都会有相应的时间要求。这些事件与下列UIApplicationDelegate方法相对应:
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
上面所有这些方法,应用只有有限的时间去完成处理。如果花费时间太长,操作系统将终止应用。
User force-quit(用户强制退出)
iOS 4.x开始支持多任务。如果应用阻塞界面并停止响应, 用户可以通过在主屏幕上双击Home按钮来终止应用。此时,操作应用将生成一个崩溃日志。
注意: 双击Home按钮后,你将看到运行过的所有应用。那些应用不一定是正在运行,也不一定是被挂起。
通常,用户点击Home按钮时,应用将在后台保留约10分钟,然后操作系统自动将其终止。 所以双击Home按钮显示的应用列表只是表明那是一系列过去打开过的应用。删除那些应用的图标不会产生任何崩溃日志。
Low Memory termination(低内存终止)
子类化UIViewController时,你或许已经注意到didReceiveMemoryWarning方法。
在前台运行的应用拥有访问和使用内存的最高优化级。然而,这并不意味着该应用能使用设备的所有可用内存 ——每个应用只能使用一部分可用内存。
当内存使用达到一定程度时,操作系统将发出一个 UIApplicationDidReceiveMemoryWarningNotification 通知。同时,调用 didReceiveMemoryWarning 方法。
此时,为了让应用继续正常运行,操作系统开始终止在后台的其他应用以释放一些内存。所有后台应用被终止后,如果你的应用还需要更多内存,操作系统会将你的应用也终止掉,并产生一个崩溃日志。而在这种情况下被终止的后台应用,不会产生崩溃日志。
注意: 根据 苹果文档, Xcode不会自动添加低内存日志。你必需手动获取日志。 然而,根据我的个人经验,使用 Xcode 4.5.2, 低内存日志也会自动导入,只是”Process”和”Type”属性都被标为Unknown(未知)。
另外,值得一提的是在极短时间内分配一大块内存将给系统内存带来巨大负担。这样,也会产生内存警告的通知。
应用中有Bug
如你所想,大多数闪退都是由于应用中有Bug,因此大多数崩溃日志的产生都是因为应用中的Bug。Bug的种类的有很多。比如:
- 数组越界
- 插空
- 空引用
- 引用未定义方法
- 多线程安全性
- 访问野指针
- 发送未实现的selector
- .......
Crash文件的解析
Crash文件结构
当程序运行Crash的时候,系统会把运行的最后时刻的运行信息记录下来,存储到一个文件中,也就是我们所说的Crash文件。iOS的Crash日志通常由以下6各部分组成。
1 Process Information(进程信息)
第一部分是闪退进程的相关信息。
- Incident Idnetifier
崩溃报告的唯一标识符,不同的Crash - CrashReporter Key
设备标识相对应的唯一键值(并非真正的设备的UDID,苹果为了保护用户隐私iOS6以后已经无法获取)。通常同一个设备上同一版本的App发生Crash时,该值都是一样的。 - Hardware Model
代表发生Crash的设备类型,上图中的“iPad4,4”代表iPad Air - Process
代表Crash的进程名称,通常都是我们的App的名字, []里面是当时进程的ID - Path
可执行程序在手机上的存储位置,注意路径时到XXX.app/XXX,XXX.app其实是作为一个Bundle的,真正的可执行文件其实是Bundle里面的XXX,感兴趣的可以自己查一下相关资料,有机会我后面也会介绍到 - Identifier
你的App的Indentifier,通常为“com.xxx.yyy”,xxx代表你们公司的域名,yyy代表某一个App - Version
当前App的版本号,由Info.plist中的两个字段组成,CFBundleShortVersionString and CFBundleVersion - Code Type
当前App的CPU架构 - Parent Process
当前进程的父进程,由于iOS中App通常都是单进程的,一般父进程都是launchd
2 Basic Information(基本信息)
这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。如果有很多崩溃日志都来自iOS 6.0,说明问题只发生在iOS 6.0上。
- Date/Time
Crash发生的时间,可读的字符串 - OS Version
系统版本,()内的数字代表的时Bulid号 - Report Version
Crash日志的格式,目前基本上都是104,不同的version里面包含的字段可能有不同
3 Exception(异常-很重要)
在这部分,你可以看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不同,在这部分你还能看到一些另外的信息。
- Exception Type
异常类型 - Exception Subtype
异常子类型 - Crashed Thread
发生异常的线程号
4 Thread Backtrace(线程回溯)
这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单。
发生Crash的线程的Crash调用栈,从上到下分别代表调用顺序,最上面的一个表示抛出异常的位置,依次往下可以看到API的调用顺序。上图的信息表明本次Crash出现xxxViewController的323行,出错的函数调用为orderCountLoadFailed。
5 Thread State(线程状态)
这部分是闪退时寄存器中的值。一般不需要这部分的信息,因为回溯部分的信息已经足够让你找出问题所在。
Crash时发生时刻,线程的状态,通常我们根据Crash栈即可获取到相关信息,这部分一般不用关心。
6 Binary Images(二进制映像)
这部分列出了闪退时已经加载的二进制文件。
- 符号化Symbolication
第一次看到崩溃日志上的回溯时,你或许会觉得它没什么意义。我们习惯使用方法名和行数,而非像这样的神秘位置:
将这些十六进制地址转化成方法名称和行数的过程称之为符号化。
从Xcode的Organizer窗口获取崩溃日志后过几秒钟,崩溃日志将被自动符号化。上面那行被符号化后的版本如下 :
Xcode符号化崩溃日志时,需要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需完全匹配才行。否则,日志将无法被完全符号化。
所以,保留每个分发给用户的编译版本非常重要。提交应用前进行归档时,Xcode将保存应用的二进制文件。可以在Xcode Organizer的Archives标签栏下找到所有已归档的应用文件。
在发现崩溃日志时,如果有相匹配的.dSYM文件和应用二进制文件,Xcode会自动对崩溃日志进行符号化。如果你换到别的电脑或创建新的账户,务必将所有二进制文件移动到正确的位置,使Xcode能找到它们。
(注意: 你必需同时保留应用二进制文件和.dSYM文件才能将崩溃日志完整符号化。每次提交到iTunes Connect的构建都必需归档。
.dSYM文件和二进制文件是特定绑定于每一次构建和后续构建的,即使来自相同的源代码文件,每一次构建也与其他构建不同,不能相互替换。
如果你使用Build 和 Archive 命令,这些文件会自动放在适当位置。 如果不是使用Build 和 Archive命令,放在Spotlight能够搜索到的位置(比如Home目录)即可。)
常见的Exception Type & Exception Code
Exception Type
EXC_BAD_ACCESS
此类型的Excpetion是我们最常见碰到的Crash,通常用于访问了不改访问的内存导致。一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。
SIGSEGV: 通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。
SIGABRT: 收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。
SEGV:(Segmentation Violation),代表无效内存地址,比如空指针,未初始化指针,栈溢出等;
SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)
SIGILL:尝试执行非法的指令,可能不被识别或者没有权限EXC_BAD_INSTRUCTION
此类异常通常由于线程执行非法指令导致EXC_ARITHMETIC
除零错误会抛出此类异常
Exception Code
0xbaaaaaad
此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发0xbad22222
当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序0x8badf00d
这个前面已经介绍了,程序启动或者恢复时间过长被watch dog终止0xc00010ff
程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止0xdead10cc
程序退到后台时还占用系统资源,如通讯录被系统终止0xdeadfa11
前面也提到过,程序无响应用户强制关闭
崩溃日志的实例
获取Crash的途径
本机
将iOS设备连接到电脑上,然后打开Xcode。从菜单栏上选择 Window 菜单, 然后选择 Organizer (快捷方式是 Shift-CMD-2).
在 Organizer 窗口上, 选中 Devices 标签栏. 在左侧的导航面板上,选中 Device Logs, 如下图所示:
看看上图,左侧有好几个 Device Logs 菜单项。 LIBRARY 下面的Device Logs是你所有设备(曾经连接到Xcode的)的日志 。每个设备下面的 Device Logs 是对应设备的日志。
itunes connect(Manage Your Applications - View Details - Crash Reports)
应用提交到App Store后,你也能从 iTunes Connect 获取到用户的崩溃日志. 登录到 iTunes Connect 上。
设备与电脑上的iTunes Store同步
设备与电脑上的iTunes Store同步后,会将崩溃日志保存在电脑上。根据电脑操作系统的不同,崩溃日志将保存在以下位置:
Mac OS X:~/Library/Logs/CrashReporter/MobileDevice/
Windows XP: C:Documents and Settings
Windows Vista or 7: C:Users
第三方的Crash收集系统
有很多优秀的第三方Crash收集系统大大的方便了我们收集Crash,甚至还带了符号化Crash日志的功能。比较常用的有Crashlytics,Flurry等。
附录
Apple官方文档
Understanding and Analyzing iOS Application Crash Reports
Technical Note TN2123 CrashReporter
https://developer.apple.com/library/ios/qa/qa1592/_index.html
WWDC视频
Understanding Crash Reports on iPhone OS
声明
本文内容来自以下文章,我只是做了整理。
iOS应用崩溃日志分析
iOS应用崩溃日志分析——绝对有用,绝对实用啊
ios crash的原因与抓取crash日志的方法