执行 hook 事件
在 Aspects 源码分析的第一篇文章中主要分析了为 hook 做的准备工作, 接下来分析一下, 当 selector 执行时是如何执行你自己添加的自定义 hook 事件的.
通过 hook 准备工作的处理后 , 外界调用的 hook selector 会直接进入消息转发执行到方法 forwardInvocation: , 然后此时 forwardInvocation: 方法的 IMP 是指向处理 hook 的函数 __ASPECTS_ARE_BEING_CALLED__, 这个函数也是整个 hook 事件的核心函数. 代码实现如下
- static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
- NSCParameterAssert(self);
- NSCParameterAssert(invocation);
- SEL originalSelector = invocation.selector;
- SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
- invocation.selector = aliasSelector;
- 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;
- // Before hooks.
- aspect_invoke(classContainer.beforeAspects, info);
- aspect_invoke(objectContainer.beforeAspects, info);
- // Instead hooks.
- 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]; //aliasSelector 已经在 aspect_prepareClassAndHookSelector 函数中替换为原来 selector 的实现 , 这里就是调回原方法的实现代码
- break;
- }
- }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
- }
- // After hooks.
- aspect_invoke(classContainer.afterAspects, info);
- aspect_invoke(objectContainer.afterAspects, info);
- // If no hooks are installed, call original implementation (usually to throw an exception)
- 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];
- }
- }
- // Remove any hooks that are queued for deregistration.
- [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
- }
复制代码
这个函数首先把传进来的 NSInvocation 对象的 selector 赋值为 IMP 指向调用方法的原 IMP 的 aliasSelector , 这样可以方便调用会原方法的 IMP 的实现.
获取 hook 事件容器
- AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
- AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
- AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
复制代码
这里是通过 aliasSelector 分别取出绑定在 hook 对象 以及 hook class (hook 对象的 isa 指针指向的 Class)中对应的容器对象 AspectsContainer , 并生成一个 AspectInfo 对象, 用于封装执行方法及 hook 事件是所需的实参. 接下来分别是遍历两个容器对象中的三个数组 (beforeAspects ,insteadAspects ,afterAspects) 是否有 hook 的标识对象 AspectIdentifier , 如果有的话就执行相应的 hook 事件. insteadAspects 如果这个数组有对象存放, 就说明原方法的实现被替换为执行 insteadAspects 里的 hook 事件了.
hook 执行
- // 执行 hook
- aspect_invoke(classContainer.beforeAspects, info);
- //hook 执行的宏代码
- #define aspect_invoke(aspects, info) \
- for (AspectIdentifier *aspect in aspects) {\
- [aspect invokeWithInfo:info];\
- if (aspect.options & AspectOptionAutomaticRemoval) { \
- aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
- } \
- }
- - (BOOL)invokeWithInfo:(id<AspectInfo>)info {
- // 根据 block 得签名字符串 , 生成对应的消息调用对象. 用来在设置完参数后调用 block
- NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
- // 取出外界调用方法时, 系统封装的消息调用对象, 用来获取实参的值
- NSInvocation *originalInvocation = info.originalInvocation;
- NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
- // Be extra paranoid. We already check that on hook registration.
- if (numberOfArguments> originalInvocation.methodSignature.numberOfArguments) {
- AspectLogError(@"Block has too many arguments. Not calling %@", info);
- return NO;
- }
- // The `self` of the block will be the AspectInfo. Optional.
- // 这里设置 Block 的 第一个参数为传进来的 AspectInfo 对象 , 第 0 位置的参数是 Block 本身
- if (numberOfArguments> 1) { // 有参数的话就吧第一个参数 设置为 AspectInfo , 第 0 位置是 block 本身.
- /**
- 官方文档解析 : When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied
- &info : info 对象指针的地址
- 这样传参的目的是保证了, 参数无论是普通类型参数还是对象都可以通过你传进来的指针, 通过拷贝指针指向的内容来获取到 普通类型数据 或者 对象指针.
- */
- [blockInvocation setArgument:&info atIndex:1];
- }
- void *argBuf = NULL;
- // 遍历参数类型 typeStr , 为 blockInvocation 对应的参数创建所需空间 , 赋值数据 , 设置 blockInvocation 参数
- for (NSUInteger idx = 2; idx <numberOfArguments; idx++) {
- const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
- NSUInteger argSize; // 实参多需要的空间大小
- NSGetSizeAndAlignment(type, &argSize, NULL); // 根据 encodeType 字符串 创建对应空间存放 block 的参数数据所属要的 size
- if (!(argBuf = reallocf(argBuf, argSize))) { // 创建 size 大小的空间
- AspectLogError(@"Failed to allocate memory for block invocation.");
- return NO;
- }
- [originalInvocation getArgument:argBuf atIndex:idx]; // 获取到指向对应参数的指针
- [blockInvocation setArgument:argBuf atIndex:idx]; // 把指向对应实参指针的地址 (相当于指向实参指针的指针) 传给 invocation 进行拷贝, 得到的就是指向实参对象的指针
- }
- [blockInvocation invokeWithTarget:self.block]; // 设置完实参执行 block
- if (argBuf != NULL) {
- free(argBuf); //c 语言的创建空间 , 用完后需要释放, 关于 c 语言的动态内存相关资料可以看 https://blog.csdn.net/qq_29924041/article/details/54897204
- }
- return YES;
- }
复制代码
可以看出 AspectIdentifier 的 - invokeWithInfo 是执行 hook 事件最终的方法. 该方法主要处理的事情是: 根据传进来的 AspectInfo 对象为最初定义 hook 事件的 Block 设置相应的参数. 并执行 Block(hook 事件)
blockInvocation 设置参数解析
设置了 block 的第一个位置的参数为 AspectInfo * info , 这样做及未来方便内部遍历设置参数 (与 selector 保持一致, 自定义参数从 索引为 2 的位置开始), 又方便了外界在定义 hook 的事件是获取到实例对象 - [info instance]
getArgument:atIndex: 返回的是对应索引参数的指针 (地址). 假如参数是一个对象指针的话, 会返回对象的指针地址. 而 setArgument:atIndex: 会把传进来的参数(指针) 拷贝其指向的内容到相应的索引位置中. 所以 argBuf 在整个 for 循环中可以不断地使用同一个指针并不断的 reallocf 返回指向一定堆空间的指针. argBuf 指针只是作为一个设置参数的中介, 每一个 for 循环后 setArgument :atIndex: 都会把 argBuf 指向的内容拷贝到 invocation 中.
hook 的移除
AspectIdentifier 的 remove 方法, 会调用到下面的函数
- static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
- NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
- __block BOOL success = NO;
- aspect_performLocked(^{
- id self = aspect.object; // strongify
- if (self) {
- AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
- success = [aspectContainer removeAspect:aspect]; // 重 container 的 三个数组中移除 aspect
- aspect_cleanupHookedClassAndSelector(self, aspect.selector);
- // destroy token
- aspect.object = nil;
- aspect.block = nil;
- aspect.selector = NULL;
- }else {
- NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
- AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
- }
- });
- return success;
- }
复制代码
1. 移除 AspectContainer 中的 AspectIdentifier
首先获取被 hook 的对象中通过 runtime 绑定的关联属性 ---AspectsContainer *aspectContainer , 并分别移除数组的 hook 标识对象 - AsepctIdentifier * aspect, 实现代码如下:
- //AspectContainer 的实例方法
- - (BOOL)removeAspect:(id)aspect {
- for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
- NSStringFromSelector(@selector(insteadAspects)),
- NSStringFromSelector(@selector(afterAspects))]) {
- NSArray *array = [self valueForKey:aspectArrayName];
- NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
- if (array && index != NSNotFound) {
- NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
- [newArray removeObjectAtIndex:index];
- [self setValue:newArray forKey:aspectArrayName];
- return YES;
- }
- }
- return NO;
- }
复制代码
2. 还原 selector 指向的 IMP
- // Check if the method is marked as forwarded and undo that.
- Method targetMethod = class_getInstanceMethod(klass, selector);
- IMP targetMethodIMP = method_getImplementation(targetMethod);
- if (aspect_isMsgForwardIMP(targetMethodIMP)) {
- // Restore the original method implementation.
- const char *typeEncoding = method_getTypeEncoding(targetMethod);
- SEL aliasSelector = aspect_aliasForSelector(selector);
- Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
- IMP originalIMP = method_getImplementation(originalMethod);
- NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
- class_replaceMethod(klass, selector, originalIMP, typeEncoding);
- AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
- }
复制代码
在进行 hook 准备工作室, 把 selector 的 IMP 修改成立进入消息转发的, 并且添加了一个新的 selector(asepct__selector)指向原 selector 的 IMP 这里是还原 selector 的指向.
3. 移除 AspectTracker 对应的记录
- static void aspect_deregisterTrackedSelector(id self, SEL selector) {
- if (!class_isMetaClass(object_getClass(self))) return;
- NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
- NSString *selectorName = NSStringFromSelector(selector);
- Class currentClass = [self class];
- do {
- AspectTracker *tracker = swizzledClassesDict[currentClass];
- if (tracker) {
- [tracker.selectorNames removeObject:selectorName];
- if (tracker.selectorNames.count == 0) {
- [swizzledClassesDict removeObjectForKey:tracker];
- }
- }
- }while ((currentClass = class_getSuperclass(currentClass)));
- }
复制代码
如果被 hook 的是类 (调用的是类方法添加 hook). 在全局对象(NSMutableDictionary *swizzledClassesDict) 中移除 hook class 的整个向上继承关系链上的 AspectTracker 中的 selectors 数组中的 selectorName 字符串. ####4. 还原被 hook 的实例对象的 isa 的指向 + 还原被 hook Class 的 forwardInvocation: 方法的 IMP 指向
- // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
- AspectsContainer *container = aspect_getContainerForObject(self, selector);
- if (!container.hasAspects) {
- // Destroy the container
- aspect_destroyContainerForObject(self, selector);
- // Figure out how the class was modified to undo the changes.
- NSString *className = NSStringFromClass(klass);
- if ([className hasSuffix:AspectsSubclassSuffix]) {
- Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
- NSCAssert(originalClass != nil, @"Original class must exist");
- object_setClass(self, originalClass); // 把 hook 的类对象 isa 从_Aspects_class -> 原来的类
- AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
- // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
- // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
- //objc_disposeClassPair(object.class);
- }else {
- // Class is most likely swizzled in place. Undo that.
- if (isMetaClass) {
- aspect_undoSwizzleClassInPlace((Class)self);
- }
- }
- }
复制代码
这里首先判断 hook 对象的 AspectContainer 属性数组中是否还有 AspectIndetafier 对象(hook 标识). 如果没有的话就清除掉该对象的关联属性容器. 接下来分两种情况进行还原处理
情况 1. 被 hook 的是普通实例对象 : 此时需要把对象的 isa 指向还原会为原来的 Class --> object_setClass(self, originalClass);
情况 2. 被 hook 的是类: 还原 forwardInvocation: 方法的 IMP 指向为原来的进入消息转发的 IMP, 并且从全局变量 swizzledClasses 中移除类名字符串的记录.
来源: https://juejin.im/post/5b9b75bb6fb9a05ce7514ce1