[iOS Study Notes]——Talk about wild pointers and zombie object positioning

[iOS Study Notes]——Talk about wild pointers and zombie object positioning

1. Start with an exception

In the development of iOS projects, more or less we will encounter some crashes. Most of the exceptions thrown by Crash are NSException layer. Such exceptions are caused by OC layer code problems, usually stack information and exceptions. The prompt information is very clear, which can directly locate the problematic code, so that it is not difficult to solve such problems. In addition to NSException, the exceptions that can cause Crash include Unix layer and Mach layer exceptions.

Mach exceptions are generally low-level kernel-level exceptions. We can capture such exceptions through some low-level APIs. This is not the content of this article and will not be repeated here. The Unix layer refers to the Mach exception that the developer does not catch, and will be converted into a corresponding Unix signal and passed to the error thread.

If there are exceptions similar to EXC_BAD_ACCESS among the exceptions you collect online in the iOS project, it is likely to be caused by wild pointers generated by memory problems. This is also the core content we will discuss in this article.

1. What is a wild pointer?

At present, we mostly use ARC for memory management when writing iOS programs. Usually, we don't need to care too much about memory management. But that doesn't mean there won't be memory issues. In principle, when we create any object, we will first apply for a memory space from the memory for the object to use through the operating system, and save the address of this memory space into a pointer for our convenient reference in the code. to this memory. Then 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 to allocate to other applicants, and the other is to assign the pointer in the code Empty recycling. This ensures that the program can run sustainably and healthily. The working process is shown in the following figure:

But no matter in life or programming, accidents will always happen. Usually, there are few problems in the step of applying for memory to the operating system. The stability of the operating system itself is much stronger than that of the application program. The problem mostly occurs when the memory is freed. There may be two kinds of problems:

One is that we directly clear the pointer variable of the object that is no longer needed, but we do not tell the operating system to reclaim 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 reclaimed. In this case, this piece of memory becomes unowned memory and the operating system does not know it, which leads to the memory leak problem we often say. As the application runs for longer and longer, the memory leak may become more and more Too many will eventually lead to insufficient memory, and the program can no longer run normally.

The other is that we tell the operating system to reclaim this piece of memory, and this piece of memory is really reclaimed, but there is still a pointer variable in the program that stores this address and has not been emptied. At this time, the pointer becomes a pointer, Because the memory it points to has been reclaimed, we don't know whether this memory has been used again or is still storing the original data. After that, if you accidentally use the data of this piece of memory through this pointer, no matter reading or writing, there will be all kinds of strange problems, and it is difficult for us to locate. In this article, we mainly talk about the causes and positioning methods of such wild pointer problems.

2. What problems can wild pointers cause?

Most of the EXC_BAD_ACCESS problems we encountered during development are caused by wild pointers. There are two main signals: SIGSEGV and SIGBUS. Among them, SIGSEGV indicates that the address of the operation is illegal, the unallocated memory is accessed, or the memory without write permission is written. SIGBUS indicates wrong memory type access.

The problems caused by wild pointers are all kinds of strange and difficult to locate. When wild pointers are used in a 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, the operation of the program may seem like there is any problem, but it is actually very dangerous, and the performance of the program logic is out of control.

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

2> The accessed memory is 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 failure, SIGBUS address type exception, SIGFPE operator exception, SIGILL instruction exception, and so on.

If the current memory is accessible, it may be against our intention to write bad memory in other places, causing exceptions when other places use it. It is also possible that the data type to be used does not match our original object, resulting in errors in unimplemented selector classes, errors in finding method classes, various underlying logic errors, and malloc errors. At this point, it is very difficult to troubleshoot the problem.

To sum up, the harm of wild pointers is very great. In addition to causing abnormal crashes, it may also cause exceptions to other normally used codes, and has irreproducibility and randomness. For example, you may find a certain Crash's stack is that a method of an object is called. It is not easy to find, but you can't find a similar method call in the code. In fact, there is a wild pointer problem elsewhere, and then the correct object is just allocated to the wild pointer. The memory pointed to, the wild pointer destroys the data in this memory. For this Crash problem, we are almost helpless.

3. Try to create a wild pointer scene

Through the previous introduction, we understand the causes and hazards of the wild pointer problem. Now you can try it out. Create a new iOS project using Xcode. Create a new class named MyObject in it and add a property to it, 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 scene where the wild pointer problem occurs. The object property of the ViewController class is declared as unsafe_unretained. This modifier means that the current property is not managed by ARC. After the object it refers to is released, this pointer Nor will it be left blank. In the above code, we create a MyObject object in the viewDidLoad method and copy it to the object property of the current controller. Since the life cycle of the object in the stack is valid in the current code block, when the viewDidLoad method ends, this memory will be Recycling, at this time, the object pointer becomes a wild pointer.

We can set 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 during the next run, the memory address allocated by the object object is 0x600001e542d0 (it will be different for each run), and accessing the properties of the object later is actually the access to the data in this memory. If we know the memory address, we can also Direct use of addresses for access does not necessarily require variables. For example, the po instruction in LLDB in the above figure can send messages directly to the memory address. The effect is the same as calling the object method through variables.

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

Sometimes, the program may crash directly 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, and the problems of wild pointers are so diverse. If it is in an actual project, it is more difficult for us to find the source of the problem if there is a problem with wild pointers. And in the ARC environment, the above example scenario is actually very easy to check, and more reasons for wild pointers are caused by multi-threaded unsafe reading and writing data. Combined with multi-threaded use, the problem of wild pointers is more difficult to check.

Second, the monitoring of wild pointers from the principle

To solve the problems caused by wild pointers, in addition to programming, try to pay attention to avoid dangerous writing methods. More importantly, a set of solutions can be summarized to monitor such problems in a process. Due to the characteristics of the wild pointer problem, we actually do not know whether the wild pointer problem will occur when the memory is released, and we cannot backtrack after the wild pointer problem occurs. Therefore, we have to use a preset idea to find a monitoring solution for this type of problem, that is, assuming that there are still wild pointers to access it after the current memory is released, in design, we can not really release this piece of memory, but release this piece of memory. The memory is marked as problematic, and if it is found that there is an access to the problematic memory, it indicates a wild pointer problem. When marking the memory, we can also record some information of the original object, such as the class name, so that when a wild pointer problem occurs, no matter what the specific Crash stack is, we can know which class of object release problem occurs. The wild pointer can greatly narrow the scope of troubleshooting.

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

1. Preset the marked memory, passively waiting for the wild pointer problem to be triggered.

2. Record the class that causes the wild pointer problem, and start the investigation from the use of the class object instead of the stack when the crash occurs.

For the above two points, let's take a look at how to achieve it.

1. Zombie Object

The memory of the object to be released is not really recycled, but only marked, and we will visualize the object at this time 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 outputs relevant prompt information. Edit the scheme you want to run in Xcode and turn on the zombie object feature as follows:

Run the project again, after the program crashes, the following information will be output:

*** -[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 generate are in the online environment and cannot be reproduced. This function is very tasteless. Can we implement wild pointer monitoring without relying on Xcode? First of all, we need to understand the implementation principle of zombie objects in Xcode.

2. Exploration of the realization principle of Apple zombie object

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

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

As can be seen from the comments, the zombie object implemented by the system does deal with the dealloc method. It is speculated that the dealloc method of NSObject is actually replaced by 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 be used to indicate whether the zombie object function is enabled. It should be consistent with the environment variable function we set in Xcode. __CFZombifyNSObject can be known from the comments. It should be the implementation of the zombie object. We add a symbolic breakpoint of __CFZombifyNSObject in Xcode, and the content after the breakpoint is as follows:

Seeing the assembly here, you should not be too unfamiliar. We propose the core pseudocode, which is roughly as follows:

// 定义字符串
define "NSObject"
// 用来获取NSObject类
objc_lookUpClass "NSObject"
// 定义字符串
define "dealloc"
define "__dealloc_zombie"
// 获取dealloc方法的实现
class_getInstanceMethod "NSObject" "dealloc"
// 获取__dealloc_zombie方法的实现
class_getInstanceMethod "NSObject" "__dealloc_zombie"
// 交换dealloc与__dealloc_zombie的方法实现
method_exchangeImplementations "dealloc" "__dealloc_zombie"

Similar to what we thought, we can add a symbolic breakpoint of __dealloc_zombie to see how the __dealloc_zombie method is implemented, 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 assembly contents, and the overall process is relatively clear. The pseudocode is as follows:

// 获取当前类
object_getClass
// 通过当前类获取当前类型
class_getName
// 将_NSZombie_拼接上当前类名
zombiesClsName = "_NSZombie_%s" + className
// 获取zombiesClsName类
objc_lookUpClass zombiesClsName
// 判断是否已经存在zombiesCls
if not zombiesCls:
    // 如果不存在 
    // 现获取"_NSZombie_"类
    cls = objc_lookUpClass "_NSZombie_"
    // 复制出一个cls类,类名为zombiesClsName
    objc_duplicateClass cls zombiesClsName
// 字符串变量释放
free zombiesClsName
// objc中原本的对象销毁方法
objc_destructInstance(self)
// 将当前对象的类修改为zombiesCls
object_setClass zombiesCls
// 判断是否开启了僵尸对象功能
if not __CFZombieEnabled:
    // 如果没开启 将当前内存释放掉
    free

The above pseudo code is basically the overall process of the __dealloc_zombie method implementation. In the objc source code, the original dealloc method implementation path of the 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无需回收内存
    if (isTaggedPointer()) return;  // fixme necessary?
    // nonpointer为1表示不只是地址,isa中包含了其他信息
    // weakly_referenced表示是否有弱引用
    // has_assoc 表示是否有关联属性
    // has_cxx_dtor 是否需要C++或Objc析构
    // has_sidetable_rc是否有散列表计数引脚
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    { 
        // 如果都没有 直接回收内存
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
id object_dispose(id obj)
{
    if (!obj) return nil;
    // 进行内存回收前的销毁工作
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

It can be seen that the implementation of __dealloc_zombie and the real dealloc is actually only the recovery part of the current memory, and the objc_destructInstance method will be executed normally. This method is implemented 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++ 析构
        if (cxx) object_cxxDestruct(obj);
        // 移除关联属性
        if (assoc) _object_remove_assocations(obj);
        // 弱引用表和散列表的清除
        obj->clearDeallocating();
    }

    return obj;
}

Through the above analysis, we found that the zombie object implemented by the system is actually very safe and does not have a negative effect on the operation of normal code. The only effect is that the memory is not recycled, which will increase the burden of memory usage, but it can be released through certain strategies. .

3. Manually implement online wild pointer problem collection

After understanding the implementation principle of the system zombie object, that is, without relying on the Debug environment, we can also follow this idea to implement the zombie object monitoring function.

1. Modeled after Apple's zombie object idea

First create a template class named _YHZombie_, implemented as follows:

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

@interface _YHZombie_ : NSObject

@end


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

@implementation _YHZombie_

// 调用这个对象对的所有方法都hook住进行LOG
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%p-[%@ %@]:%@",self ,[NSStringFromClass(self.class) componentsSeparatedByString:@"_YHZombie_"].lastObject, NSStringFromSelector(aSelector), @"向已经dealloc的对象发送了消息");
    // 结束当前线程
    abort();
}

@end

Create a new NSObject category to replace the 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, except for some fault-tolerant judgments, has the same idea as the zombie object of the system.

Run our test code again, when accessing the wild pointer, an exception will be generated 100%, and the output is as follows:

0x600003a8c2e0-[MyObject name]:向已经dealloc的对象发送了消息

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

2. Extend monitoring to C pointers

Through the zombification of the object, the wild pointer problem of the OC layer can be well monitored, but this method is not practical for the pointer of the C layer. If the C-related pointer is used in the project, due to the memory management method Without reference counting, you cannot use Hook dealloc to zombify objects. For example, we create a struct like this:

typedef struct {
    NSString *name;
} MyStruct;

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

MyStruct *p;
p = malloc(sizeof(MyStruct));
// 此时内存中的数据不可控 可能是之前未擦除的
printf("%x\n", *((int *)p));
// 使用可能会出现野指针问题
NSLog(@"%@", p->name);
// 进行内存数据的初始化
p->name = @"HelloWorld";
// 回收内存
free(p);
// 此时内存中的数据不可控
NSLog(@"%@", p->name);

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

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 when the current pointer directly uses this data.

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

Regardless of the above scenario, this wild pointer problem is very random and difficult to debug. Therefore, what we need to deal with at the core is to change randomness to inevitability, that is, to find a way to directly crash when using these problematic memory, instead of possibly crashing. It is easy to handle scenario 1. We can hook the malloc method in C, and directly write a predetermined exception data into the memory after allocating the memory, so that a crash will inevitably occur when this data is used before initialization. For scenario 2, we can hook the free method in C, and write an agreed exception data directly into this memory after reclaiming the memory. If the memory is not re-allocated next time, it will inevitably cause a crash after using it. The Malloc Scribble debugging function provided by Xcode is implemented in this way.

Turn on the Malloc Scribble option of Xcode and run the above code, the effect is as shown below:

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 documentation, but after free, the memory data obtained may not be 0x55 as the document says, because this memory may be overwritten by other content. The official website documentation is 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, just need to rewrite the system malloc-related functions and free functions. For the Hook of the C language function, we can directly use the fishhook library:

https://github.com/facebook/fishhook

After importing the above library, create a new class named YHMallocScrbble, 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 realize that the wild pointer problem is changed from random to inevitable, and the C pointer is universal.

Compared with the zombie object scheme, the Malloc Scribble method can universally use C pointers, and truly realizes the recovery of object memory without temporarily using memory. But there are also great drawbacks. For example, the 0x55 written after free is invalid in many cases, because this memory may be rewritten by other places, resulting in a random crash. Of course, we can also not call the free method of the original system in the custom free method, so that the memory cannot be forcibly allocated, which is actually similar to the zombie object scheme. And compared to the zombie object scheme, Malloc Scribble can only change randomness to a certain extent, which is convenient for the exposure of problems, but for developers, there is not much information to tell us what type of data is the problem. Still difficult.

Fourth, some extensions

The above is just a brief introduction to some methods and principles of monitoring the wild pointer problem. In addition to zombie objects and Malloc Scribble, Xcode also provides the Address Sanitizer tool to monitor memory problems. The 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 The tool can store the stack information of the object during malloc, so we can locate the problem. No matter which method is adopted, if we really want to execute it online, there are still many things to do, such as the data collection strategy, the cleaning timing of zombie object memory, when to judge the problem and grab the stack, etc.

Finally, I hope this article can bring you some ideas for dealing with the problem of wild pointers 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, be a friend

QQ:316045346

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/2340880/blog/5341605