先看我画的一张图
野指针探测实现1
1、通过fishhook替换C函数的free方法为自定义的safe_free,类似于Method Swizzling
2、在safe_free方法中对已经释放变量的内存,填充0x55,使已经释放变量不能访问,从而使某些野指针的crash从不必现安变成必现。
- 为了防止填充0x55的内存被新的数据内容填充,使野指针crash变成不必现,在这里采用的策略是,safe_free不释放这片内存,而是自己保留着,即safe_free方法中不会真的调用free。
- 同时为了防止系统内存过快消耗(因为要保留内存),需要在保留的内存大于一定值时释放一部分,防止被系统杀死,同时,在收到系统内存警告时,也需要释放一部分内存
3、发生crash时,得到的崩溃信息有限,不利于问题排查,所以这里采用代理类(即继承自NSProxy的子类),重写消息转发的三个方法,以及NSObject的实例方法,来获取异常信息。但是这的话,还有一个问题,就是NSProxy只能做OC对象的代理,所以需要在safe_free中增加对象类型的判断
Zombie Objects
僵尸对象
可以用来检测内存错误(EXC_BAD_ACCESS),它可以捕获任何阐释访问坏内存的调用
给僵尸对象发送消息的话,它仍然是可以响应的,然后会发生崩溃,并输出错误日志来显示野指针对象调用的类名和方法
苹果的僵尸对象检测原理
从dealloc的源码中,我们可以看到“Replaced by NSZombie”,即对象释放时, NSZombie 将在 dealloc 里做替换,如下所示
所以僵尸对象的生成过程伪代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Class cls = object_getClass(self);
const char *clsName = class_getName(cls)
const char *zombieClsName = "_NSZombie_" + clsName;
Class zombieCls = objc_lookUpClass(zombieClsName); if (!zombieCls) { Class baseZombieCls = objc_lookUpClass(“_NSZombie_");
//6、创建 zombieClsName 类 zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0); } //7、在对象内存未被释放的情况下销毁对象的成员变量及关联引用。 objc_destructInstance(self);
//8、修改对象的 isa 指针,令其指向特殊的僵尸类 objc_setClass(self, zombieCls);
|
野指针探测实现2
野指针检测流程
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的子类
2、hook dealloc函数的具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
| @interface MIZombieSniffer : NSObject
+ (void)installSniffer;
+ (void)uninstallSnifier;
+ (void)appendIgnoreClass: (Class)cls;
@end
<!--2、MIZombieSniffer.m--> #import "MIZombieSniffer.h" #import "MIZombieProxy.h" #import <objc/runtime.h>
typedef void (*MIDeallocPointer) (id objc);
static BOOL _enabled = NO;
static NSArray *_rootClasses = nil;
static NSDictionary<id, NSValue*> *_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; while (rootCls != [NSObject class] && rootCls != [NSProxy class]) { rootCls = class_getSuperclass(rootCls); } NSString *clsName = NSStringFromClass(rootCls); MIDeallocPointer deallocImp = NULL; [[_rootClassDeallocImps objectForKey:clsName] getValue:&deallocImp]; if (deallocImp != NULL) { deallocImp(obj); } }
static inline IMP __mi_swizzleMethodWithBlock(Method method, void *block){
IMP blockImp = imp_implementationWithBlock((__bridge id _Nonnull)(block)); return method_setImplementation(method, blockImp); }
@implementation MIZombieSniffer
+ (void)initialize { _rootClasses = [@[[NSObject class], [NSProxy class]] retain]; }
#pragma mark - public + (void)installSniffer{ @synchronized (self) { if (!_enabled) { [self _swizzleDealloc]; _enabled = YES; } } }
+ (void)uninstallSnifier{ @synchronized (self) { if (_enabled) { [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; 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 {
NSValue *objVal = [NSValue valueWithBytes: &obj objCType: @encode(typeof(obj))]; object_setClass(obj, [MIZombieProxy class]); ((MIZombieProxy *)obj).originClass = currentClass; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __unsafe_unretained id deallocObj = nil; [objVal getValue: &deallocObj]; object_setClass(deallocObj, currentClass); __mi_dealloc(deallocObj); }); } } copy]; }); NSMutableDictionary *deallocImps = [NSMutableDictionary dictionary]; for (Class rootClass in _rootClasses) { Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc")); IMP originalDeallocImp = __mi_swizzleMethodWithBlock(oriMethod, swizzledDeallocBlock); [deallocImps setObject: [NSValue valueWithBytes: &originalDeallocImp objCType: @encode(typeof(IMP))] forKey: NSStringFromClass(rootClass)]; } _rootClassDeallocImps = [deallocImps copy]; }
+ (void)_unswizzleDealloc{ [_rootClasses enumerateObjectsUsingBlock:^(Class rootClass, NSUInteger idx, BOOL * _Nonnull stop) { IMP originDeallocImp = NULL; NSString *clsName = NSStringFromClass(rootClass); [[_rootClassDeallocImps objectForKey:clsName] getValue:&originDeallocImp]; NSParameterAssert(originDeallocImp); Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc")); method_setImplementation(oriMethod, originDeallocImp); }]; [_rootClassDeallocImps release]; _rootClassDeallocImps = nil; }
@end
|