美文网首页
Aspects原理详解

Aspects原理详解

作者: lsj980Ya | 来源:发表于2020-11-26 17:01 被阅读0次

    Aspects是什么

    Aspects是一个开源的的库,面向切面编程,它能允许你在每一个类和每一个实例中存在的方法里面加入任何代码。可以在方法执行之前或者之后执行,也可以替换掉原有的方法。通过Runtime消息转发实现Hook,Aspects不支持hook Static方法,既OC类方法

    消息发送

    我们知道给一个对象发送消息的时候会这几个步骤

    1. 类对象中查找,找到执行,找不见到则执行_objc_msgForward消息转发
    2. 如果在 1中找不到,则执行resolveInstanceMethod动态添加方法
    3. 如果2中没有处理该消息,则执行forwardingTargetForSelector转发到另一个对象处理
    4. 如果3中仍未处理这次消息则执行forwardInvocation这个时候runtime会将未知消息的所有细节都封装为NSInvocation对象作为当前方法的参数
    5. 如果以上都未处理,则报错

    如果让我们去hook一个方法如何做

    1.找到将要被hook的方法(original)
    2.自定义一个方法(custom)
    3.交换方法的实现,把original方法的实现指向custom,custom实现指向original然后在custom内部调用custom(实际指向是original)

    + (void)load {
        Method original  = class_getClassMethod([self class], @selector(original));
        Method exchange  = class_getClassMethod([self class], @selector(aspectMethod));
        method_exchangeImplementations(original, exchange);
    }
    
    - (void)original {
        NSLog(@"Busness logic");
    }
    
    //交换后调用aspectMethod方法的时候回走custom的方法
    - (void)aspectMethod {
        NSLog("aspect 记录日志操作");
        [self aspectMethod];//因为交换了,所以这里☞的是original方法的实现
        NSLog("aspect记录日志操作");
    }
    

    Aspects如何做的

    我们知道对于未实现的方法会走消息转发流程
    Aspects是通过把被hook方法的实现指向了_objc_msgForward,既直接走转发的流程,然后把forwardInvocation的实现指向了__ASPECTS_ARE_BEING_CALLED__方法,相当于调用被hook方法时直接执行__ASPECTS_ARE_BEING_CALLED__方法,在__ASPECTS_ARE_BEING_CALLED__中的不同时机插入一些切面逻辑

    static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
        // 在被hook的方法之前执行
        aspect_invoke(classContainer.beforeAspects, info);
        aspect_invoke(objectContainer.beforeAspects, info);
    
        //替换被hook的方法
        BOOL respondsToAlias = YES;
        if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
            aspect_invoke(classContainer.insteadAspects, info);
            aspect_invoke(objectContainer.insteadAspects, info);
        }else {
             //执行被Hook函数的逻辑
            Class klass = object_getClass(invocation.target);
            do {
                if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                    [invocation invoke];
                    break;
                }
            }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
        }
        //被hook的方法执行之后执行
        aspect_invoke(classContainer.afterAspects, info);
        aspect_invoke(objectContainer.afterAspects, info);
    }
    

    Aspects源码探索

    先介绍四个工具类
    AspectInfo:存储了被Hook方法相关的信息,如被hook的方法对应的实例对象,被hook方法的参数,被hook方法的实现,用来在aspects操作结束之后回调原来的实现
    @interface AspectInfo : NSObject <AspectInfo>
    //被hook的实例
    @property (nonatomic, unsafe_unretained, readonly) id instance;
    //被hook方法的参数
    @property (nonatomic, strong, readonly) NSArray *arguments;
    //被hook方法的原始实现
    @property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
    @end
    
    AspectIdentifier:存储了切面相关的信息,包括何时(AspectOptions)、做什么(block)
    @interface AspectIdentifier : NSObject
    //hook的是哪一个方法,在移除切面的时候会用到
    @property (nonatomic, assign) SEL selector;
    //具体切面的逻辑block
    @property (nonatomic, strong) id block;
    //block的签名,用来做安全校验用的
    @property (nonatomic, strong) NSMethodSignature *blockSignature;
    //被hook的对象
    @property (nonatomic, weak) id object;
    //被hook的时机
    @property (nonatomic, assign) AspectOptions options;
    @end
    
    AspectsContainer:容器类,类对象中每一个被hook的方法都对应一个container,用来存放AspectIdentifier,根据AspectOptions分别存储在三个对应的数组中,在执行时循环执行
    @interface AspectsContainer : NSObject
    @property (atomic, copy) NSArray *beforeAspects;
    @property (atomic, copy) NSArray *insteadAspects;
    @property (atomic, copy) NSArray *afterAspects;
    @end
    
    AspectTracker:这个类主要是用来判断当前方法能不能被hook
    @interface AspectTracker : NSObject
    //当前tracker对应的类
    @property (nonatomic, strong) Class trackedClass;
    //当前tracker对应的类名字
    @property (nonatomic, readonly) NSString *trackedClassName;
    //当前类所有被hook的方法的名字集合,如果selectorNames包含selector则证明当前类对象是hook了该selector的类对象
    @property (nonatomic, strong) NSMutableSet *selectorNames;
    //当前类所有被hook的方法对应的子类的trackers
    @property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
    @end
    

    Aspects中hook类有一个准则(自己总结的,如有错误请指正):
    1.@"retain", @"release", @"autorelease", @"forwardInvocation:"这些方法不能被hook
    2.@"dealloc"只能在AspectPositionBefore时机执行(因为dealloc后该实例对象就不存在了)
    3.同一个类对象的同一个方法可以被多次hook
    4.不同类对象如果存在于一条继承链关系链上则只能hook一次(AspectTracker就是用来判断在一条继承链上是否曾经被hook过)(如何判断?? 通过当前类的selectorNamesToSubclassTrackers根据selector找到对应的trackers集合,然后判断tracker.selectorNames是否包含当前selector,递归执行上述步骤,如果找到了则证明被hook过下面有详细介绍)

    NSObject +Aspects

    头文件入口函数,内部条用了aspect_add

    类对象方法
    + (id<AspectToken>)aspect_hookSelector:(SEL)selector
                          withOptions:(AspectOptions)options
                           usingBlock:(id)block
                                error:(NSError **)error {
        return aspect_add((id)self, selector, options, block, error);
    }
    
    实例方法

    最终还是通过生成一个中间类对象,类似kvo,只对当前对象起作用,而不会影响其他实例对象,后面详细介绍

    - (id<AspectToken>)aspect_hookSelector:(SEL)selector
                          withOptions:(AspectOptions)options
                           usingBlock:(id)block
                                error:(NSError **)error {
        return aspect_add(self, selector, options, block, error);
    }
    
    //hook类方法
    static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
        __block AspectIdentifier *identifier = nil;
        aspect_performLocked(^{
            if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
                // 每一个类对象被hook的方法都对应一个container,存放了对同一个对象的同一个方法的多次hook
                AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
                identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
                if (identifier) {
                    [aspectContainer addAspect:identifier withOptions:options];
                    // Modify the class to allow message interception.
                    aspect_prepareClassAndHookSelector(self, selector, error);
                }
            }
        });
        return identifier;
    }
    
    判断能不能被hook

    这个函数分两部分,添加tracker部分和判断能不能被hook部分

    static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
    
          //1判断部分
    
          //2添加部分
    }
    

    我们先看添加部分,知道了怎么添加我们去判断的时候才能更好的知道怎么去判断

    1.添加Tracker

    总结:

    • 根据当前类找到tracker,如果不存在则生成新的并保存到全局字段中
    • 类对象第一次hook该方法时subtracker为nil,selector添加到selectorNames(这里就解释了为什么 tracker.selectorNames如果包含了selector则能够证明该类是hook了selector而不是父类hook的,如果是父类hook回把子类的tracker添加到以 `selector` 为key的selectorNamesToSubclassTrackersNSSet中而不是把selector添加到selectorNames)
    • 查找父类并重复执行直到根类为止,形成了一个链表结构,如图所示


      类和Tracker的关系
        //添加Tracker部分
        // Add the selector as being modified.
        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        do {
            //根据当前类查找类对应的tracker
            tracker = swizzledClassesDict[currentClass];
            //如果不存在则创建,并保存
            if (!tracker) {
                tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
            }
            if (subclassTracker) {
                [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
            } else {
                [tracker.selectorNames addObject:selectorName];
            }
            subclassTracker = tracker;
        }while ((currentClass = class_getSuperclass(currentClass)));
    
    2.判断能不能被hook

    总结:

    • 根据类对象查找对应的Tracker
    • 向下子类查找,根据当前tracker查找链表中的以当前selector为key的所有子tracker,并判断tracker.selectorNames是否包含当前selector,如果包含则证明子类已经hook过
    • 判断当前类是否hook了selector,如果父类hook了则返回NO,如果是当前类对象对同一个方法hook多次,这个是允许的
        if (class_isMetaClass(object_getClass(self))) {//如果self是类对象
            Class klass = [self class];
            NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
            Class currentClass = [self class];
            AspectTracker *tracker = swizzledClassesDict[currentClass];
            if ([tracker subclassHasHookedSelectorName:selectorName]) {//检查直接或间接继承了该类的子类是否hook了selectorName方法
                //链表从header到ender查询hook了selectorName的类对应的AspectTracker对象
                NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
                NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];//获取所有hook该方法的子类名称
                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;
            }
            do {//查找父类是否hook过
                //找到类对应的AspectTracker信息
                tracker = swizzledClassesDict[currentClass];
                if ([tracker.selectorNames containsObject:selectorName]) {//证明hook过该方法
                    if (klass == currentClass) {//同一个类对象对同一个方法  hook  多次
                        // Already modified and topmost!
                        return YES;
                    }
                    //父类的继承链上的类对象曾经hook过
                    NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
                    AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                    return NO;
                }
            } while ((currentClass = class_getSuperclass(currentClass)));
    
    

    交换方法实现直接走消息转发的逻辑

    总结来说就是把原来的selector方法指向消息转发函数,把自定义的函数指向原来的selectorIMP实现

    • selector => _objc_msgForward / _objc_msgForward_stret
    • aspects_`selector`指向selectorIMP
    static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
        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]) {
                __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);
            }
            // We use forwardInvocation to hook in.
            class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
            AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
        }
    }
    

    交换forwardInvocation实现(这一步最初我也很迷惑,为什么会把这个方法hook一遍,直接用forwardInvocation也是可以实现的,这里其实是为了防止子类重写了这个方法而有没有调用父类的)

    在消息转发中会走forwardInvocation这个方法,我们交换这个类的实现为我们自己的方法__ASPECTS_ARE_BEING_CALLED__,至此我们熟悉了调用Aspects函数后的准备工作,以及是如何转发到我们自己定义的函数中的

    干货来了

    总结:
    1.根据self和selector找到对应的container
    2.执行container beforeAspects中的切面
    3.根据aspects_`selector`找到原始的方法实现,赋值给invocation
    3.1如果是替换操作则直接执行container insteadAspects
    3.2执行原始的没有被hook的方法实现
    4执行container afterAspects中的切面
    5这一步其实是针对父类hook了一个方法,类相应的方法已经交换过了,这时候如果子类执行了一个不存在的方法也会走消息转发相关的逻辑从而走到这里面来,通过判断是否响应交换后的方法来判断是因为调用了一个不存在的方法走进来的还是通过hook交换方法实现走进来的,一般这里直接抛出异常即可

    #pragma mark - Aspect Invoke Point
    
    // This is a macro so we get a cleaner stack trace.
    #define aspect_invoke(aspects, info) \
    for (AspectIdentifier *aspect in aspects) {\
        [aspect invokeWithInfo:info];\
        if (aspect.options & AspectOptionAutomaticRemoval) { \
            aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
        } \
    }
    
    // This is the swizzled forwardInvocation: method.
    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];
                    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)];
    }
    

    总结:

    终于写完了,记录一下吧,这其中只是讲解了主要的流程,有很多细节没有涉及到,如涉及到Block 的底层结构,如何获取Block的签名,如何回调block中的代码,以及如何如何移除aop切面的逻辑等。还是需要自己去多研究源码

    相关文章

      网友评论

          本文标题:Aspects原理详解

          本文链接:https://www.haomeiwen.com/subject/dddniktx.html