Address Sanitizer 初探

前言

文中分享都基于自己的demo,完全模拟事发场景。

起因

前段时间,频繁收到不少QA童鞋报的crash。操作步骤各不相同,崩溃栈也没一个一样的,大致如下:

Address Sanitizer 初探_第1张图片
崩溃1

Address Sanitizer 初探_第2张图片
崩溃2

Address Sanitizer 初探_第3张图片
崩溃3

酱紫的崩溃,反正我看着是挺绝望的。。

Address Sanitizer 初探_第4张图片
感觉身体被掏空

完全是毫无头绪啊~~~

问题进展

挂了四五天的bug,期间经过不懈的努力,最后找到大致的复现规律了。在组内大腿的建议下,第一次使用起Address Sanitizer。

打开Address Sanitizer。run起来后,按照之前的复现步骤,居然一下就断住了。

Address Sanitizer 初探_第5张图片
捕获异常

显然,这是访问一个已经释放了的指针,的确是有问题。

但是。 这会跟之前的崩溃是一个原因么?

【PS:alloc和dealloc的地方,是继承的引擎库,没有代码权限,无法直接修改验证】

反正其他方式也没有进展,就当这个就是问题原因吧,那么继续分析下这样的操作之后,会发生什么事。

原因定位

搞个新项目,在vc的touchBegin中,加入如下代码

unsigned int size = 8; // size = 8,比较容易命中。
    buffer = malloc(size);
    snprintf(buffer, size, "Hello!");
    NSLog(@"%p, %s", buffer, buffer);
    free(buffer);
    
    // memory history <#expression#>
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 在未来的某个时机,改变该指针的值
        snprintf(buffer, size, "Hello!");
    });

这个与刚刚捕获到的异常,同样的是use-after-free。

run起来,碰了下viewController,咦,没崩?

Address Sanitizer 初探_第6张图片
弄啥嘞

再点了两下看看。

Boom~

特喵,再打开,再点! Boom~~

我不信,我点、我点、我点点点! Boom~~


导出crashLog,符号化之~


果然,几次崩溃完全不一样,全都在系统库中。

Address Sanitizer 初探_第7张图片

问题剖析

到这里,基本上定位到问题原因了。但是,为什么会乱蹦呢?

不弄清楚,不能忍!

打开刚刚新建的项目,搞俩断点

Address Sanitizer 初探_第8张图片

run ---> touch

走到断点A。同时控制台输出Log

2017-06-22 16:02:41.993918+0800 WWDC_Demo[9323:2875577] 0x148e512c0, Hello!

copy 上面的内存地址0x148e512c0。 cmd+shift+M,进入到View Memory界面,输入刚刚copy的内存地址。

Address Sanitizer 初探_第9张图片
查看内存

这时候能看到,右侧Hello! 这是刚代码中给buffer赋的值。

代码继续往下走,走过free(buffer).

再次cmd+shift+M,进入到View Memory界面,输入刚刚copy的内存地址。


友情提醒

注意,这时候不能直接点击右侧的树状结构中,之前生成的内存。那个不会实时更新,显示的是当时的内存内容。

Address Sanitizer 初探_第10张图片
每次打开ViewMemory,右侧会出现对应的树状结构

回到原来的话题,这时候发现,内存块的值,已经不再是Hello!

Address Sanitizer 初探_第11张图片
原来buffer的内存地址,值已经改变

纳尼?值变了?

咱们刚的代码中,明明只是free,释放了这块内存地址呀,并没有改变他的值。

难道说,刚刚free之后,马上就有其他地方申请到了这块内存地址,并修改了?


再次验证,修改代码中,malloc的size为1028。重新来一次之前的步骤。发现free后值依然是Hello! 。 也就是那块内存地址还没被申请,同样的,之后的操作也不会崩溃。

Address Sanitizer 初探_第12张图片
断点A、B均如此
再次提醒

修改size为1028,并不一定内存地址free后就不会再被重新申请了,只是概率较低。所以即使再被重新申请了,别慌,再来一次。~

另外,一旦被重新申请,那么程序继续跑下去,必然会导致崩溃在莫名其妙的地方。
反之,在断点B之前,还没有被重新申请,则不会崩溃。

结论

通过C语言,释放内存地址后,再操作内存区域。【oc是c的进一步封装,alloc和dealloc应该都有保护代码,操作基本都会马上崩溃】

  • 如果此时这块内存区域尚未被申请,还不会有问题。
  • 假如此时这块内存区域已经被申请,修改后,并不一定会马上出问题,待申请方重新使用的时候,就有可能崩溃。

当然,不管以上哪一种情况,Address Sanitizer都能捕获到。

Address Sanitizer原理简述

  • 在申请的内存地址两侧插入对应的redzone ==> 检测Overflow
  • 延迟已被free的堆空间的重用 ==> 检测Use-after-free
  • 访问某内存时,会检查其对应的shadow memory的state。
  • 管理shadow memory,同时保证shadow空间不被使用

PS:shadow memory --- 影子内存,每8个字节的内存会映射8位(1字节)的影子内存,用于表示对应内存的状态。
当检测到某块内存对应的shadow memory为0时,说明这块内存是OK,可使用的,反之,则捕获异常。


以上,如有描述不当,或者理解不对的地方,欢迎指出~~

你可能感兴趣的:(Address Sanitizer 初探)