title: Crash 收集
date: 2016-08-05 09:30:49
tags: 崩溃日志
thumbnail: http://images.17173.com/2012/news/2012/12/18/hxz1218mwjs05s.jpg
通常情况下, App中会做崩溃的日志收集, 以便进行问题的追踪。奋斗提供了多个平台日志捕获的代码片断。
崩溃捕获
在iOS中碰到异常之后, 捕获异常, 在Log的位置做日志记录, 崩溃次数统计, 下次启动可以做日志上传或者应用启动保护。
下边的runLoop
相关的代码的所用是把当前App的RunLoop
跑起来, 让程序可以继续运作, 但是某些功能可能无法使用, 可以避免大部分闪退。这段代码是很久以前学自孙源
。
下边的代码可以满足最简单的日志收集。成熟的开源项目有 KSCrash
、plcrashreporter
、CrashKit
等。如果想自己的做的更全面就要涉及到更多的知识了, 这篇文章可以帮到你漫谈iOS Crash收集, 如果细读的话, 请注意多个 Crash 日志收集服务共存的坑
。
-(BOOL)install{
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
return YES;
}
void UncaughtExceptionHandler(NSException *exception) {
NSArray *callStack = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *content = [NSString stringWithFormat:@"\n异常错误报告\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];
DLogOut(@"%@", content);
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray * modes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString * mode in modes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
}
信息上传
收集到错误信息后, 在第二次启动的时候自然要上传用户的崩溃信息。
一般来说, 部分程序提供了关闭日志与错误搜集这个功能的开关。但是大部分程序都是默认开启这项功能, 包括苹果一直在默默的收集Crash日志。
主要的流程:-ApplicationDidFinishLaunch
之后首先检查用户是否开启了错误收集与反馈, 进而决定是否上传错误日志。 判断本地是否存在未上报的错误日志文件, 如果符合条件, 上报当前的崩溃日志, 清空本地日志。一个简单的网络请求而已, 这里不做代码的展示。 当然, App也可以合理的提供一些BUG反馈的界面, 如果没有提供可以使用友盟
、蒲公英
类似的摇一摇反馈等。
程序崩溃保护
- 启动崩溃
程序有可能在启动的时候就崩溃了, 这怎么办呢?微信读书给出了很好的方案, 原文在这里启动连续闪退保护。原理其实很简单, 捕获到程序在一定时间内没有退出的话, 为正常启动。否则清零计数, 当错误计数达到了设置的最大次数时候, 调用Block进行用户自己的一些处理(文中一处有思路问题:程序启动用户就滑掉了, 其实不会影响的, 只要在AppDelegate-程序退入后台的方法中做一些手脚就好了, 滑掉应用也是要先退入后台)。 总体来说框架思路还是很直接的, 程序启动嘛, 5s差不多用户已经进去了, 所以代码达到了作者的初始思路。光是启动保护其实并没有什么大的用处, 可以在自己做的时候加入一些更好的处理方式。启动崩溃使用上边提到的框架可以解决部分问题。
- 使用中崩溃
在使用中崩溃, 在调试的时候, 有很多次实际是崩溃到了main
函数中, 在我们打开全局断电以及僵尸模式等调试设置后, 大部分的崩溃还是会定位到出错的位置, 但是我们在不调试的时候怎么知道某一个用户是崩溃到什么位置呢?因为什么崩溃?还是要牵扯到最开始提到的Crash捕获, 在这里我们就要做错误的分析。通常的崩溃的callstack
中我们可以得到部分想要的信息, 然后就是联合判断。
实际我们做的, 所有的Model都继承基类, 会有自己的标识, 被持有的对象标识等, 其他视图的基类也有对应的标识。建议大家这么做, 任何时候都有好处。
核心的思路是, 获取到Window当前的VC, 然后运行时取出这个VC相关的信息, 进行定位与分析。
这个方法可以准确的定位到80%的错误发生, 可以直接定位到具体的VC, 然后结合callStack
中的一些信息就可以方便的定位到错误。这样, 我们定位错误的能力提升到80%, 在加上我们对应用的数据打点以及用户的行为追踪, 定位错误的能力提升了更多!。
这里提供一片关于BUG信息分析的文章iOS崩溃日志解密。当然为了懒, 你也可以使用念茜提到的Crashlytics
, Hockeyapp
这样一体的解决方案。
文章到此结束, 下边是本周干货
杀死野指针(MRC老项目细看)
- 开发期间
开发中我们发现, 哎呦, 我们自己测试的没有问题, 为什么别人一用就有问题, 或者是偶尔出现问题?这就是已经被释放的对象在搞鬼。有些对象被release了, 但是他占用的那一块内存保存完好, 有可能不Crash, 访问了不可访问的数据, 肯定会Crash。对象释放后内存被改动过,写上了可以访问的数据,但是再次访问的时候执行的代码把别的数据写坏了,遇到这种Crash只能哭了(随机Crash,难度大,概率低), 对象释放后再次release(几乎是必现Crash,但也有例外,很常见)。
- 加入全局断点
- 做下边的配置

在调试中, 看看就知道, 总之我知道最常见的野指针是会出来见你的。
但是这是你插着调试, 如果打包内测就没有办法了, 所以这里我们还提供另一种方法, HOOK, 涉及到的方法一般是dealloc,object_dispose,free。这里我们直接选择去hook
C语言的free方法, 这里用到了facebook·fishhook, 在Main函数中直接进行Hook。
#import "fishhook.h"
#import <malloc/malloc.h>
static void (*origin_free)(void *);
void hook_free(void * p) {
size_t memSiziee = malloc_size(p);
memset(p, 0x55, memSiziee);
printf("call origin free");
origin_free(p);
return;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
// Hook free method.
rebind_symbols((struct rebinding[1]){{"free", hook_free, (void *)&origin_free}}, 1);
return
UIApplicationMain(argc, argv, nil, NSStringFromClass([INSApp class]));
}
}
这个时候运行程序发现调用了我们的hook-free方法, 这里为什么把已经被free的内容覆盖为0x55呢?
实际上填写数据的关键在于填写数据后其地址指向不可读的内存。而填写0x55,和出现异常情况的对象地址0x555555连接起来被当成指针使用的话,就会被识别为0x55555555,而CPU访问这个地址就会抛出异常。另外一点,就是方便区分野指针,例如在Xcode启用Enable Scribble时,指定alloc之后填写的地址为0xaa,防止内存初始化就使用,也是为了方便和free之后的内存做区分。
下边的文章自行细读, 我就不在这里扯了 ~
网友评论