博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS开发中的AOP利器 - Aspects 源码分析(二)
阅读量:6951 次
发布时间:2019-06-27

本文共 11533 字,大约阅读时间需要 38 分钟。

执行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指向调用方法的原IMPaliasSelector , 这样可以方便调用会原方法的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执行

//执行hookaspect_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
)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的移除

AspectIdentifierremove方法,会调用到下面的函数

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修改成立进入消息转发的,并且添加了一个新的selectorasepct__selector)指向原selectorIMP这里是还原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中移除类名字符串的记录。

你可能感兴趣的文章
2020年商用的5G,中兴通讯已经下了哪些先手棋?
查看>>
ACL2016最佳论文:通过交互学习语言游戏
查看>>
微软开始受到越来越多尊重 谁是幕后功臣?
查看>>
史上最失败系统!微软正式终止对Vista支持
查看>>
360与Bing合作上线英文搜索
查看>>
保持新投资技术先进性和保护既有投资的完美均衡 —— 成都地铁4号线二期PIS车地无线通信...
查看>>
《Android和PHP开发最佳实践》一第3章 PHP开发准备
查看>>
黑客的克星或叫“白客”
查看>>
假如在1996年,微软、IBM、苹果你会投资谁?
查看>>
国网四川电力应用大数据服务经济社会发展
查看>>
杭州云栖大会10月起航,这里有一份最全的大会剧透
查看>>
雅虎卖身不影响梅耶尔赚钱 她总薪酬2.2亿美元
查看>>
Win10周年升级新增52000个emoji表情
查看>>
大数据降噪方法论
查看>>
衰退的爱立信,进击的华为
查看>>
索尼工厂被迫停止生产,日本地震带来的冲击可能不止于此
查看>>
独角兽复活:Twilio上市预示IPO市场起死回生
查看>>
数据中心运维管理经验39条
查看>>
安防的未来五年 如何把握机遇深耕市场?
查看>>
如此逼真的高清图像居然是端到端网络生成的?GANs 自叹不如 | ICCV 2017
查看>>