原文地址:http://bugly.qq.com/blog/?p=200
腾讯Bugly(http://bugly.qq.com)
是的,你没有看错,要说的就是提高iOS的crash率!
欲让其灭亡先让其疯狂,我们当然不是人为制造crash,准确地说,是使隐藏的随机性crash暴露出来,提高测试时的crash率,从而降低版本发布后的crash率。
写c、c++代码的同学应该都清楚,crash最多的原因通常有两种,一种是多线程,一种是野指针。这两种crash都带随机性,而且这两种crash有相当一部分都很难区分,甚至大量的crash只有系统栈,如果不能根据日志重现,几乎是无解,让人非常蛋疼。
本文主要讨论的方向是objc的野指针。objc的野指针最常见的一种栈是objc_msgSend,从Bugly上报的Crash数据来看,objc_msgSend的量占了五分之一,这其中大多数是objc野指针。当然也有相当多的objc野指针不是这种表现,所以野指针的crash体量很惊人。
为什么Obj-C野指针的Crash那么多?
我们有这么多自动化和人工测试流程,而且还有几轮的灰度过程,其实很多crash场景都应该已经覆盖到了,但随机性意味着,测试的时候它没有问题,等用户用了才有问题,这种情况该怎么办?!
我觉得关键在于它的随机性,随机性问题我初略地分为两类:
第一类是跑不进出错的逻辑,执行不到出错的代码,这种可以提高测试场景覆盖度来解决。
第二类是跑进了有问题的逻辑,但是野指针指向的地址并不一定会导致crash,这好像要看人品了?
一说到人品就头疼啊有木有,由于上辈子做了太多善事,人品太好每次自测的时候根本不crash有木有!
先来分析分析
野指针是指指向一个已删除的对象或未申请访问受限内存区域的指针。本文说的objc野指针,说的是objc对象释放之后指针未置空,导致的野指针(objc里面一般不会出现为初始化对象的常识性错误)。
既然是访问已经释放的对象为什么不是必现crash呢?
因为dealloc执行后只是告诉系统,这片内存我不用了,而系统并没有就让这片内存不能访问。
现实大概是下面几种可能的情况:
参考下面的这张图:
看看下面的代码,明显有问题,但是大部分时候是不会crash的。
UIView* testObj=[[UIView alloc] init];
[testObj release];
[testObj setNeedsLayout];
但是这个放在用户那边或者不是UIView这个类就不好说了,crash率可能飕飕就上去了!
让随机变成不随机
从上面列的情况来看,出现随机Crash的情况有很多种!这是得多蛋疼呢!或许最好的办法让他们全都立马crash,然后把野指针都找出来!
仔细看看上面的关键路径只有出现被随机填入的数据是不可访问的时候才会必现crash。
这个地方我们可以做一下手脚,把这一随机的过程变成不随机的过程。对象释放后在内存上填上不可访问的数据,其实这种技术其实一直都有,xcode的Enable Scribble就是这个作用。
下面我们就拿刚刚的代码试一下。
scheme=>diagnostics=>Enable Scribble
果然,必现了,0x5555561!!
但是有个问题:这货不能放在测试同学那边用!因为总不能让测试同学装了xcode来测试吧?
于是我们自己动手实现一个,这个过程中我们要解决几个问题:
上hook后的free代码:
void safe_free(void* p){
size_t memSiziee=malloc_size(p);
memset(p, 0x55, memSiziee);
orig_free(p);
return;
}
测试一下,出现了和Enable Scribble一样的crash!
重复造了这个xcode的轮子之后,以后编包给测试,终于在某些情况下不需要那么拼人品了。但是这仅仅覆盖了众多野指针中的一部分,还有大量的疑问等着继续解答。比如:
小编有话说
笔者的经验告诉我们:正视问题,才有机会把它解决。
开发者在开发过程中,如果能够秉持不规避问题的心态,尽可能多的暴露问题、解决问题。那这个产品正在走向优秀的路途上。
不总结哪来经验,不分享经验何用?
在此小编号召大家多总结,互分享,踊跃给我们投稿,把自己踩过并爬出来的坑树个指示牌警醒后人,让猿们的开发生活更加美好!