[iOS study notes] - talk about wild pointer and zombie object positioning

[iOS study notes] - talk about wild pointer and zombie object positioning

1, Start with an exception

In the development of iOS projects, we will encounter some Crash situations more or less. Most of the exceptions thrown by Crash are from the NSException layer. Such exceptions are caused by OC layer code problems. Usually, the stack information and exception prompt information are very clear, which can be directly located to the problem code, so it is not difficult to solve such problems. Exceptions that can cause Crash include exceptions in Unix layer and Mach layer in addition to nsexceptions.

Mach exceptions are generally low-level kernel level exceptions. We can capture such exceptions through some low-level API s. This is not the content of this article and will not be repeated here. Unix layer means that Mach exceptions not captured by developers will be converted into corresponding Unix signals and passed to the error thread.

If you have exceptions like exc in the iOS project online collection_ BAD_ If the access is abnormal, the high probability is caused by the wild pointer caused by the memory problem. This is also the core content of this paper.

1. What is a wild pointer?

At present, we mostly use ARC for memory management when writing iOS programs. Generally, we don't need to care too much about memory management. However, this does not mean that memory problems will not occur. In principle, when we create any object, we will first apply for a piece of memory space from the memory through the operating system for the object to use, and save the address of the memory space in the pointer for us to reference this memory conveniently in the code. When this object is destroyed, in principle, we need to do two things: one is to return this memory, and then the operating system can reuse this memory and allocate it to other applicants; the other is to empty and recycle the pointer in the code. This can ensure the sustainable and healthy operation of the program. The working process is shown in the figure below:

But no matter in life or programming, accidents always happen. Usually, there are few problems in applying for memory from the operating system. The stability of the operating system itself is much stronger than that of the application program. Most problems occur when memory is released. There may be two kinds of problems:

One is the object that no longer needs to be used. We directly cleared the pointer variable, but did not tell the operating system to recycle this memory. After that, there is no place to store the address of this memory in the program, and this memory will never be used and recycled. In this case, this memory becomes a non main memory and the operating system does not know it, resulting in the memory leakage problem we often say. With the running time of the application getting longer and longer, the memory leakage may increase, resulting in insufficient memory and the program can no longer run normally.

The other is that we tell the operating system to reclaim this memory, and this memory is actually reclaimed, but there are still pointer variables in the program, and this address is not cleared. At this time, this pointer becomes a pointer because the memory it points to has been reclaimed, We don't know whether this memory is used again or still stores the original data. After that, if we accidentally use the data in this memory through this pointer, all kinds of strange problems will occur regardless of reading and writing, and it is difficult for us to locate. In this paper, we mainly talk about the causes and location methods of this kind of field pointer problem.

2. What problems will arise from the wild pointer?

Most of the Excs we encountered in development_ BAD_ Access problems are caused by wild pointers. There are mainly two kinds of signals: SIGSEGV and SIGBUS. SIGSEGV indicates that the address of the operation is illegal, accessing unallocated memory or writing to memory without write permission. SIGBUS indicates an incorrect memory type access.

The problems caused by the wild pointer are strange and difficult to locate. When the wild pointer is used in the program, there may be two scenarios:

1> The accessed memory is not overwritten

If the other objects that the original object depends on are not deleted, it looks like there is any problem with the operation of the program, but it is actually very dangerous, and the logical performance of the program is uncontrollable.

If other objects that the original object depends on are deleted, other wild pointers may be generated internally, and various complex exception scenarios will still occur.

2> The accessed memory was overwritten again

This slave scenario will be more troublesome. If the accessibility of the current memory area changes, many types of exceptions will be generated, such as objc_msgSend failed, SIGBUS address type exception, SIGFPE operator exception, SIGILL instruction exception, etc.

If the current memory is accessible, it may violate our intention to write the memory used in other places, causing exceptions in other places. It is also possible that the data type to be used does not match our original object, resulting in errors in unimplemented selector classes, lookup method classes, various underlying logic errors, malloc errors, etc. At this time, it is very difficult to check the problem.

To sum up, the harm of wild pointer is very great. In addition to causing abnormal Crash itself, it may also cause exceptions to other normally used codes, which are non reproducible and random. For example, you may find that the stack of a Crash calls a method of an object, but you can't find similar method calls through the code, In fact, there is a problem with the wild pointer in other places. Then the correct object is just allocated to the memory pointed to by the wild pointer, which destroys the data in this memory. There is almost nothing we can do about this Crash problem.

3. Try to create a wild pointer scene

Through the previous introduction, we understand the causes and hazards of the wild pointer problem. You can try it now. Use Xcode to create a new iOS project. Create a new class named MyObject and add an attribute as follows:

#import <Foundation/Foundation.h>

@interface MyObject : NSObject

@property(copy) NSString *name;

@end

Write the following test code in the ViewController class:

#import "ViewController.h"
#import "MyObject.h"
@interface ViewController ()

@property (nonatomic, unsafe_unretained)MyObject *object;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    MyObject *object = [[MyObject alloc] init];
    self.object = object;
    self.object.name = @"HelloWorld";
    void *p = (__bridge void *)(self.object);
    NSLog(@"%p,%@",self.object,self.object.name);
    NSLog(@"%p,%@",p, [(__bridge MyObject *)p name]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%p",self->_object);
    NSLog(@"%@",self.object.name);
}

@end

Here, we manually create a scenario where there will be wild pointer problems. The object attribute of the ViewController class is declared as unsafe_unretained, this modifier means that the current property is not managed by ARC, and the pointer will not be null after the referenced object is released. In the above code, we created a MyObject object in the viewDidLoad method and copied it to the object attribute of the current controller. Since the life cycle of the object in the stack is valid in the current code block, this memory will be recycled when the viewDidLoad method ends, and the object pointer becomes a wild pointer.

We can mark a breakpoint at the end of the viewDidLoad method to observe the memory allocation address of the current MyOject object, as follows:

It can be seen that the memory address allocated by the object object object is 0x600001e542d0 (it will be different in each run). The properties of the object accessed later are actually access to the data in memory. If we know the memory address, we can also access it directly using the address without variables, For example, in the above figure, you can send messages directly to the memory address through the po instruction in LLDB. The effect is the same as calling object methods through variables.

After that, we can click the current page after running. In most cases, address exception Crash will appear. We can output the stack information of the next thread through LLDB, as follows:

Sometimes, the program may directly Crash into the main method and enter more strange stack information, as follows:

As shown in the figure above, the stack information prompts us to call the name method of the array, which is actually because this block of memory has been reallocated.

We only created a Demo project without any logic. The problems of wild pointer are so diverse. If there is a problem of wild pointer in the actual project, it is more difficult for us to find the source of the problem. Moreover, in the ARC environment, the scenario in the above example is actually very good. It is found that more wild pointers are caused by unsafe multi-threaded reading and writing data. Combined with multi-threaded use, the problem of wild pointers is more difficult to check.

2, Monitoring of field pointer from principle

To solve the problems caused by wild pointers, in addition to paying attention to some programming and avoiding dangerous writing methods. More importantly, it can summarize a set of schemes to monitor such problems in a process. Due to the characteristics of the wild pointer problem, we don't know whether the wild pointer problem will occur when the memory is released, and we can't trace back after the wild pointer problem occurs. Therefore, we should use the preset idea to find the monitoring scheme for this kind of problem, that is, assuming that there is still a wild pointer to access it after the current memory is released, in the design, we can not really release this memory, but mark this memory as problematic. Later, if we find that there is access to this problematic memory, it indicates that there is a wild pointer problem. When marking memory, we can also record some information of the original object, such as class name, so that when a wild pointer problem occurs, no matter what the stack of the specific Crash is, we can know which class of object releases the wild pointer, which can greatly reduce the troubleshooting scope of the problem.

Therefore, the core point of dealing with the wild pointer problem lies in two points:

1. Preset marker memory and passively wait for the trigger of field pointer problem.

2. Record the classes that cause wild pointer problems. Start with the use of class objects instead of the stack when crashing.

For the above two points, let's see how to implement it.

1. Zombie object

The memory of the object to be released is not really recycled, but only marked. We will visualize the object as a "zombie object". Xcode supports opening zombie objects by default. When we access a zombie object, a Crash will inevitably occur, and the console will output relevant prompt information. Edit the scheme to run in Xcode and open the zombie object function, as shown below:

Run the project again and the program Crash will output the following information:

*** -[MyObject retain]: message sent to deallocated instance 0x600000670670

We can clearly know that the memory problem of the MyObject object caused the wild pointer to crash.

Although the zombie object function of Xcode is easy to use, it can only be used during debugging. More often, the wild pointer problems we produce are in the online environment and cannot be reproduced. This function is very chicken ribs. Can we monitor wild pointers without relying on Xcode? First, we need to understand the implementation principle of zombie objects in Xcode.

2. Research on the implementation principle of Apple zombie object

First of all, we can roughly know that to realize the zombie object probability, we need to do something with dealloc method. We can start with this method to find clues and check the source code of objc. In its NSObject.m, we can see the following code:

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

It can be seen from the comments that the zombie object implemented by the system does handle the dealloc method. It is speculated that the dealloc method of NSObject is actually replaced by the Runtime. There is also some content about Zombies in the source code of CoreFoundation. You can see the following code in CFRuntime.c:

extern void __CFZombifyNSObject(void);  // from NSObject.m

void _CFEnableZombies(void) {
}

Among them_ CFEnableZombies is easy to understand. It should indicate whether the zombie object function is enabled. It should be consistent with the environment variable function set in Xcode__ CFZombifyNSObject can know from the annotation that it should be the implementation of zombie objects. Let's add one in Xcode__ The symbol breakpoint of CFZombifyNSObject. The content after the breakpoint is as follows:

You should be familiar with the assembly here. We put forward the core pseudo code, which is roughly as follows:

// Define string
define "NSObject"
// Used to get the NSObject class
objc_lookUpClass "NSObject"
// Define string
define "dealloc"
define "__dealloc_zombie"
// Implementation of dealloc method
class_getInstanceMethod "NSObject" "dealloc"
// Get__ dealloc_ Implementation of zombie method
class_getInstanceMethod "NSObject" "__dealloc_zombie"
// Exchange dealloc and__ dealloc_ Method and implementation of zombie
method_exchangeImplementations "dealloc" "__dealloc_zombie"

As we thought, we can add another one below__ dealloc_zombie's symbolic breakpoint, take a look__ dealloc_ How to implement zombie method is as follows:

CoreFoundation`-[NSObject(NSObject) __dealloc_zombie]:
->  0x10ef77c49 <+0>:   pushq  %rbp
    0x10ef77c4a <+1>:   movq   %rsp, %rbp
    0x10ef77c4d <+4>:   pushq  %r14
    0x10ef77c4f <+6>:   pushq  %rbx
    0x10ef77c50 <+7>:   subq   $0x10, %rsp
    0x10ef77c54 <+11>:  movq   0x2e04fd(%rip), %rax      ; (void *)0x0000000110021970: __stack_chk_guard
    0x10ef77c5b <+18>:  movq   (%rax), %rax
    0x10ef77c5e <+21>:  movq   %rax, -0x18(%rbp)
    0x10ef77c62 <+25>:  testq  %rdi, %rdi
    0x10ef77c65 <+28>:  js     0x10ef77d04               ; <+187>
    0x10ef77c6b <+34>:  movq   %rdi, %rbx
    0x10ef77c6e <+37>:  cmpb   $0x0, 0x488703(%rip)      ; __CFConstantStringClassReferencePtr + 7
    0x10ef77c75 <+44>:  je     0x10ef77d1d               ; <+212>
    0x10ef77c7b <+50>:  movq   %rbx, %rdi
    0x10ef77c7e <+53>:  callq  0x10eff4b52               ; symbol stub for: object_getClass
    0x10ef77c83 <+58>:  leaq   -0x20(%rbp), %r14
    0x10ef77c87 <+62>:  movq   $0x0, (%r14)
    0x10ef77c8e <+69>:  movq   %rax, %rdi
    0x10ef77c91 <+72>:  callq  0x10eff464e               ; symbol stub for: class_getName
    0x10ef77c96 <+77>:  leaq   0x242db5(%rip), %rsi      ; "_NSZombie_%s"
    0x10ef77c9d <+84>:  movq   %r14, %rdi
    0x10ef77ca0 <+87>:  movq   %rax, %rdx
    0x10ef77ca3 <+90>:  xorl   %eax, %eax
    0x10ef77ca5 <+92>:  callq  0x10eff4570               ; symbol stub for: asprintf
    0x10ef77caa <+97>:  movq   (%r14), %rdi
    0x10ef77cad <+100>: callq  0x10eff4ab0               ; symbol stub for: objc_lookUpClass
    0x10ef77cb2 <+105>: movq   %rax, %r14
    0x10ef77cb5 <+108>: testq  %rax, %rax
    0x10ef77cb8 <+111>: jne    0x10ef77cd7               ; <+142>
    0x10ef77cba <+113>: leaq   0x2427aa(%rip), %rdi      ; "_NSZombie_"
    0x10ef77cc1 <+120>: callq  0x10eff4ab0               ; symbol stub for: objc_lookUpClass
    0x10ef77cc6 <+125>: movq   -0x20(%rbp), %rsi
    0x10ef77cca <+129>: movq   %rax, %rdi
    0x10ef77ccd <+132>: xorl   %edx, %edx
    0x10ef77ccf <+134>: callq  0x10eff4a62               ; symbol stub for: objc_duplicateClass
    0x10ef77cd4 <+139>: movq   %rax, %r14
    0x10ef77cd7 <+142>: movq   -0x20(%rbp), %rdi
    0x10ef77cdb <+146>: callq  0x10eff482e               ; symbol stub for: free
    0x10ef77ce0 <+151>: movq   %rbx, %rdi
    0x10ef77ce3 <+154>: callq  0x10eff4a5c               ; symbol stub for: objc_destructInstance
    0x10ef77ce8 <+159>: movq   %rbx, %rdi
    0x10ef77ceb <+162>: movq   %r14, %rsi
    0x10ef77cee <+165>: callq  0x10eff4b6a               ; symbol stub for: object_setClass
    0x10ef77cf3 <+170>: cmpb   $0x0, 0x48867f(%rip)      ; __CFZombieEnabled
    0x10ef77cfa <+177>: je     0x10ef77d04               ; <+187>
    0x10ef77cfc <+179>: movq   %rbx, %rdi
    0x10ef77cff <+182>: callq  0x10eff482e               ; symbol stub for: free
    0x10ef77d04 <+187>: movq   0x2e044d(%rip), %rax      ; (void *)0x0000000110021970: __stack_chk_guard
    0x10ef77d0b <+194>: movq   (%rax), %rax
    0x10ef77d0e <+197>: cmpq   -0x18(%rbp), %rax
    0x10ef77d12 <+201>: jne    0x10ef77d3d               ; <+244>
    0x10ef77d14 <+203>: addq   $0x10, %rsp
    0x10ef77d18 <+207>: popq   %rbx
    0x10ef77d19 <+208>: popq   %r14
    0x10ef77d1b <+210>: popq   %rbp
    0x10ef77d1c <+211>: retq   
    0x10ef77d1d <+212>: movq   0x2e0434(%rip), %rax      ; (void *)0x0000000110021970: __stack_chk_guard
    0x10ef77d24 <+219>: movq   (%rax), %rax
    0x10ef77d27 <+222>: cmpq   -0x18(%rbp), %rax
    0x10ef77d2b <+226>: jne    0x10ef77d3d               ; <+244>
    0x10ef77d2d <+228>: movq   %rbx, %rdi
    0x10ef77d30 <+231>: addq   $0x10, %rsp
    0x10ef77d34 <+235>: popq   %rbx
    0x10ef77d35 <+236>: popq   %r14
    0x10ef77d37 <+238>: popq   %rbp
    0x10ef77d38 <+239>: jmp    0x10eff44c8               ; symbol stub for: _objc_rootDealloc
    0x10ef77d3d <+244>: callq  0x10eff443e               ; symbol stub for: __stack_chk_fail

There are many contents in the assembly, and the overall process is relatively clear. The pseudo code is as follows:

// Get current class
object_getClass
// Gets the current type from the current class
class_getName
// Will_ NSZombie_ Splice current class name on
zombiesClsName = "_NSZombie_%s" + className
// Get zombiesClsName class
objc_lookUpClass zombiesClsName
// Determine whether zombiesCls already exists
if not zombiesCls:
    // If it doesn't exist 
    // Now get the "_NSZombie"
    cls = objc_lookUpClass "_NSZombie_"
    // Copy out a cls class named zombiesClsName
    objc_duplicateClass cls zombiesClsName
// String variable release
free zombiesClsName
// Original object destruction method in objc
objc_destructInstance(self)
// Modify the class of the current object to zombiesCls
object_setClass zombiesCls
// Determine whether the zombie object function is enabled
if not __CFZombieEnabled:
    // If not, release the current memory
    free

The pseudo code above is basically__ dealloc_ The overall implementation process of zombie method. In objc source code, the original dealloc method implementation path of NSObject class is as follows:

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->rootDealloc();
}

inline void objc_object::rootDealloc()
{
    // taggedPointer does not need to reclaim memory
    if (isTaggedPointer()) return;  // fixme necessary?
    // A nonpointer of 1 means not only the address, but also other information contained in isa
    // weakly_referenced indicates whether there is a weak reference
    // has_assoc indicates whether there is an associated attribute
    // has_ cxx_ Does dTOR require C + + or Objc destructors
    // has_ sidetable_ Does RC have a hash table count pin
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    { 
        // If you don't recycle memory directly
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
id object_dispose(id obj)
{
    if (!obj) return nil;
    // Destroy the memory before recycling
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

As you can see__ dealloc_ The implementation of zombie and real dealloc is only different from the current memory recycling part, objc_destructInstance method will execute normally. The implementation of this method is as follows:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        // C + + destructor
        if (cxx) object_cxxDestruct(obj);
        // Remove associated properties
        if (assoc) _object_remove_assocations(obj);
        // Cleanup of weak reference tables and hash tables
        obj->clearDeallocating();
    }

    return obj;
}

Through the above analysis, we find that the zombie object implemented by the system is very safe and does not have a negative impact on the operation of normal code. The only impact is that non recycling of memory will increase the burden of memory use, but it can be released through some strategies.

3, Manual collection of on-line field pointer problems

After understanding the implementation principle of the system zombie object, that is, it does not depend on the Debug environment. We can also follow this idea to realize the zombie object monitoring function.

1. Follow the idea of Apple's Zombie object

First create a file called_ YHZombie_ The template class is implemented as follows:

// _YHZombie_.h
#import <Foundation/Foundation.h>

@interface _YHZombie_ : NSObject

@end


//  _YHZombie_.m
#import "_YHZombie_.h"

@implementation _YHZombie_

// All methods calling this object pair are hook and LOG
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%p-[%@ %@]:%@",self ,[NSStringFromClass(self.class) componentsSeparatedByString:@"_YHZombie_"].lastObject, NSStringFromSelector(aSelector), @"Xiang already dealloc Sent a message to the object");
    // End current thread
    abort();
}

@end

Create a new NSObject category to replace dealloc method, as follows:

//  NSObject+YHZombiesNSObject.h
#import <Foundation/Foundation.h>

@interface NSObject (YHZombiesNSObject)

@end


//  NSObject+YHZombiesNSObject.m
#import "NSObject+YHZombiesNSObject.h"
#import <objc/objc.h>
#import <objc/runtime.h>

@implementation NSObject (YHZombiesNSObject)

+(void)load {
    [self __YHZobiesObject];
}

+ (void)__YHZobiesObject {
    char *clsChars = "NSObject";
    Class cls = objc_lookUpClass(clsChars);
    Method oriMethod = class_getInstanceMethod(cls, NSSelectorFromString(@"dealloc"));
    Method newMethod = class_getInstanceMethod(cls, NSSelectorFromString(@"__YHDealloc_zombie"));
    method_exchangeImplementations(oriMethod, newMethod);
    
}

- (void)__YHDealloc_zombie {
    const char *className = object_getClassName(self);
    char *zombieClassName = NULL;
    asprintf(&zombieClassName, "_YHZombie_%s", className);
    Class zombieClass = objc_getClass(zombieClassName);
    if (zombieClass == Nil) {
        zombieClass = objc_duplicateClass(objc_getClass("_YHZombie_"), zombieClassName, 0);
    }
    objc_destructInstance(self);
    object_setClass(self, zombieClass);
    if (zombieClassName != NULL)
    {
        free(zombieClassName);
    }
}


@end

The above code is as like as two peas in the system except some fault tolerance judgments.

Run our test code again. When accessing the wild pointer, an abnormal interrupt will be generated 100%, and the output is as follows:

0x600003a8c2e0-[MyObject name]:Xiang already dealloc Sent a message to the object

Now, we have simply implemented a wild pointer monitoring tool that does not depend on Xcode in principle.

2. Extend monitoring to C pointer

Through the zombie of objects, the wild pointer problem of OC layer can be well monitored, but this method is not practical. If C-related pointers are used in the project, due to the memory management method without reference counting, it is impossible to zombie objects by Hook dealloc. For example, we create a structure as follows:

typedef struct {
    NSString *name;
} MyStruct;

When using this structure, if it is used before initialization or after memory recycling, wild pointer problems may occur, as follows:

MyStruct *p;
p = malloc(sizeof(MyStruct));
// At this time, the data in the memory is uncontrollable and may not have been erased before
printf("%x\n", *((int *)p));
// Wild pointer problems may occur when using
NSLog(@"%@", p->name);
// Initialize memory data
p->name = @"HelloWorld";
// Reclaim memory
free(p);
// At this time, the data in memory is uncontrollable
NSLog(@"%@", p->name);

We can think about the main reasons for the above wild pointer scenario:

1. After obtaining the allocated memory, if the memory has been used before, the data is uncontrollable at this time, and there will be problems if the current pointer directly uses the data.

2. After reclaiming the memory, the data in the current memory is uncontrollable and may be used by others or pointers that have not been cleared before.

No matter which scenario above, the wild pointer problem is very random and difficult to debug. Therefore, what we need to deal with is to change randomness into inevitability, that is, find a way to directly Crash when using these problematic memory, rather than possible Crash. It is easy to handle scenario 1. We can hook the malloc method in C and directly write an agreed exception data into memory after allocating memory. In this way, a Crash will inevitably occur when using this data before initialization. For scenario 2, we can hook the free method in C and write an agreed exception data directly into the memory after recovering the memory. If the memory is not reallocated next time, a Crash will inevitably occur after using it. The Malloc Scribble debugging function provided by Xcode is implemented in this way.

Turn on Malloc Scribble option of Xcode and run the above code. The effect is shown in the following figure:

It can be seen that after malloc allocates memory, all bytes are filled with 0xAA, and Crash will inevitably occur when used before initialization. This is consistent with the explanation of Apple's official documents, but after free, the memory data obtained may not be 0x55 as mentioned in the documents, because this memory may be overwritten by other contents. The official website documents are described as follows:

We can also manually implement a tool to change the wild pointer problem from random to inevitable according to the idea of Malloc Scribble. We only need to rewrite the malloc related functions and free functions of the system. For hooks of C language functions, we can directly use the fishhook Library:

https://github.com/facebook/fishhook

After importing the above library, create a new class named yhmallocscrabble, which is implemented as follows:

//  YHMallcScrbble.h
#import <Foundation/Foundation.h>

@interface YHMallcScrbble : NSObject

@end

//  YHMallcScrbble.m
#import "YHMallcScrbble.h"
#import "fishhook.h"
#import "malloc/malloc.h"


void * (*orig_malloc)(size_t __size);
void (*orig_free)(void * p);


void *_YHMalloc_(size_t __size) {
    void *p = orig_malloc(__size);
    memset(p, 0xAA, __size);
    return p;
}

void _YHFree_(void * p) {
    size_t size = malloc_size(p);
    memset(p, 0x55, size);
    orig_free(p);
}



@implementation YHMallcScrbble

+ (void)load {
    rebind_symbols((struct rebinding[2]){{"malloc", _YHMalloc_, (void *)&orig_malloc}, {"free", _YHFree_, (void *)&orig_free}}, 2);
}

@end

In this way, we can change the wild pointer problem from random to inevitable, and use the general C pointer.

Compared with the zombie object scheme, the Malloc Scribble method can use general C pointers, and really realizes the recovery of object memory without temporary memory. However, there are also great disadvantages. For example, 0x55 written after free is invalid in many cases, because this memory may be rewritten by other places, resulting in the Crash is still random. Of course, we can also not call the free of the original system in the custom free method, so that this memory can not be allocated. In fact, it is similar to the zombie object scheme. Moreover, compared with the zombie object scheme, Malloc Scribble can only turn randomness into necessity to a certain extent to facilitate the exposure of problems. However, for developers, there is not much information to tell us what type of data is the problem, and it is still difficult to troubleshoot.

4, Some extensions

The above just briefly introduces some means and principles of monitoring the field pointer problem. In addition to zombie objects and Malloc Scribble, Xcode also provides the Address Sanitizer tool to monitor memory problems. Its principle is also to deal with malloc and free functions, but when the program accesses the problematic memory, it can Crash in time. At the same time, this tool can store the stack information of objects in malloc. In terms of positioning, we can solve the problem. No matter which method is adopted, if we really want to execute online, there are still many things to do, such as data collection strategy, cleaning time of zombie object memory, when to judge the problem and grab the stack, etc.

Finally, I hope this article can bring you some ideas to deal with the problem of field pointer in development. The sample code written in this article can be downloaded at the following address:

https://github.com/ZYHshao/ZombiesDemo

Focus on technology, understand love, be willing to share and be a friend

QQ: 316045346

Keywords: xcode objective-c

Added by Elarion on Sun, 05 Dec 2021 18:08:58 +0200