0%

野指针

先看我画的一张图

野指针探测实现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
//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);

野指针探测实现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

/*!
* @method installSniffer
* 启动zombie检测
*/
+ (void)installSniffer;

/*!
* @method uninstallSnifier
* 停止zombie检测
*/
+ (void)uninstallSnifier;

/*!
* @method appendIgnoreClass
* 添加白名单类
*/
+ (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;

//获取非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交换dealloc
static 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
希望对您有所帮助,您的支持将是我莫大的动力!