反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash
更新
- 在最新的Xcode 9 Beta版本中,此Bug已被苹果修复:Bug ID 30530580: Crash on “Release” Build Configuration scheme setting for Out Parameters code
二级指针
二级指针,也叫指针的指针,或者Out Parameters,可以用来改变一个指针的地址值,由于在Objective-C里面方法、函数不支持返回多个值,所以经常用二级指针实现这个功能,比如NSFileManager
的- (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error
方法,就可以让方法在内部创建error后传出。
问题
最近在Debug代码的时候,注释掉了一个带有二级指针参数的方法内部所有代码,然后在Release环境下安装运行,结果居然Crash了,猛然想起好像以前同事也遇到过,仔细检查了下,感觉代码是没有问题的,所以继续深究,新建了一个空的工程,重现了这个EXC_BAD_ACCESS的Crash,代码非常简单,如下:
1 | // main.m |
经测试,在Xcode 8.0、以及最新的8.2.1稳定版下,Release环境中,都会Crash,放到iOS工程里面,模拟器、真机也会。但是,在Xcode7下就没有问题。
分析
源码分析
首先,从代码本身上来看,是没有问题的,带有二级指针的方法testFunc:
是空的方法,虽然传进来了NSMutableArray **array
,但是没有对其赋值。
runTest
方法也只是创建了一个NSMutableArray
对象,然后取了指针的指针,调用了两次testFunc:
,然后打印,按理来说是没有任何副作用的。
所以,直接从代码上看,发现不了问题的原因。
clang -rewrite-objc分析
直接看代码没有用,那就用clang重写出C++代码分析,命令行对main.m
执行clang -rewrite-objc main.m
,得到C++重写后的代码,在文件的最后,就能找到testFunc:
和runTest
的C++代码,如下:
1 | // runTest的C++代码 |
从C++代码来看,也没有可能会导致EXC_BAD_ACCESS问题的释放、dealloc之类的代码,继续!
Hopper Disassembler反编译分析
重写出C++代码无法解决,就只能让Hopper出马了,毕竟“汇编面前,没有秘密”=。=,以x86_64架构二进制为例。
先来看testFunc:
的:

跟预想的一致,没有任何“有效代码”。
再看runTest
方法:

为了方便分析,把反编译出来的汇编代码分了段:
- 调用
NSMutableArray new
创建array变量,保存在r15寄存器上 - 第一次调用
testFunc:
- 先
retain
,然后release
,rbx保存array,r15释放 - 第二次调用
testFunc:
- 先
release
,再retain
,即释放了rbx,然后又retain,这里明显有问题 - 调用
NSlog
打印 - 最后release释放array变量
为了进一步验证,用Hopper的反汇编生成Objective-C代码功能进行验证,生成的代码如下:
1 | // Xcode8 Release环境编译的x86_64二进制反编译后生成的代码 |
release和retain的顺序错误
Hopper生成的代码跟汇编分析基本一致,至此,可以看出,唯一“不正常”的代码就是第5段的“先release,再retain”,这会导致array变量提前释放,以至于后面再次retain无效,NSLog的时候,出现访问已被释放的变量,于是出现EXC_BAD_ACCESS崩溃。
对比Xcode7
现在已经基本可以确定问题了,但是为什么Xcode7就不会Crash?反编译一探究竟。
相同的代码,Xcode7 Release环境编译出可执行二进制文件,反编译后,用Hopper生成Objective-C代码,然后跟Xcode8的做对比,如下:

可以看出,Xcode8编译后,release和retain的顺序错了,导致了array变量提前被释放,继而EXC_BAD_ACCESS错误。而Xcode7编译的一切正常。
问题原因小结
现在可以大胆猜测,EXC_BAD_ACCESS的原因就是Xcode8的编译错误。
在编译上面的代码时,Xcode8不能正确的处理array变量作为二级指针传入testFunc空函数后的release、retain顺序,导致array被提前释放,最终产生EXC_BAD_ACCESS错误。
目前已经给Xcode提了Bug report,等待苹果的回复~
所有的代码、Xcode7和8编译出的二进制可执行文件、Hopper的文件,打包放在了Github上:https://github.com/zekunyan/OutParameterPointerCrashOnXcode8
总结
汇编面前,没有秘密!=。=