离久的小站

Aspects/Stinger 源码阅读

2020/05/30 Share

前言

本文需要对 Objective-C 的类的继承关系,runtime使用需要一定理解。

Aspects 和 Stinger 的存在意义

在 iOS 中,我们 hook 一个方法时,可以使用系统提供的 runtime 进行方法交换。

然而方法交换,会修改原来一个类原本的结构,并且造成多个实例遭到 hook 。

打个比方,比如我们需要对 UIViewController 的方法进行 hook ,runtime 代码如下

1
2
3
4
5
6
7
8
9
10
11
12

- (void)hookMethod {
/// 获取 目标 Method 和 hook Method
Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidAppear:));
Method hookMethod = class_getInstanceMethod([UIViewController class], @selector(hook_viewDidAppear:));
/// 交换两个方法
method_exchangeImplementations(originalMethod, hookMethod);
}

- (void)hook_viewDidAppear:(BOOL)animated {
[self hook_viewDidAppear:animated];
}

这样,我们就可以对一个方法进行 hook 。

但这样的话,我们就对所有的 UIViewController 的实例进行了。

若如果我们只想对一个实例进行 hook 呢 ?

Aspects 和 Stinger 可以对一个 类/实例 ,进行 hook 而不影响其子类/其它实例。

Aspects

api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;


[self aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info){
NSLog(@"viewDidAppear");
} error:nil];

原理简述

主要关注的方法有 aspect_hookClass 、 aspect_swizzleForwardInvocation 、aspect_hookedGetClass 和 ASPECTS_ARE_BEING_CALLED

下面我们删除一些数据结构存储、判断容错代码,对原理进行分析。

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

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
/// 获取已经 hook 的 Class , 下面有分析这个函数
Class klass = aspect_hookClass(self, error);
/// 拿出目标方法 Method 与 IMP
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
/// 获取目标方法的 typeEncoding
const char *typeEncoding = method_getTypeEncoding(targetMethod);
/// 构造一个新的方法名
SEL aliasSelector = aspect_aliasForSelector(selector);
/// 添加一个新方法 aliasSelector ,方法实现是 targetMethodIMP
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
/// 将 Class 的目标 Selector 指向 _objc_msgForward
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
}

/// hook class 并返回
static Class aspect_hookClass(NSObject *self, NSError **error) {
/// 获取当前类
Class statedClass = self.class;
Class baseClass = object_getClass(self);
/// 获取当前类的名字
NSString *className = NSStringFromClass(baseClass);
/// 构建一个新名字
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
/// 动态创建一个继承于 baseClass ,名字为 subclassName 的类
Class subclass = objc_allocateClassPair(baseClass, subclassName, 0);

/// 将消息转发方法 forwardInvocation: 指向 Aspects 的一个 C 函数 __ASPECTS_ARE_BEING_CALLED__
aspect_swizzleForwardInvocation(subclass);

/// aspect_hookedGetClass 会将第一个参数的类的 -(Class)class 方法替换成第二个参数类的 -(Class)class 方法
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);

/// 注册这个类,使其可以被使用
objc_registerClassPair(subclass);

/// 将 self 实例中的 isa 指针指向 subclass。也就是说 self 现在是 subclass 的实例了。
object_setClass(self, subclass);
return subclass;
}

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
/// 获取 forwardInvocation: 消息转发 IMP
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
/// 将 __ASPECTS_ARE_BEING_CALLED__ 替换掉 forwardInvocation:
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
}

static void aspect_hookedGetClass(Class class, Class statedClass) {
/// 这样替换 ,[class class] 返回的 Class 是 statedClass
Method method = class_getInstanceMethod(class, @selector(class));
IMP newIMP = imp_implementationWithBlock(^(id self) {
return statedClass;
});
class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}

/// __ASPECTS_ARE_BEING_CALLED__ 作为全局消息转发中心
/// 由于当前目标方法实现已经被替换成 _objc_msgForward ,所以目标方法被调用时,会调用到 @selector(forwardInvocation:)
/// __ASPECTS_ARE_BEING_CALLED__ 被调用时,获取到的参数和 @selector(forwardInvocation:) 一样
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
/// 获取当前方法 selector
SEL originalSelector = invocation.selector;
/// 获取 hook 后的 selector
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
/// selector 替换
invocation.selector = aliasSelector;
/// 获取绑定的 AspectsContainer ,也就是用户传入的 block 信息
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;

/**
aspect_invoke 是一个宏,用来执行用户传入的 block

#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
*/

/// 执行 Berfore 的实现
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);

/// 替换实现
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}

/// 执行 After 的实现
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);

/// 若找不到方法抛出异常
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}

[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

Stinger

Stinger 其实是和 Aspects 在 hook Class 大致上原理实现是一样的。

差异就是 Stinger 并没有利用 Objective-C 中的 @selector(forwardInvocation:) 做消息转发。

而是利用了 libffi 这个库去完成了消息的转发,在速度上有提升。

下面就是对 libffi 关于对方法 hook 的操作简述。

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

/** 1. 获取 Method 的信息 **/
/// 获取目标方法 Method
Method method = class_getInstanceMethod(class, selector);
/// 获取方法的 typeEncoding
const char *methodTypeEncoding = method_getTypeEncoding(method);
/// 构造签名
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:methodTypeEncoding];
/// 知道参数个数
NSUInteger argumentsNum = signature.numberOfArguments;

/** 2. 通过目标方法信息构造 ffi_type **/
/// 通过参数个数,构造一个 ffi_type ,代表方法模版的参数类型
ffi_type **argTypes = malloc(sizeof(ffi_type *) * (argumentsNum));
/// 方法前两个是 self 和 selector ,所以固定是指针类型
argTypes[0] = &ffi_type_pointer;
argTypes[1] = &ffi_type_pointer;
/// 其余的根据 typeEncoding 类型设置
for (int i = 2; i < argumentsNum; i ++) {
const char *argType = [signature getArgumentTypeAtIndex:i];
/// 例如 c 则返回 &ffi_type_schar,i 则返回 &ffi_type_sint ,@ 则返回 &ffi_type_pointer
argTypes[i] = [self ffi_typeForTypeEncoding:argType];
}
/// 通过返回类型,构造一个 ffi_type ,代表方法模版的返回值类型
ffi_type *returnType = [self ffi_typeForTypeEncoding:signature.methodReturnType];

/** 3. 构造方法模版 **/
ffi_cif *cif = malloc(sizeof(ffi_cif));
ffi_prep_cif(cif, FFI_DEFAULT_ABI, (int)argumentsNum, returnType, argTypes);

/** 4. 通过模版构造一个新的方法实现 IMP **/
void *newIMP;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), &newIMP);
/// _st_ffi_function 和 Aspects 中的 __ASPECTS_ARE_BEING_CALLED__ 一样,作为中心转发方法
ffi_prep_closure_loc(closure, cif, _st_ffi_function, (__bridge void *)self, newIMP);

/// 替换掉原来的方法
if (!class_addMethod(class, selector, newIMP, methodTypeEncoding)) {
class_replaceMethod(class, selector, newIMP, methodTypeEncoding);
}

此时,执行原来的方法时会通过 libffi 执行到 _st_ffi_function,实现 hook 。

CATALOG
  1. 1. 前言
  2. 2. Aspects 和 Stinger 的存在意义
  3. 3. Aspects
    1. 3.1. api
    2. 3.2. 原理简述
  4. 4. Stinger