preface

In the development of iOS, the problem of memory management has always been a problem to be solved. Crash reason = EXC_BAD_ACCESS is an error that occurs frequently. The occurrence of similar problems is usually a memory management problem, and the probability is wild pointer problem. The object that the pointer points to has been recycled, but the pointer still exists. If the object is still operating through the pointer, a bad memory access error will be reported.

How do you pick out bad Pointers

In the Xcode development tool of the system, check the option of Zombie Objects in the following operation path: Product -> Scheme -> Edit Scheme -> Diagnostics Earlier versions of Xcode added the variable NSZombieEnabled = YES to the Environment Variables under Arguments

Once configured, you’ll notice that Xcode takes up more memory than it did before, so you can guess why?

The previous problems were clearly exposed, for example

*** -[Student test]: message sent to deallocated instance 0x6000038a2ac0
Copy the code

The Student object has a memory management problem with wild Pointers. The Student object has a memory management problem with wild Pointers.

Skilled and magical craftsmanship

1. Print Class superClass

How does the system work? We can find some clues with the help of runtime.

Perform the following operations

    Student* st = [Student new];
    
    printClassInfo(st);
    
    [st release];
    
    printClassInfo(st);
    
    [st test];
Copy the code
  1. You can see that in the first printClassInfoself:Student - superClass:NSObject
  2. So after release, the second printClassInfo prints outself:_NSZombie_Student - superClass:nil
  3. Student = _NSZombie_Student = _NSZombie_Student = _NSZombie_Student;
  4. NSObject _dealloc_zombie (NSObject _dealloc_zombie) is in the call stack after the object release is called.
  5. Add a Symbolic Breakpoint for _dealloc_zombie in Xcode;
  6. After the restart, the following assembly code logic is displayed

2. The logic behind __dealloc_zombie

CoreFoundation`-[NSObject(NSObject) __dealloc_zombie]:
->  0x109509b28 <+0>:   pushq  %rbp
    0x109509b29 <+1>:   movq   %rsp, %rbp
    0x109509b2c <+4>:   pushq  %r14
    0x109509b2e <+6>:   pushq  %rbx
    0x109509b2f <+7>:   subq   $0x10, %rsp
    0x109509b33 <+11>:  movq   0x2dd60e(%rip), %rax      ; (void *)0x000000010ac290b0: __stack_chk_guard
    0x109509b3a <+18>:  movq   (%rax), %rax
    0x109509b3d <+21>:  movq   %rax, -0x18(%rbp)
    0x109509b41 <+25>:  testq  %rdi, %rdi
    0x109509b44 <+28>:  js     0x109509be3               ; <+187>
    0x109509b4a <+34>:  movq   %rdi, %rbx
    0x109509b4d <+37>:  cmpb   $0x0, 0x4856ac(%rip)      ; __CFConstantStringClassReferencePtr + 7
    0x109509b54 <+44>:  je     0x109509bfc               ; <+212>
    0x109509b5a <+50>:  movq   %rbx, %rdi
    0x109509b5d <+53>:  callq  0x10958455a               ; symbol stub for: object_getClass
    0x109509b62 <+58>:  leaq   -0x20(%rbp), %r14
    0x109509b66 <+62>:  movq   $0x0, (%r14)
    0x109509b6d <+69>:  movq   %rax, %rdi
    0x109509b70 <+72>:  callq  0x109584068               ; symbol stub for: class_getName
    0x109509b75 <+77>:  leaq   0x24086f(%rip), %rsi      ; "_NSZombie_%s"
    0x109509b7c <+84>:  movq   %r14, %rdi
    0x109509b7f <+87>:  movq   %rax, %rdx
    0x109509b82 <+90>:  xorl   %eax, %eax
    0x109509b84 <+92>:  callq  0x109583f90               ; symbol stub for: asprintf
    0x109509b89 <+97>:  movq   (%r14), %rdi
    0x109509b8c <+100>: callq  0x1095844c4               ; symbol stub for: objc_lookUpClass
    0x109509b91 <+105>: movq   %rax, %r14
    0x109509b94 <+108>: testq  %rax, %rax
    0x109509b97 <+111>: jne    0x109509bb6               ; <+142>
    0x109509b99 <+113>: leaq   0x240282(%rip), %rdi      ; "_NSZombie_"
    0x109509ba0 <+120>: callq  0x1095844c4               ; symbol stub for: objc_lookUpClass
    0x109509ba5 <+125>: movq   -0x20(%rbp), %rsi
    0x109509ba9 <+129>: movq   %rax, %rdi
    0x109509bac <+132>: xorl   %edx, %edx
    0x109509bae <+134>: callq  0x109584476               ; symbol stub for: objc_duplicateClass
    0x109509bb3 <+139>: movq   %rax, %r14
    0x109509bb6 <+142>: movq   -0x20(%rbp), %rdi
    0x109509bba <+146>: callq  0x10958423c               ; symbol stub for: free
    0x109509bbf <+151>: movq   %rbx, %rdi
    0x109509bc2 <+154>: callq  0x109584470               ; symbol stub for: objc_destructInstance
    0x109509bc7 <+159>: movq   %rbx, %rdi
    0x109509bca <+162>: movq   %r14, %rsi
    0x109509bcd <+165>: callq  0x109584572               ; symbol stub for: object_setClass
    0x109509bd2 <+170>: cmpb   $0x0, 0x485628(%rip)      ; __CFZombieEnabled
Copy the code

So the logic behind this approach goes something like this

  1. Object_getClass Gets the current object’s class;
  2. Class_getName gets the string corresponding to the current object class.
  3. A string concatenation _NSZombie_%s, using objc_lookUpClass to find if a zombie class exists, such as _NSZombie_Student;
  4. If it does not exist, create it again by obtaining the objc_lookUpClass template class _NSZombie_;
  5. Then make a copy of _NSZombie_Student with objc_duplicateClass, based on class _NSZombie_;
  6. Unlike objc_allocateClassPair, which recreates a subclass from a parent class, objc_duplicateClass simply copies a copy, not a subclass. So the superclass that got _NSZombie_Student before was nil;
  7. Objc_destructInstance is then used to disassociate the object completely and its memory is not actually reclaimed.
  8. Finally, call object_setClass to redirect the Student’s ISA to _NSZombie_Student;
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; } id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; }Copy the code

The difference is that object_Dispose has one more step free(OBj), which is the true memory release.

Debugging, next into ___forwarding___, message forwarding logic.

3. forwarding

_NSZombie_Student does not implement this method. _NSZombie_ is an empty class that does not have any methods implemented, so any method on a Zombied object that calls any method after memory is freed, It goes into the logic of message forwarding.

In the message forwarding implementation class, print out the corresponding class and method information and choose forced crash.

Simple code implementation

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = NSSelectorFromString(@"dealloc");
        SEL swizzledSelector = @selector(_dealloc_zombie);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

-(void)_dealloc_zombie{
    
    __NSZombie_Enabled(self);
}

void __NSZombie_Enabled(id obj){
    
    Class baseClass = object_getClass(obj);
    const char *className = class_getName(baseClass);
    const char *zombieClassName = [NSString stringWithFormat:@"_NSZombie_%s",className].UTF8String;
    Class zombieClass = objc_lookUpClass(zombieClassName);
    if (!zombieClass) {
        Class baseZombieClass = objc_lookUpClass("_NSZombie_");
        zombieClass = objc_duplicateClass(baseZombieClass, zombieClassName, 0);
        class_addMethod(zombieClass, @selector(forwardingTargetForSelector:),(IMP)forwardSelector, "@@::");
        objc_destructInstance(obj);
        object_setClass(obj, zombieClass);
    }
}

void forwardSelector(NSObject* self, SEL selector, SEL forwardSelector){
    
    Class zombieClass = object_getClass(self);
    NSString* zombieClassName = [NSString stringWithUTF8String:class_getName(zombieClass)];
    NSString* originalClassName = [zombieClassName substringFromIndex:10];
    NSLog(@"*** - [%@ %@]: message sent to deallocated instance %p", originalClassName, NSStringFromSelector(forwardSelector), self);
    abort();
}
Copy the code

*** – [Student test]: Message sent to deallocated Instance 0x600003A78360