Aspects 源码分析

  1. 简介
  2. 核心实现
  3. 核心源码分析
    1. 能否Hook 判断
    2. 管理hook
    3. 动态生成子类,改变isa指针
  4. Hook 方式解析
    1. hook 父子类循环问题

简介

Aspects是一个面向切面编程的库。
如果想深入了解iOS Runtime中的消息发送机制,Aspects的源码是值得分析的。

项目主页
Aspects

核心实现

Aspects的核心实现就是利用Runtime中的消息分发机制如图:

原理

Aspects通过把selector的方法替换为msg_forward方法转发 转而调用 forwardInvocation(forwardInvocation的实现被Aspects替换,将原来的方法实现与添加的实现组合在了一起)

核心源码分析

这是Aspects 面向切面编程的入口方法

1
2
3
4
5
6
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}

这段代码可以分三部分来看

  1. aspect_isSelectorAllowedAndTrack 这个方法 对父子类同时hook一个方法进行了一些限制
  2. aspect_getContainerForObject 通过Runtime添加关联值的方式 管理hook的方法
  3. aspect_prepareClassAndHookSelector 这是核心的实现,涉及到动态生成子类,改变isa指针的指向,改变方法的实现 一系列操作

能否Hook 判断

aspect_isSelectorAllowedAndTrack 中会判断方法能否被hook

1
2
3
4
5
6
7
8
9
10
// 判断当前这个类有没有曾经hock 过方法
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker subclassHasHookedSelectorName:selectorName]) {
NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}

举个例子
比如说:
UIView hook 了 initWithFrame: 为 method1
UIButton hook 了 initWithFrame: 为 method1

UIButton 调用 initWithFrame: 时 是 method1 ,method1 中的实现会 调用[super initWithFrame:] ,而[super initWithFrame:] 是 method1 这就造成了循环引用

最近发现了一片文章提供了解决重复hook的解决方案,阿里星牛逼!

基于桥的全量方法Hook方案 - 探究苹果主线程检查实现

管理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
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//一个实例 只有一个container
//这是区分实例对象和类对象的关键
//实例对象可以有很多个,但是同一个类的类对象只能有一个
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//原来的selector block
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//container 里 存有 identifier (selector,block)
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}

动态生成子类,改变isa指针

aspect_prepareClassAndHookSelector这是核心的实现,涉及到动态生成子类,改变isa指针,改变方法的实现 一系列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
//动态创建子类,改变forwardInvocation方法的实现
Class klass = aspect_hookClass(self, error);
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
//子类的aliasSelector的实现为 当前类的selector
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
//selector方法替换为_objc_msgForward
// We use forwardInvocation to hook in.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}

这个写法有点像KVO的实现。动态生成子类,hook子类的forwardInvocation方法,并且将isa指针指向subclass, 这种写法对于使用者,没有什么影响,可以当成原来的对象使用,Swizzling子类的方法,避免了去改变对象的类。

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
#pragma mark - Hook Class
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
//这里可以思考一下 class 方法 和 isa 的区别
//[self class] KVO可能改变了isa指针的指向
Class statedClass = self.class;
// object_getClass 能准确的找到isa指针
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
//如果已经子类化了 就返回
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
//如果是类 就改掉类的forwardInvocation 而不是一个子类对象
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
//考虑到KVO,KVO的底层实现,交换了isa指针
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
// 通过创建新子类的方式
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
// forwardInvocation 替换成 (IMP)_ASPECTS_ARE_BEING_CALLED__
aspect_swizzleForwardInvocation(subclass);
//子类的class方法返回当前被hook的对象的class
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
//将当前self设置为子类,这里其实只是更改了self的isa指针而已, 这里hook了子类的forwardInvocation方法,再次使用当前类时,其实是使用了子类的forwardInvocation方法。
object_setClass(self, subclass);
return subclass;
}

Hook 方式解析

目的是:大量的给目的方法打桩 , 打桩的代码相同

思路:传统hook , 直接创建一个新的方法,调用原来的方法,添加代码
这样,如果如果要hook 大量的方法,则需要很多method定义

既然我们要给方法添加自己的实现, 等价于 调用方法之前/之后添加实现。

那么我们必须要找到方法是如何调用的

1. 通过符号表直接查找对应符号的IMP
2. objc_msgsend
    + resolveInstance...
    + forwordtarger...
    + forwardInvocation... 

走objc_msgsend 必然会调用三个方法之一
resolveInstance.,forwordtarger 拿不到原来方法的target 和 sel ,无法调用原来的实现

forwardInvocation 中的invocation 有target和sel,hook 这个方法,然后添加自己的实现,调用原来的方法

然后如何让方法每次都走objc_msgsend呢?
把原来的 sel的IMP改成objc_msgsend.

1
2
IMP msgForwardIMP = _objc_msgForward;
class_replaceMethod(cls, originSelector, msgForwardIMP, originTypes);

这时我们需要保存原来的 IMP

然后hook forwardInvocation … 换成自己的实现,调用原来的IMP和新增的代码

然后… 还要注意 如果真的需要 forwardInvocation 的处理问题

ANYMethodLog https://github.com/qhd/ANYMethodLog 这个实现 简单点,考虑的情况少…
Aspects https://github.com/steipete/Aspects 这个有针对 fowardInvocation 的处理

不同开源库实现的方式多种多样,总之… 你判断就得了呗

1
2
3
4
5
6
7
8
9
10
11
12
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
// origin forwardInvocation 处理
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}

hook 父子类循环问题

假设我们现在对UIView、UIButton都Hook了initWithFrame:这个方法,在调用[[UIView alloc] initWithFrame:]和[[UIButton alloc] initWithFrame:]都会定向到C函数qhd_forwardInvocation中,在UIView调用的时候没问题。但是在UIButton调用的时候,由于其内部实现获取了super initWithFrame:,就产生了循环定向的问题。

objc_msgsend(super…) 其实还是子类的self

父类调用 - (void)forwardInvocation:(NSInvocation *)anInvocation; 中的隐藏参数self 也是 其实是子类啊… emmm …

这就导致了循环…

script>