设为首页收藏本站

安徽论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 52215|回复: 0

iOS-底层原理36:内存优化(一) 野指针探测

[复制链接]

1

主题

0

回帖

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-3-2 17:14:13 | 显示全部楼层 |阅读模式
网站内容均来自网络,本站只提供信息平台,如有侵权请联系删除,谢谢!
本文主要讲解两种野指针检测的原理及实现
技术点:野指针探测

本文的主要目的是理解野指针的形成过程以及如何去检测野指针
引子

在介绍野指针之前,首先说下目前的异常处理类型,附上苹果官网链接
异常类型

异常大致可以分为两类:

  • 1、软件异常:主要是来自kill()、pthread_kill()、iOS中的NSException未捕获、absort等
  • 2、硬件异常:硬件的信号始于处理器trap,是和平台相关的,野指针崩溃大部分是硬件异常
而在处理异常时,需要关注两个概念

  • Mach异常:Mach层捕获
  • UNIX信号:BSD层获取
iOS中的POSIX API就是通过Mach之上的BSD层实现的,如下图所示


  • Mach 是一个受 Accent 启发而搞出的Unix兼容系统。
  • BSD层是建立在Mach之上,是XNU中一个不可分割的一部分。BSD负责提供可靠的、现代的API
  • POSIX表示可移植操作系统接口(Portable Operating System Interface)
所以,综上所述,Mach异常和UNIX信号存在对应的关系


  • 1、硬件异常流程:硬件异常 -> Mach异常 -> UNIX信号
  • 2、软件异常流程:软件异常 -> UNIX信号
Mach异常与UNIX信号的转换
下面是Mach异常 与 UNIX信号 的转换关系代码,来自 xnu 中的 bsd/uxkern/ux_exception.c
switch(exception) {case EXC_BAD_ACCESS:    if (code == KERN_INVALID_ADDRESS)        *ux_signal = SIGSEGV;    else        *ux_signal = SIGBUS;    break;case EXC_BAD_INSTRUCTION:    *ux_signal = SIGILL;    break;case EXC_ARITHMETIC:    *ux_signal = SIGFPE;    break;case EXC_EMULATION:    *ux_signal = SIGEMT;    break;case EXC_SOFTWARE:    switch (code) {    case EXC_UNIX_BAD_SYSCALL:    *ux_signal = SIGSYS;    break;    case EXC_UNIX_BAD_PIPE:    *ux_signal = SIGPIPE;    break;    case EXC_UNIX_ABORT:    *ux_signal = SIGABRT;    break;    case EXC_SOFT_SIGNAL:    *ux_signal = SIGKILL;    break;    }    break;case EXC_BREAKPOINT:    *ux_signal = SIGTRAP;    break;}

  • 将其对应关系汇总成一个表格,如下所示

  • 其中Mach异常有以下
Mach异常说明EXC_BAD_ACCESS不能访问的内存EXC_BAD_INSTRUCTION非法或未定义的指令或操作数EXC_ARITHMETIC算术异常(例如除以0)。iOS 默认是不启用的,所以我们一般不会遇到EXC_EMULATION执行打算用于支持仿真的指令EXC_SOFTWARE软件生成的异常,我们在 Crash 日志中一般不会看到这个类型,苹果的日志里会是 EXC_CRASHEXC_BREAKPOINT跟踪或断点EXC_SYSCALLUNIX 系统调用EXC_MACH_SYSCALLMach 系统调用

  • UNIX信号有以下几种
UNIX信号说明SIGSEGV段错误。访问未分配内存、写入没有写权限的内存等。SIGBUS总线错误。比如内存地址对齐、错误的内存类型访问等。SIGILL执行了非法指令,一般是可执行文件出现了错误SIGFPE致命的算术运算。比如数值溢出、NaN数值等。SIGABRT调用 abort() 产生,通过 pthread_kill() 发送。SIGPIPE管道破裂。通常在进程间通信产生。比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。根据苹果相关文档,可以忽略这个信号。SIGSYS系统调用异常。SIGKILL此信号表示系统中止进程。崩溃报告会包含代表中止原因的编码。exit(), kill(9) 等函数调用。iOS 系统杀进程,如 watchDog 杀进程。SIGTRAP断点指令或者其他trap指令产生。野指针

所指向的对象被释放或者收回,但是该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址。这个指针就是野指针
野指针分类
这个参考腾讯Bugly团队的总结,大致分为两类

  • 内存没被覆盖
  • 内存被覆盖
如下图所示
为什么OC野指针的crash这么多?
我们一般在app发版前,都会经过多轮的自测、内侧、灰度测试等,按照常理来说,大部分的crash应该都被覆盖了,但是由于野指针的随机性,使得经常在测试时不会出现crash,而是在线上出现crash,这对app体验来说是非常致命的
而野指针的随机性问题大致可以分为两类:

  • 1、跑不进出错的逻辑,执行不到出错的代码,这种可以通过提高测试场景覆盖率来解决
  • 2、跑进有问题的逻辑,但是野指针指向的地址并不一定会导致crash,原因是因为:野指针其本质是一个指向已经删除的对象或受限内存区域的指针。这里说的OC野指针,是指OC对象释放后指针未置空而导致的野指针。这里不必现的原因是因为dealloc执行后只是告诉系统,这片内存我不用了,而系统并没有让这片内存不能访问
野指针解决思路

这里主要是借鉴Xcode中的两种处理方案:


  • 1、Malloc Scribble ,其官方解释如下:申请内存 alloc 时在内存上填0xAA,释放内存 dealloc 在内存上填 0x55。

  • 2、Zombie Objects,其官方解释如下:一个对象已经解除了它的引用,已经被释放掉,但是此时仍然是可以接受消息,这个对象就叫做Zombie Objects(僵尸对象)。这种方案的重点就是将释放的对象,全都转为僵尸对象

两种方案对比

  • 1、僵尸对象 相比 Malloc Scribble,不需要考虑会不会崩溃的问题,只要野指针指向僵尸对象,那么再次访问野指针就一定会崩溃
  • 2、僵尸对象这种方式,不如Malloc Scribble覆盖面广,可以通过hook free方法将c函数也包含在其中
1、Malloc Scribble

思路:当访问到对象内存中填充的是0xAA、0x55时,程序就会出现异常

  • 申请内存 alloc 时在内存上填0xAA,
  • 释放内存 dealloc 在内存上填 0x55。
以上的申请和释放的填充分别对应一下两种情况

  • 申请:没有做初始化就直接被访问
  • 释放:释放后访问
所以综上所述,针对野指针,我们的解决办法是:在对象释放时做数据填充0x55即可。关于对象的释放流程可以参考这篇文章iOS-底层原理 33:内存管理(一)TaggedPointer/retain/release/dealloc/retainCount 底层分析
野指针探测实现1

这个实现主要依据腾讯Bugly工程师:陈其锋的分享,在其代码中的主要思路是

  • 1、通过fishhook替换C函数的free方法为自定义的safe_free,类似于Method Swizzling
  • 2、在safe_free方法中对已经释放变量的内存,填充0x55,使已经释放变量不能访问,从而使某些野指针的crash从不必现安变成必现。

    • 为了防止填充0x55的内存被新的数据内容填充,使野指针crash变成不必现,在这里采用的策略是,safe_free不释放这片内存,而是自己保留着,即safe_free方法中不会真的调用free。
    • 同时为了防止系统内存过快消耗(因为要保留内存),需要在保留的内存大于一定值时释放一部分,防止被系统杀死,同时,在收到系统内存警告时,也需要释放一部分内存

  • 3、发生crash时,得到的崩溃信息有限,不利于问题排查,所以这里采用代理类(即继承自NSProxy的子类),重写消息转发的三个方法(参考这篇文章iOS-底层原理 14:消息流程分析之 动态方法决议 & 消息转发),以及NSObject的实例方法,来获取异常信息。但是这的话,还有一个问题,就是NSProxy只能做OC对象的代理,所以需要在safe_free中增加对象类型的判断
以下是完整的野指针探测实现代码

  • 引入fishhook

  • 实现NSProxy的代理子类
@interface MIZombieProxy : NSProxy@property (nonatomic, assign) Class originClass;@end#import "MIZombieProxy.h"@implementation MIZombieProxy- (BOOL)respondsToSelector:(SEL)aSelector{    return [self.originClass instancesRespondToSelector:aSelector];}- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{    return [self.originClass instanceMethodSignatureForSelector:sel];}- (void)forwardInvocation: (NSInvocation *)invocation{    [self _throwMessageSentExceptionWithSelector: invocation.selector];}#define MIZombieThrowMesssageSentException() [self _throwMessageSentExceptionWithSelector: _cmd]- (Class)class{    MIZombieThrowMesssageSentException();    return nil;}- (BOOL)isEqual:(id)object{    MIZombieThrowMesssageSentException();    return NO;}- (NSUInteger)hash{    MIZombieThrowMesssageSentException();    return 0;}- (id)self{    MIZombieThrowMesssageSentException();    return nil;}- (BOOL)isKindOfClass:(Class)aClass{    MIZombieThrowMesssageSentException();    return NO;}- (BOOL)isMemberOfClass:(Class)aClass{    MIZombieThrowMesssageSentException();    return NO;}- (BOOL)conformsToProtocol:(Protocol *)aProtocol{    MIZombieThrowMesssageSentException();    return NO;}- (BOOL)isProxy{    MIZombieThrowMesssageSentException();    return NO;}- (NSString *)description{    MIZombieThrowMesssageSentException();    return nil;}#pragma mark - MRC- (instancetype)retain{    MIZombieThrowMesssageSentException();    return  nil;}- (oneway void)release{    MIZombieThrowMesssageSentException();}- (void)dealloc{    MIZombieThrowMesssageSentException();    [super dealloc];}- (NSUInteger)retainCount{    MIZombieThrowMesssageSentException();    return 0;}- (struct _NSZone *)zone{    MIZombieThrowMesssageSentException();    return  nil;}#pragma mark - private- (void)_throwMessageSentExceptionWithSelector:(SEL)selector{    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"(-[%@ %@]) was sent to a zombie object at address: %p", NSStringFromClass(self.originClass),NSStringFromSelector(selector), self] userInfo:nil];}@end

  • hook free方法的具体实现
@interface MISafeFree : NSObject//系统警告时,用函数释放一些内存void free_safe_mem(size_t freeNum);@end#import "MISafeFree.h"#import "queue.h"#import "fishhook.h"#import "MIZombieProxy.h"#import #import #import //用于保存zombie类static Class kMIZombieIsa;//用于保存zombie类的实例变量大小static size_t kMIZombieSize;//用于表示调用free函数static void(* orig_free)(void *p);//用于保存已注册的类的集合static CFMutableSetRef registeredClasses = nil;/* 用来保存自己保留的内存 - 1、队列要线程安全或者自己加锁 - 2、这个队列内部应该尽量少申请和释放堆内存 */struct DSQueue *_unfreeQueue = NULL;//用来记录自己保存的内存的大小int unfreeSize = 0;//最多存储的内存,大于这个值就释放一部分#define MAX_STEAL_MEM_SIZE 1024*1024*100//最多保留的指针个数,超过就释放一部分#define MAX_STEAL_MEM_NUM 1024*1024*10//每次释放时释放的指针数量#define BATCH_FREE_NUM 100@implementation MISafeFree#pragma mark - Public Method//系统警告时,用函数释放一些内存void free_safe_mem(size_t freeNum){#ifdef DEBUG    //获取队列的长度    size_t count = ds_queue_length(_unfreeQueue);    //需要释放的内存大小    freeNum = freeNum > count ? count : freeNum;    //遍历并释放    for (int i = 0; i < freeNum; i++) {        //获取未释放的内存块        void *unfreePoint = ds_queue_get(_unfreeQueue);        //创建内存块申请的大小        size_t memSize = malloc_size(unfreePoint);        //原子减操作,多线程对全局变量进行自减        __sync_fetch_and_sub(&unfreeSize, (int)memSize);        //释放        orig_free(unfreePoint);    }#endif}#pragma mark - Life Circle+ (void)load{#ifdef DEBUG    loadZombieProxyClass();    init_safe_free();#endif}#pragma mark - Private Methodvoid safe_free(void* p){    //获取自己保留的内存的大小    int unFreeCount = ds_queue_length(_unfreeQueue);    //保留的内存大于一定值时就释放一部分    if (unFreeCount > MAX_STEAL_MEM_NUM*0.9 || unfreeSize>MAX_STEAL_MEM_SIZE) {        free_safe_mem(BATCH_FREE_NUM);    }else{        //创建p申请的内存大小        size_t memSize = malloc_size(p);        //有足够的空间才覆盖        if (memSize > kMIZombieSize) {            //指针强转为id对象            id obj = (id)p;            //获取指针原本的类            Class origClass = object_getClass(obj);            //判断是不是objc对象            char *type = @encode(typeof(obj));            /*             - strcmp 字符串比较             - CFSetContainsValue 查看已注册类中是否有origClass这个类             如果都满足,则将这块内存填充0x55             */            if (strcmp("@", type) == 0 && CFSetContainsValue(registeredClasses, origClass)) {                //内存上填充0x55                memset(obj, 0x55, memSize);                //将自己类的isa复制过去                memcpy(obj, &kMIZombieIsa, sizeof(void*));                //为obj设置指定的类                object_setClass(obj, [MIZombieProxy class]);                //保留obj原本的类                ((MIZombieProxy*)obj).originClass = origClass;                //多线程下int的原子加操作,多线程对全局变量进行自加,不用理会线程锁了                __sync_fetch_and_add(&unfreeSize, (int)memSize);                //入队                ds_queue_put(_unfreeQueue, p);            }else{                orig_free(p);            }        }else{            orig_free(p);        }    }}//加载野指针自定义类void loadZombieProxyClass(){    registeredClasses = CFSetCreateMutable(NULL, 0, NULL);    //用于保存已注册类的个数    unsigned int count = 0;    //获取所有已注册的类    Class *classes = objc_copyClassList(&count);    //遍历,并保存到registeredClasses中    for (int i = 0; i < count; i++) {        CFSetAddValue(registeredClasses, (__bridge const void *)(classes));    }    //释放临时变量内存    free(classes);    classes = NULL;    kMIZombieIsa = objc_getClass("MIZombieProxy");    kMIZombieSize = class_getInstanceSize(kMIZombieIsa);}//初始化以及free符号重绑定bool init_safe_free(){    //初始化用于保存内存的队列    _unfreeQueue = ds_queue_create(MAX_STEAL_MEM_NUM);    //dlsym 在打开的库中查找符号的值,即动态调用free函数    orig_free = (void(*)(void*))dlsym(RTLD_DEFAULT, "free");    /*     rebind_symbols:符号重绑定     - 参数1:rebindings 是一个rebinding数组,其定义如下         struct rebinding {           const char *name;  // 目标符号名           void *replacement; // 要替换的符号值(地址值)           void **replaced;   // 用来存放原来的符号值(地址值)         };     - 参数2:rebindings_nel 描述数组的长度     */    //重绑定free符号,让它指向自定义的safe_free函数    rebind_symbols((struct rebinding[]){{"free", (void*)safe_free}}, 1);    return true;}@end

  • 测试
- (void)viewDidLoad {    [super viewDidLoad];    id obj = [[NSObject alloc] init];    self.assignObj = obj;//    [MIZombieSniffer installSniffer];}- (IBAction)mallocScribbleAction:(id)sender {    UIView* testObj = [[UIView alloc] init];    [testObj release];    for (int i = 0; i < 10; i++) {        UIView* testView = [[UIView alloc] initWithFrame:CGRectMake(0,200,CGRectGetWidth(self.view.bounds), 60)];        [self.view addSubview:testView];    }    [testObj setNeedsLayout];}打印结果如下
2、Zombie Objects

僵尸对象

  • 可以用来检测内存错误(EXC_BAD_ACCESS),它可以捕获任何阐释访问坏内存的调用
  • 给僵尸对象发送消息的话,它仍然是可以响应的,然后会发生崩溃,并输出错误日志来显示野指针对象调用的类名和方法
苹果的僵尸对象检测原理
首先我们来看下Xcode中僵尸对象是如何实现的,具体操作步骤可以参考这篇文章iOS Zombie Objects(僵尸对象)原理探索

  • 从dealloc的源码中,我们可以看到“Replaced by NSZombie”,即对象释放时, NSZombie 将在 dealloc 里做替换,如下所示
    所以僵尸对象的生成过程伪代码如下
//1、获取到即将deallocted对象所属类(Class)Class cls = object_getClass(self);//2、获取类名const char *clsName = class_getName(cls)//3、生成僵尸对象类名const char *zombieClsName = "_NSZombie_" + clsName;//4、查看是否存在相同的僵尸对象类名,不存在则创建Class zombieCls = objc_lookUpClass(zombieClsName);if (!zombieCls) {     //5、获取僵尸对象类 _NSZombie_ Class baseZombieCls = objc_lookUpClass(“_NSZombie_");     //6、创建 zombieClsName 类 zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);}//7、在对象内存未被释放的情况下销毁对象的成员变量及关联引用。   objc_destructInstance(self);//8、修改对象的 isa 指针,令其指向特殊的僵尸类objc_setClass(self, zombieCls);

  • 当僵尸对象再次被访问时,将进入消息转发流程,开始处理僵尸对象访问,输出日志并发生crash
    所以僵尸对象触发流程伪代码如下
//1、获取对象classClass cls = object_getClass(self);//2、获取对象类名const char *clsName = class_getName(cls);//3、检测是否带有前缀_NSZombie_if (string_has_prefix(clsName, "_NSZombie_")) {//4、获取被野指针对象类名  const char *originalClsName = substring_from(clsName, 10); //5、获取当前调用方法名 const char *selectorName = sel_getName(_cmd); //6、输出日志 Log(''*** - [%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self); //7、结束进程 abort();所以综上所述,这中野指针探测方式的思路是:dealloc方法的替换,其关键是调用objc_destructInstance 来解除对象的关联引用
野指针探测实现2

这种方式的思路主要是来源sindrilin的源码,其主要思路是:

  • 野指针检测流程

    • 1、开启野指针检测
    • 2、设置监控到野指针时的回调block,在block中打印信息,或者存储堆栈
    • 3、检测到野指针是否crash
    • 4、最大内存占用空间
    • 5、是否记录dealloc调用栈
    • 6、监控策略

      • 1)只监控自定义对象
      • 2)白名单策略
      • 3)黑名单策略
      • 4)监控所有对象

    • 7、交换NSObject的dealloc方法

  • 触发野指针

    • 1、开始处理对象
    • 2、是否达到替换条件

      • 1)根据监控策略,是否属于要检测的类
      • 2)空间是否足够

    • 3、如果符合条件,则获取对象,并解除引用,如果不符合则正常释放,即调用原来的dealloc方法
    • 4、向对象内填充数据
    • 5、赋值僵尸对象的类指针替换isa
    • 6、对象+dealloc调用栈,保存在僵尸对象中
    • 7、根据情况是否清理内存和对象

通过僵尸对象检测的实现思路

  • 1、通过OC中Mehod Swizzling,交换根类NSObject和NSProxy的dealloc方法为自定义的dealloc方法
  • 2、为了避免内存空间释放后被重写造成野指针的问题,通过字典存储被释放的对象,同时设置在30s后调用dealloc方法将字典中存储的对象释放,避免内存增大
  • 3、为了获取更多的崩溃信息,这里同样需要创建NSProxy的子类
具体实现

  • 1、创建NSProxy的子类,其实现与上面的MIZombieProxy是一模一样的
  • 2、hook dealloc函数的具体实现
@interface MIZombieSniffer : NSObject/*! *  @method installSniffer *  启动zombie检测 */+ (void)installSniffer;/*! *  @method uninstallSnifier *  停止zombie检测 */+ (void)uninstallSnifier;/*! *  @method appendIgnoreClass *  添加白名单类 */+ (void)appendIgnoreClass: (Class)cls;@end#import "MIZombieSniffer.h"#import "MIZombieProxy.h"#import //typedef void (*MIDeallocPointer) (id objc);//野指针探测器是否开启static BOOL _enabled = NO;//根类static NSArray *_rootClasses = nil;//用于存储被释放的对象static NSDictionary *_rootClassDeallocImps = nil;//白名单static inline NSMutableSet *__mi_sniffer_white_lists(){    //创建白名单集合    static NSMutableSet *mi_sniffer_white_lists;    //单例初始化白名单集合    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        mi_sniffer_white_lists = [[NSMutableSet alloc] init];    });    return mi_sniffer_white_lists;}static inline void __mi_dealloc(__unsafe_unretained id obj){    //获取对象的类    Class currentCls = [obj class];    Class rootCls = currentCls;    //获取非NSObject和NSProxy的类    while (rootCls != [NSObject class] && rootCls != [NSProxy class]) {        //获取rootCls的父类,并赋值        rootCls = class_getSuperclass(rootCls);    }    //获取类名    NSString *clsName = NSStringFromClass(rootCls);    //根据类名获取dealloc的imp指针    MIDeallocPointer deallocImp = NULL;    [[_rootClassDeallocImps objectForKey:clsName] getValue:&deallocImp];    if (deallocImp != NULL) {        deallocImp(obj);    }}//hook交换deallocstatic inline IMP __mi_swizzleMethodWithBlock(Method method, void *block){    /*     imp_implementationWithBlock :接收一个block参数,将其拷贝到堆中,返回一个trampoline     可以让block当做任何一个类的方法的实现,即当做类的方法的IMP来使用     */    IMP blockImp = imp_implementationWithBlock((__bridge id _Nonnull)(block));    //method_setImplementation 替换掉method的IMP    return method_setImplementation(method, blockImp);}@implementation MIZombieSniffer//初始化根类+ (void)initialize{    _rootClasses = [@[[NSObject class], [NSProxy class]] retain];}#pragma mark - public+ (void)installSniffer{    @synchronized (self) {        if (!_enabled) {            //hook根类的dealloc方法            [self _swizzleDealloc];            _enabled = YES;        }    }}+ (void)uninstallSnifier{    @synchronized (self) {        if (_enabled) {            //还原dealloc方法            [self _unswizzleDealloc];            _enabled = NO;        }    }}//添加百名单+ (void)appendIgnoreClass:(Class)cls{    @synchronized (self) {        NSMutableSet *whiteList = __mi_sniffer_white_lists();        NSString *clsName = NSStringFromClass(cls);        [clsName retain];        [whiteList addObject:clsName];    }}#pragma mark - private+ (void)_swizzleDealloc{    static void *swizzledDeallocBlock = NULL;    //定义block,作为方法的IMP    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        swizzledDeallocBlock = (__bridge void *)[^void(id obj) {            //获取对象的类            Class currentClass = [obj class];            //获取类名            NSString *clsName = NSStringFromClass(currentClass);            //判断该类是否在白名单类            if ([__mi_sniffer_white_lists() containsObject: clsName]) {                //如果在白名单内,则直接释放对象                __mi_dealloc(obj);            } else {                //修改对象的isa指针,指向MIZombieProxy                /*                 valueWithBytes:objCType  创建并返回一个包含给定值的NSValue对象,该值会被解释为一个给定的NSObject类型                 - 参数1:NSValue对象的值                 - 参数2:给定值的对应的OC类型,需要使用编译器指令@encode来创建                 */                NSValue *objVal = [NSValue valueWithBytes: &obj objCType: @encode(typeof(obj))];                //为obj设置指定的类                object_setClass(obj, [MIZombieProxy class]);                //保留对象原本的类                ((MIZombieProxy *)obj).originClass = currentClass;                //设置在30s后调用dealloc将存储的对象释放,避免内存空间的增大                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{                    __unsafe_unretained id deallocObj = nil;                    //获取需要dealloc的对象                    [objVal getValue: &deallocObj];                    //设置对象的类为原本的类                    object_setClass(deallocObj, currentClass);                    //释放                    __mi_dealloc(deallocObj);                });            }        } copy];    });    //交换了根类NSObject和NSProxy的dealloc方法为originalDeallocImp    NSMutableDictionary *deallocImps = [NSMutableDictionary dictionary];    //遍历根类    for (Class rootClass in _rootClasses) {        //获取指定类中dealloc方法        Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc"));        //hook - 交换dealloc方法的IMP实现        IMP originalDeallocImp = __mi_swizzleMethodWithBlock(oriMethod, swizzledDeallocBlock);        //设置IMP的具体实现        [deallocImps setObject: [NSValue valueWithBytes: &originalDeallocImp objCType: @encode(typeof(IMP))] forKey: NSStringFromClass(rootClass)];    }    //_rootClassDeallocImps字典存储交换后的IMP实现    _rootClassDeallocImps = [deallocImps copy];}+ (void)_unswizzleDealloc{    //还原dealloc交换的IMP    [_rootClasses enumerateObjectsUsingBlock:^(Class rootClass, NSUInteger idx, BOOL * _Nonnull stop) {        IMP originDeallocImp = NULL;        //获取根类类名        NSString *clsName = NSStringFromClass(rootClass);        //获取hook后的dealloc实现        [[_rootClassDeallocImps objectForKey:clsName] getValue:&originDeallocImp];        NSParameterAssert(originDeallocImp);        //获取原本的dealloc实现        Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc"));        //还原dealloc的实现        method_setImplementation(oriMethod, originDeallocImp);    }];    //释放    [_rootClassDeallocImps release];    _rootClassDeallocImps = nil;}@end

  • 3、测试
@interface ViewController ()@property (nonatomic, assign) id assignObj;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    id obj = [[NSObject alloc] init];    self.assignObj = obj;    [MIZombieSniffer installSniffer];}- (IBAction)zombieObjectAction:(id)sender {    NSLog(@"%@", self.assignObj);}打印崩溃信息如下
参考文章

补充

github源码链接

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
免责声明
1. 本论坛所提供的信息均来自网络,本网站只提供平台服务,所有账号发表的言论与本网站无关。
2. 其他单位或个人在使用、转载或引用本文时,必须事先获得该帖子作者和本人的同意。
3. 本帖部分内容转载自其他媒体,但并不代表本人赞同其观点和对其真实性负责。
4. 如有侵权,请立即联系,本网站将及时删除相关内容。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表