The secondary pointer

Secondary Pointers, also called Pointers to Pointers, or Out Parameters, can be used to change the address value of a pointer. Since methods and functions in Objective-C do not allow multiple values to be returned, secondary Pointers are often used to do this. For example, NSFileManager’s – (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error method allows the method to create an error internally and pass it out.

The problem

In the Debug code, recently commented out with a secondary pointer parameter method of internal all code, and then in the Release environment installation operation, result unexpectedly Crash, suddenly remembered as colleagues also met before, examined, feel there is no question of the code, so continue to explore, has built an empty project, Duplicate the EXC_BAD_ACCESS Crash with very simple code as follows:

// main.m
#import 
#import 
@interface TestClass : NSObject
@end
@implementation TestClass
+ (void)runTest {
    NSMutableArray *array = [NSMutableArray new];
    [TestClass testFunc:&array];
    [TestClass testFunc:&array];
    NSLog(@"array: %@", array);
}
+ (void)testFunc:(NSMutableArray **)array {
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [TestClass runTest];
    }
    return 0;
}Copy the code

After testing, both Xcode 8.0 and the latest stable version 8.2.1 will Crash in the Release environment, which will be put into iOS projects, simulators and real computers. However, there is no problem with Xcode7.

Analysis of the

Source code analysis

TestFunc: is an empty method that passes NSMutableArray **array, but does not assign to it.

The runTest method also simply creates an NSMutableArray object, takes a pointer to a pointer, calls testFunc: twice, and prints, supposedly without any side effects.

So, looking directly at the code, you can’t find the cause of the problem.

Clang – rewrite – objc analysis

Clang-rewrite-objc main.m: testFunc: runTest: C++ runTest: C++ runTest: C++ runTest: C++ runTest: C++ runTest: C++ runTest: C++ runTest:

Static void _C_TestClass_runTest(Class self, SEL _cmd) { NSMutableArray *array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("new")); ((void (*)(id, SEL, NSMutableArray **))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("testFunc:"), &array); ((void (*)(id, SEL, NSMutableArray **))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("testFunc:"), &array); NSLog((NSString *)&__NSConstantStringImpl__var_folders__5_6lqsnn195jj6pw61j66npcnm0000gn_T_main_5a7341_mi_0, array); } static void _C_TestClass_testFunc_(Class self, SEL _cmd, NSMutableArray **array) {}Copy the code

From the C++ code, there is also no dealloc code that could cause EXC_BAD_ACCESS issues, continue!

Hopper Disassembler decompiler analysis

Rewrite C++ code to solve the problem, so let Hopper play, after all, “assembly, no secrets” =. =, take the x86_64 architecture binary as an example.

TestFunc:

As expected, there is no “working code”.

Now look at the runTest method:

To facilitate analysis, the decompiled assembly code is divided into sections:

  1. callNSMutableArray newCreate an array variable and store it in register R15
  2. First calltestFunc:
  3. First,retainAnd thenrelease, RBX saves array, r15 frees
  4. Second calltestFunc:
  5. First,releaseAgain,retainRelease RBX, then retain, there is clearly a problem
  6. callNSlogprint
  7. Finally release releases the array variable

For further verification, use Hopper’s disassembler function to generate Objective-C code for verification. The generated code is as follows:

Void +[TestClass runTest](void * self, Void * _cmd) {// create r15 = [NSMutableArray new]; TestFunc: [TestClass testFunc:r15]; // retain then release RBX = [r15 retain]; [r15 release]; TestFunc: [TestClass testFunc: RBX]; / / release first!!!!!! [rbx release]; // Then retain RBX = [RBX retain]; // NSLog prints NSLog(@"array: %@", RBX); // release [RBX release]; return; }Copy the code

Hopper generated the same code as the assembly analysis. So far, the only “abnormal” code is the “release first, then retain” in paragraph 5, which causes the array variable to be released prematurely and the subsequent retain to be invalid. EXC_BAD_ACCESS crashes when access to released variables occurs during NSLog.

Contrast Xcode7

Now the problem is basically fixed, but why didn’t Xcode7 Crash? Decompile to find out.

Xcode7 Release generates the executable binary, decombuilds it, uses Hopper to generate Objective-C code, and compares it with Xcode8:

As you can see, after Xcode8 was compiled, release and retain were in the wrong order, causing the array variable to be released early, resulting in the EXC_BAD_ACCESS error. Xcode7 compiles just fine.

Summary of the cause of the problem

It’s a safe guess that the reason for EXC_BAD_ACCESS is a compilation error in Xcode8. When compiling the above code, Xcode8 failed to properly handle the release/retain order of array variables passed as second-level Pointers to testFunc. As a result, array was released early, resulting in EXC_BAD_ACCESS errors.

Now I have submitted a Bug report to Xcode, waiting for Apple’s reply ~

All the code, binary executables compiled by Xcode7 and 8, and Hopper files are packaged on Github: github.com/zekunyan/Ou…

conclusion

Assembly face, no secrets! =. =