iOS 如何快速有效的定位App carsh以及防护
为什么会产生carsh?
除去部分系统Bug,大多数carsh都是代码上有或多或少的问题,内存,性能,访问无效对象,线程死锁,等等都可能是造成app carsh的原因.
在日常开发中大多情况都是发现问题,测试复现,以及carsh定位来解决问题. 但是对于carsh的问题分析以及总结,其实是一个高级开发的快速解决问题的重要能力体现.
这里总结一些技巧与经验,方便以后自己或者其他人去查看.
如何定位carsh?
当app发生crash时,系统会生成crash report并存储在设备上。crash report会描述app在何种情况之下被系统终止运行,一般情况下描述会包括完整的线程调用堆栈,这对app的调试(和问题的定位)是非常有帮助的。所以你应当仔细研读这些crash report,去了解你的app究竟发生的是哪种crash,并尝试修复它们。注意: 这里需要将carsh report 使用atos符号化,否则看到的是一堆地址不利于快速定位
-
崩溃日志(crash log)
当你的app 发生crash时,一个没有被符号化的crash report会被创建并存储在设备上。
连接xcode 获取本地的carsh 流程如下
xcode->Window->Organizer->Crashes
screenshot.png
2.根据符号表来监测奔溃位置
Q:什么是符号表?
A:符号表就是指在Xcode项目编译后,在编译生成的二进制文件.app的同级目录下生成的同名的.dSYM文件。
.dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件。
Q:符号表有什么用
A:符号表就是用来符号化 crash report。crash report中有一些方法16进制的内存地址等,通过符号表就能找到对应的能够直观看到的方法名之类。
3.上传符号表到第三方监控平台
Q:为什么要第三方监控平台
A:方便开发者监控已经上线了的app在线上发生的问题.同时,有些问题可能测试无法发现到,这个时候解决只能依靠监控平台的日志.
Q: 有哪些第三方监控平台
A: 国内的友盟,腾讯Bugly, 笔者前司是海外项目,使用的是google 的Firebase Crashlytics,强烈安利很好用.
screenshot.png
carsh的类型
一. 常见Crash report 报错信息
1.是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存,一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。
2.SIGSEGV: 通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。
3.**SIGABRT: ** 收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。
4.SEGV:(Segmentation Violation),代表无效内存地址,比如空指针,未初始化指针,栈溢出等;
5.SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)
6.SIGILL:尝试执行非法的指令,可能不被识别或者没有权限7.EXC_BAD_INSTRUCTION
此类异常通常由于线程执行非法指令导致
8.EXC_ARITHMETIC除零错误会抛出此类异常
- Exception Code
**0xbaaaaaad **此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发
0xbad22222当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序
0x8badf00d这个前面已经介绍了,程序启动或者恢复时间过长被watch dog终止
0xc00010ff程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止
0xdead10cc程序退到后台时还占用系统资源,如通讯录被系统终止
0xdeadfa11前面也提到过,程序无响应用户强制关闭
二.常见的carsh 出现原因
2.1 新老操作系统兼容
现象: 在新 iOS 上正常的应用,到了老版本 iOS 上秒退
原因与解决: 常见原因是系统动态链接库或Framework无法找到。这种情况通常是由于 App 引用了一个新版操作系统里的动态库(或者某动态库的新版本)或只有新 iOS 支持的 Framework
项目里面使用了新系统才有的方法. 自己兼容一下
2.2.本地存储的数据结构改变
现象:程序在升级时,修改了本地存储的数据结构,但是对用户既存的旧数据没有做好升级,结果导致初始化时因为无法正确读取用户数据而秒退。
原因与解决: 第一种:是把服务端传过来的一些信息保存在本地,使用的时候从本地数据库取。
第二种:自己的一些数据存储在本地SQLlite,新版的时候表结构改了。
一般做法是在第一次创建表的时候加一些冗余字段,以防后面不时之需。但是如果真没办法需要在旧表上增加新字段了,那就要做数据迁移了。
2.3.访问的数据为空或访问数据类型不对
现象: 后端传回了空数据,客户端没有做对应的判断继续执行下去了,这样就产生了crash。或者自己本地的某个数据为空数据而去使用了。还有就是访问的数据类型不是期望的数据类型而产生崩溃。
原因与解决: 1. 服务端都加入默认值,不返回空内容或无key,但是服务端往往会不太愿意改,还有就是有些确实应该无值的话key也不用传,减少数据量的传输
2.这种就是客户端自己做判断,如果每次都是自己去if判断是否为空或格式是否正确那肯定是比较麻烦的。所以这里用到了NSArray和NSDictionary的Category。一般我们访问的数据都是NSArray或NSDictionary,所以在取值方法里面做一下判断,返回正确的数据类型或默认值即可。
这里一般有两种方案, 1. 直接hook系统方法进行增加安全判断.
- 自定义category safe 方法在此方法内进行安全处理一般调用safe方法进行加减值.
2.4.操作了不该操作的对象,野指针之类
原因: 空指针是没有存储任何内存地址的指针。如<code>Student *s1 = NULL;</code>和<code>Student *s2 = nil;</code>
而野指针是指指向一个已删除的对象("垃圾"内存既不可用内存)或未申请访问受限内存区域的指针。野指针是比较危险的。因为野指针指向的对象已经被释放了,不能用了,你再给被释放的对象发送消息就是违法的,所以会崩溃。
解决:我们可以使用instrement 进行检查
2.5 内存处理不当
说到因为内存处理不当崩溃就要涉及到内存管理问题了。内存管理是软件开发中一个重要的课题。iOS自从引入ARC机制后,对于内存的管理开发者好像轻松了很多,但是还会发生一些内存泄露之类的问题。
对于这一块知识点需要了解ARC的一些机制,还有用instruments排查内存泄露问题等。
2.6 主线程UI卡死
主线程被卡住是非常常见的场景,具体表现就是程序不响应任何的UI交互。这时按下调试的暂停按钮,查看堆栈,就可以看到是到底是死锁、死循环等,导致UI线程被卡住。这部分需要研究多线程,还有如何看调试栏里的线程的信息。
2.7.多线程之间切换访问引起的crash
多线程引起的崩溃大部分是因为使用数据库的时候多线程同时读写数据库而造成了crash。大多数为死锁相互竞争导致原因
注意: crash report里面有 常规的crash report,还有一种Low Memory Report
低内存 report的格式和其它crash report略有不同,它没有应用的堆栈信息。一个低内存 report的Header会和crash report的header有些类似。紧接着Header的时各个字段的系统级别的内存统计信息。记录下页大小(Page Size)字段。每一个进程的内存占用大小是根据内存的页的数量来 report的。
一个低内存 report最重要的部分是进程表格。这个表格列出了所有的运行进程,包括系统在生成低内存 report时的守护进程。如果一个进程被”遗弃”了,会在[原因]一列附上具体的原因。一个进程可能被遗弃的原因有:
当你发现一个低内存crash,与其去担心哪一部分的代码出现问题,还不如仔细审视一下自己的内存使用习惯和针对低内存告警(low-memory warning)的处理措施。Locating Memory Issues in Your App 列出了如何使用Leaks Instrument工具来检查内存泄漏,和如何使用Allocations Instrument的Mark Heap 功能来避免内存浪费。 Memory Usage Performance Guidelines 讨论了如何处理接受到低内存告警的问题,以及如何高效使用内存。当然,也推荐你去看下2010年的WWDC中的 Advanced Memory Analysis with Instruments 那一章节。
发现实在难以解决的carsh,无法定位,找不到确切的原因,项目又面临紧急上线的时候怎么处理? 偶现型的问题如何解决?
开发项目过程中,有时候,其实还是会遇到自己能力可能暂时无法解决,测试也无法复现,偶现型的问题,并且解决了也不好验证,Google,stackOverflow也无法查到详细资料的问题. 但是往往有时候这个问题还十分紧急. 可能整个测试组都在等着这个问题解决然后上线.
注意: 这里介绍一个ABTest小技巧.
虽然不能源头解决问题,但是可以缓解燃眉之急.
根据第三方平台或者后端约定一个下发数据, app启动后获取缓存在偏好设置或者归档解档数据库,随意
当遇到carsh 不确定的方案时候, 我们将分析原因制作A,B两种可能能解决的问题的方案, 根据下发的数据来控制AB方案,并且根据carsh监控平台实时监控,这样来看两种方案是否能解决偶现Bug的,如果A 上线后发生问题,或者无效,立刻切换B方案来解决.