前言
在项目开发中我们总能遇到各种各样的问题造成Crash崩溃 究其原因一个是我们开发人员对系统机制理解不够深刻或者代码逻辑不够严谨造成的
我们可以少犯错但不可能不犯错 ——不知道谁说的系列(:
那么问题发生后我们应该第一时间定为找到问题再去尝试解决问题
一般都会经历这样一个过程发现问题 -> 定位问题 -> 解决问题
发现问题
- 首先大部分问题其实都应该是程序员自己先发现的
每一次提交和改动都应该经过自己严谨的考虑和初步测试保证没有问题才可以Commit是我们开发者基本素养- 再者如果你们有review机制和测试团队的话 review组同学是为了代码质量 测试团队就是为项目上线之前质量把关的最后一步了 一般测试同学会写各种测试用例各种场景来check开发同学写的项目存在哪些问题或者与需求设计不符的
- 然后就到了上线了 高并发多场景的使用 难保会出现一些没有发现奇怪的问题 甚至Crash 这就是用户来发现问题了 是我们的失职
在上面任何一个环节都肯能出现Crash问题 所以当问题出现后 我们该如何第一时间定位问题 解决问题就尤为重要
定位问题
对于定位问题我们一般又这么几种方式
一、模拟场景 Debug
一般这种可以知道特定场景并且可以直接真机Debug的问题是最好定位和解决的 一般加个全局断点就搞定了 也是最简单最不应该出现的问题
二、可以拿到出现Crash问题的手机(需要开启与开发者共享崩溃信息 一般都是打开着的)
可以打开手机设置 -> 隐私 -> 分析-> 打开共享iPhone分析 -> 打开与应用开发者共享
那我们可以通过这种方式查看手机里的Crash log 来帮助我们分析问题
打开Xcode 选择顶部条上的Window
选择Devices and Simulators 进入
选择View Device Logs 显示该设备log信息
需要Loading一会儿 我们就可以查看到这台设备上的一些Crash信息了
上面这张图看到的是WeChat Crash的堆栈符号化信息 可以看到
EXC_CRASH (SIGKILL)
Code 0x8badf00d
应该是被系统watchdog杀掉了 后面再说如何分析符号化信息
三、登陆developer开发者账号获取项目版本线上crash信息
Step1:同样先打开顶部条上的Window 选择Organizer
Step2:进入Developer开发项目打包信息看板 选择Crashes tab
选择我们线上打包的项目 选择Release版本查看Crash信息
Tips:如果不是用你的电脑打包的 可以让打包的同事把.xcarchive
给你 双击打开就会倒入到这里
Step3:上面这张图可以看到我们选择了一个FMDB 数据库操作引起的Crash 点进去查看最近两周发生了29次Crash 分别发生在什么类型的设备上 而且有详细的堆栈信息和方法调用列表
Step4:我们可以把鼠标移动到我们想查看的方法上点击后面的小箭头 open crash in project 选择你的项目去打开直接回定位到项目中的代码块 方便快捷直接
Tips:定位的地方可能不准确 需要当前文件跟上线代码保持一致 才回定位准确
四、集成友盟、Bugly或其他第三方handle了Crash数据
一般项目中都会集成第三方SDK帮助我们统计数据和Crash信息
如果你的项目中集成了三方工具来收集Crash信息 可以去三方提供的后台去查看 会有类似于Xcode的上下文堆栈信息列表 借助于对方的符号化工具定位代码中的问题也是比较方便的一种方式 在这里就不多说了
Orz...待补充中...
解决问题
符号化信息解读
建议先看一下官方崩溃信息分析文档 了解和分析应用程序崩溃报告 你会对Crash信息的获取和分析有一个比较深刻的理解 下面列举几种常见的Crash符号化信息和对应的字段意思
设备信息
Incident Identifier: AF4F2C83-8F68-47EF-B5AA-F16B067B5DF4 // crash的ID
CrashReporter Key: 5670de85ee1f0f3c904891536e81ec086ed4b35b // crash 的设备ID
Hardware Model: iPhone8,1 // 手机的型号 (iPhone8,1代表iPhone6s 8,2 代表iPhone6s Plus)
Process: kidneyUser [896] // App的名称 (该App的进程ID)
Path: /private/var/containers/Bundle/Application/48C71AA1-EB99-49B1-ABD7-2903DBA8E394/kidneyUser.app/kidneyUser // APP 的位置 路径
Identifier: kidneyDiseaseHospitalUser // bundle ID
Version: 1 (1.0) // APP的版本号
Code Type: ARM-64 (Native) // app的应用架构
Parent Process: launchd [1]
Date/Time: 2016-05-05 10:45:43.43 +0800 // crash发生的时间
Launch Time: 2016-05-05 10:42:07.07 +0800 // 进入应用的时间
OS Version: iOS 9.3.1 (13E238) // iOS系统的版本
Report Version: 105
如果产品上线之后, 回收集大量的Crash Log日志文件, 可以对Crash文件里面的手机型号,版本号, 手机型号, iOS系统版本,进行分类, 可以获得更多的信息, 更好的解决bug甚至未知的bug具体原因, 做更好的测试
异常信息
Exception Type: EXC_CRASH (SIGABRT) // 异常的类型
Exception Codes: 0x0000000000000000, 0x0000000000000000 // 异常出错的代码
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d//终止原因
Termination Description: SPRINGBOARD, scene-create watchdog transgression: com.tencent.xin exhausted CPU time allowance of 2.48 seconds | | ProcessVisibility: Background | ProcessState: Running | WatchdogEvent: scene-create | WatchdogVisibility: Background | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 10.920 (user 10.920, system 0.000), 65% CPU", | "Elapsed application CPU time (seconds): 5.897, 35% CPU" | )//终断信息描述
Exception Note: EXC_CORPSE_NOTIFY // 异常通知
Triggered by Thread: 0 // 异常发生的线程(0代表主线程, 其他为主线程)
进程信息
Filtered syslog:
None found
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 CoreFoundation 0x00000001834153c0 0x183329000 + 967616
1 CoreFoundation 0x00000001833492e8 0x183329000 + 131816
2 CoreFoundation 0x00000001833492e8 0x183329000 + 131816
...
6 CoreFoundation 0x0000000183349274 0x183329000 + 131700
7 Foundation 0x0000000183dacf90 0x183da1000 + 49040
8 UIKit 0x000000018d039758 0x18cff8000 + 268120
...
16 UIKit 0x000000018d14ae58 0x18cff8000 + 1388120
17 UIKit 0x000000018d1371b4 0x18cff8000 + 1307060
18 WeChat 0x00000001053c22e4 0x1029e0000 + 43918052
19 WeChat 0x00000001055106f8 0x1029e0000 + 45287160
20 WeChat 0x00000001054432b4 0x1029e0000 + 44446388
21 WeChat 0x0000000104f31c94 0x1029e0000 + 39132308
22 UIKit 0x000000018d04aee0 0x18cff8000 + 339680
...
28 UIKit 0x000000018d043770 0x18cff8000 + 309104
29 QuartzCore 0x00000001875e525c 0x1874c2000 + 1192540
`
Thread 1:
0 libsystem_pthread.dylib 0x0000000183093b04 0x183093000 + 2820
Thread 2 name: Dispatch queue: NSOperationQueue 0x10dc05910 (QOS: UNSPECIFIED)
Thread 2:
0 libsystem_kernel.dylib 0x0000000182ed3e08 0x182ed3000 + 3592
1 libsystem_kernel.dylib 0x0000000182ed3c80 0x182ed3000 + 3200
2 CoreFoundation 0x0000000183416e40 0x183329000 + 974400
3 CoreFoundation 0x0000000183414908 0x183329000 + 964872
4 CoreFoundation 0x0000000183334da8 0x183329000 + 48552
5 WeChat 0x00000001058784b4 0x1029e0000 + 48858292
6 CoreFoundation 0x0000000183476580 0x183329000 + 1365376
7 CoreFoundation 0x0000000183355748 0x183329000 + 182088
Thread 3:
0 libsystem_kernel.dylib 0x0000000182ef50f4 0x182ed3000 + 139508
1 libsystem_pthread.dylib 0x0000000183097c90 0x183093000 + 19600
2 mars 0x00000001087969e0 0x1085e8000 + 1763808
3 mars 0x00000001085f4148 0x1085e8000 + 49480
4 mars 0x00000001086353fc 0x1085e8000 + 316412
5 libsystem_pthread.dylib 0x0000000183095220 0x183093000 + 8736
6 libsystem_pthread.dylib 0x0000000183095110 0x183093000 + 8464
7 libsystem_pthread.dylib 0x0000000183093b10 0x183093000 + 2832
补充常见的Exception Codes代码类型
Exception Codes: 常见代码有以下几种
0x8badf00d错误码:Watchdog超时,意为“ate bad food”。
0xdeadfa11错误码:用户强制退出,意为“dead fall”。
0xbaaaaaad错误码:用户按住Home键和音量键,获取当前内存状态,不代表崩溃。
0xbad22222错误码:VoIP应用(因为太频繁?)被iOS干掉。
0xc00010ff错误码:因为太烫了被干掉,意为“cool off”。
0xdead10cc错误码:因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”
异常代码0x8badf00d指示应用程序已终止的iOS 因为看门狗超时发生,应用程序时间太长、终止,或对系统时间作出相应。一个常见的原因是做在主线程上的同步联网。无论操作是线程0上,需要搬到后台线程,或处理方式不同,所以它不会阻止在主线程。
补充常见的Exception Type异常类型的信息:
1、EXC_BAD_ACCESS:此类型是最常见的crash, 通常用于访问了不该访问的内存导致的,一般EXC_BAD_ACCESS后面的()还会带有补充信息
野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。
2、SIGSEGV:通常由于重复释放对象导致, 一般在ARC以后很少见到
3、SIGABRT: 收到Abort信号退出, 通常Foundtion库中的容器为了保护状态正常会做一些检测, 例如插入nil到数据中等会遇到此类错误.
4、SEGV(Segmentation Violation):代表无效内存地址, 比如空指针, 未初始化指针, 栈溢出等.
5、SIGBUS:总栈错误, 与SIGSEGV不同的是, SIGSEGV访问的是无效的地址, 而SIGBUS访问的是有效的地址, 但是总栈访问异常(如地址对齐问题)
6、SIGILL: 尝试执行非法的指令, 可能不被识别或者没有权限
7、SIGFPE: 数学计算相关问题, 比如除零操作
8、SIGIPIPE: 管道另一端没有进程接手数据
9、EXC_BAD_INSTRUCTION:此类异常通常由于线程执行非法指令导致
10、EXC_ARITHMETIC:除零错误会抛出此类异常
Last Exception Backtrace: 最后异常回溯, 一般根据这个代码就能找到具体的crash问题
下面截取的是微信的crash blog
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x000000018223ff24 __psynch_cvwait + 8
1 libsystem_pthread.dylib 0x000000018230ad20 _pthread_cond_wait + 704
2 Foundation 0x0000000182f9fdf0 -[NSCondition waitUntilDate:] + 344
3 Foundation 0x0000000182f9ce34 -[NSConditionLock lockWhenCondition:beforeDate:] + 256
4 UIKit 0x000000018781dbc4 -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] + 196
5 UIKit 0x0000000187c05878 -[UIKeyboardImpl setKeyboardInputMode:userInitiated:] + 112
6 UIKit 0x0000000187c0de44 -[UIKeyboardImpl recomputeActiveInputModesWithExtensions:] + 336
7 UIKit 0x000000018781e8f0 -[UIKeyboardImpl setDelegate:force:] + 2292
8 UIKit 0x0000000187817eb0 -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:] + 1180
9 UIKit 0x00000001878179e4 -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] + 80
10 UIKit 0x0000000187879670 -[UIResponder becomeFirstResponder] + 600
11 UIKit 0x0000000187879a1c -[UIView(Hierarchy) becomeFirstResponder] + 148
12 UIKit 0x0000000187900b34 -[UITextField becomeFirstResponder] + 64
13 UIKit 0x00000001879b1fe4 -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) setFirstResponderIfNecessary] + 256
14 UIKit 0x00000001879b1498 -
我们可以看到发生Crash的线程的Crash调用栈, 从上到下分别代表调用顺序, 最上面的一个表示抛出异常的位置, 一次往下可以看到API调用顺序, 上图的信息表明本次Crash出现在[NSCondition waitUntilDate:]这个方法中(后面加的数值 我猜应该是地址偏移量 大概可以找到crash的具体原因(某个文件中的某个方法), 这样问题就浮出水面了, 方便产品上线后版本迭代, 修改BUG.
使用命令行工具symbolicatecrash
有时候Xcode不能够很好的符号化crash文件。我们这里介绍如何通过symbolicatecrash来手动符号化crash log。
在处理之前,请依然将“.app“, “.dSYM”和 ".crash"文件放到同一个目录下。现在打开终端(Terminal)然后输入如下的命令:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后输入命令:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log
现在,符号化的crash log就保存在appName.log中了。
Xcode自带工具symbolicatecrash解析iOS Crash文件
防止Crash
除了日常代码习惯良好严谨外 拦截存在潜在崩溃危险的方法,在拦截的方法里进行相应的处理,也可以防止方法的崩溃
- 1、通过category给类添加方法用来替换掉原本存在潜在崩溃的方法。
- 2、利用runtime方法交换,将系统方法替换成我们给类添加的新方法。
- 3、利用异常的捕获来防止程序的崩溃,并且进行相应的处理。
- 如果对异常NSException不了解,可以点击查看NSException的介绍。
解析符号化信息