iOS 消息转发机制

作者: Johnny_Z | 来源:发表于2020-09-24 23:43 被阅读0次

    一 、动态方法决议

    1、方法最后的查找会在lookUpImpOrForward顺着继承链查询

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
        IMP imp = nil;
        Class curClass;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (fastpath(behavior & LOOKUP_CACHE)) {
            imp = cache_getImp(cls, sel);
            if (imp) goto done_nolock;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.lock();
    
        // We don't want people to be able to craft a binary blob that looks like
        // a class but really isn't one and do a CFI attack.
        //
        // To make these harder we want to make sure this is a class that was
        // either built into the binary or legitimately registered through
        // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
        //
        // TODO: this check is quite costly during process startup.
        checkIsKnownClass(cls);
    
        if (slowpath(!cls->isRealized())) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
        }
    
        if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
            cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
    
            // If sel == initialize, class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        runtimeLock.assertLocked();
        curClass = cls;
    
        // The code used to lookpu the class's cache again right after
        // we take the lock but for the vast majority of the cases
        // evidence shows this is a miss most of the time, hence a time loss.
        //
        // The only codepath calling into this without having performed some
        // kind of cache lookup is class_getInstanceMethod().
    
        for (unsigned attempts = unreasonableClassCount();;) {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp;
                goto done;
            }
    
            if (slowpath((curClass = curClass->superclass) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
    
            // Halt if there is a cycle in the superclass chain.
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
    
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (slowpath(imp == forward_imp)) {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method
                // resolver for this class first.
                break;
            }
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
     done:
        log_and_fill_cache(cls, imp, sel, inst, curClass);
        runtimeLock.unlock();
     done_nolock:
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        return imp;
    }
    

    当第一次方法找不到会进入一个决议的判断,并且将behavior中的LOOKUP_RESOLVER标志去掉,防止第二次再进来的时候出现无限递归

    f (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    

    2、探究resolveMethod_locked核心逻辑

    static NEVER_INLINE IMP
    resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
    {
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
    
        runtimeLock.unlock();
    
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            resolveInstanceMethod(inst, sel, cls);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            resolveClassMethod(inst, sel, cls);
            if (!lookUpImpOrNil(inst, sel, cls)) {
                resolveInstanceMethod(inst, sel, cls);
            }
        }
    
        // chances are that calling the resolver have populated the cache
        // so attempt using it
        return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
    }
    

    这里分为了类对象元类对象进行方法查找,要理解这部分代码主要注意:类对象是元类对象在OC层面的实例
    3、探究resolveInstanceMethod

    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        SEL resolve_sel = @selector(resolveInstanceMethod:);
    
        if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, resolve_sel, sel);
    
        // Cache the result (good or bad) so the resolver doesn't fire next time.
        // +resolveInstanceMethod adds to self a.k.a. cls
        IMP imp = lookUpImpOrNil(inst, sel, cls);
    
        if (resolved  &&  PrintResolving) {
            if (imp) {
                _objc_inform("RESOLVE: method %c[%s %s] "
                             "dynamically resolved to %p", 
                             cls->isMetaClass() ? '+' : '-', 
                             cls->nameForLogging(), sel_getName(sel), imp);
            }
            else {
                // Method resolver didn't add anything?
                _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                             ", but no new implementation of %c[%s %s] was found",
                             cls->nameForLogging(), sel_getName(sel), 
                             cls->isMetaClass() ? '+' : '-', 
                             cls->nameForLogging(), sel_getName(sel));
            }
        }
    }
    

    这里首先判断cls->ISA()中有没有方法resolveInstanceMethod,如果没有就不用看了,直接返回,如果存在,那就消息发送一下:即调用resolveInstanceMethod。对应到OC层面就会执行+(BOOL)resolveInstanceMethod:(SEL)sel;方法

    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"好🐂🍺");
        return YES;
    }
    @end
    

    4、如果我们在+(BOOL)resolveInstanceMethod:(SEL)sel; 中将找不到的方法添加到中,就可以挽救方法,使其不会出现找不到方法的崩溃unrecognized selector sent to instance

    - (void)sayMaster{
        NSLog(@"%s",__func__);
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
        if (sel == @selector(say666)) {
            NSLog(@"%@ 来了",NSStringFromSelector(sel));
    
            IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
            Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
            const char *type  = method_getTypeEncoding(sayMMethod);
            return class_addMethod(self, sel, imp, type);
        }
      
        return [super resolveInstanceMethod:sel];
    }
    

    这样就实现了拯救,注意类方法用:+(BOOL)resolveClassMethod:(SEL)sel;

    二、消息快速转发

    1、当我们发生方法找不到的奔溃时,用lldb命令bt,打印一下当前的堆栈

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
        frame #0: 0x00007fff6ecaf33a libsystem_kernel.dylib`__pthread_kill + 10
        frame #1: 0x00000001004bc9bc libsystem_pthread.dylib`pthread_kill + 430
        frame #2: 0x00007fff6ec36808 libsystem_c.dylib`abort + 120
        frame #3: 0x00007fff6be95458 libc++abi.dylib`abort_message + 231
        frame #4: 0x00007fff6be868bf libc++abi.dylib`demangling_terminate_handler() + 262
      * frame #5: 0x00000001002e9d73 libobjc.A.dylib`_objc_terminate() at objc-exception.mm:701:13
        frame #6: 0x00007fff6be94887 libc++abi.dylib`std::__terminate(void (*)()) + 8
        frame #7: 0x00007fff6be971a2 libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
        frame #8: 0x00007fff6be97169 libc++abi.dylib`__cxa_throw + 113
        frame #9: 0x00000001002e9518 libobjc.A.dylib`objc_exception_throw(obj="-[LGPerson say666]: unrecognized selector sent to instance 0x100748960") at objc-exception.mm:591:5
        frame #10: 0x00007fff34bcdbe7 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
        frame #11: 0x00007fff34ab33bb CoreFoundation`___forwarding___ + 1427
        frame #12: 0x00007fff34ab2d98 CoreFoundation`__forwarding_prep_0___ + 120
        frame #13: 0x0000000100003ba0 KCObjc`main(argc=1, argv=0x00007ffeefbff558) at main.m:17:9 [opt]
        frame #14: 0x00007fff6eb67cc9 libdyld.dylib`start + 1
        frame #15: 0x00007fff6eb67cc9 libdyld.dylib`start + 1
    

    发现有一个CoreFoundation__forwarding_prep_0___的方法的掉用;
    2、用lldb命令image list查看CoreFoundation的镜像在哪

    (lldb) image list
    [  0] 7F3BF110-8DA6-3062-ABDE-8B32FDE4EAD0 0x0000000100000000 /Users/miaomiao/Library/Developer/Xcode/DerivedData/objc-dofuiqgxjunvoictlkqqyctwbzvy/Build/Products/Debug/KCObjc 
    [  1] 34A11073-9E4C-38C3-9293-7D566ABAE8B6 0x0000000100014000 /usr/lib/dyld 
    [  2] 05BDEF2C-BCF7-3D10-8391-D9C8D28E4B72 0x00000001002d1000 /private/tmp/objc.dst/usr/lib/libobjc.A.dylib 
    [  3] 2EF4C4DA-423B-3AFE-ACD1-7DAE64E47603 0x00007fff3710c000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation 
    [  4] 001B3B7F-D02C-31D3-B961-1ED445D5A266 0x00007fff6bb4c000 /usr/lib/libSystem.B.dylib 
    [  5] C0D70026-EDBE-3CBD-B317-367CF4F1C92F 0x00007fff34a4f000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation 
    [  6] E692F14F-C65E-303B-9921-BB7E97D77855 0x00007fff6be85000 /usr/lib/libc++abi.dylib 
    [  7] 59A8239F-C28A-3B59-B8FA-11340DC85EDC 0x00007fff6be32000 /usr/lib/libc++.1.dylib 
    [  8] 5940876E-AC8A-3BE0-80B3-DE3FB14E257A 0x00007fff6e949000 /usr/lib/system/libcache.dylib 
    [  9] C095BD55-1D27-337F-9B02-885E1C7FF87A 0x00007fff6e94f000 /usr/lib/system/libcommonCrypto.dylib 
    [ 10] 6E80AC11-A277-31FA-AEEF-E5A528274C77 0x00007fff6e95b000 /usr/lib/system/libcompiler_rt.dylib 
    [ 11] EB5E0BC8-873D-3546-A40E-C36DC46FA8F6 0x00007fff6e963000 /usr/lib/system/libcopyfile.dylib 
    [ 12] 0B6C52DB-5A50-3FCD-8B5E-C0C2F35857E3 0x00007fff6e96d000 /usr/lib/system/libcorecrypto.dylib 
    [ 13] 8B85F42D-10CA-3508-9D33-9844F214E93E 0x0000000100429000 /usr/lib/system/introspection/libdispatch.dylib 
    [ 14] 24C41E8B-6B33-30C7-94C9-02D2BD051D66 0x00007fff6eb4d000 /usr/lib/system/libdyld.dylib 
    [ 15] 6F582FDB-EB1A-3ED2-A989-B750643E2647 0x00007fff6eb84000 /usr/lib/system/libkeymgr.dylib 
    [ 16] AFBCBDD3-0B55-3ECD-8E04-A73A3A57356B 0x00007fff6eb92000 /usr/lib/system/liblaunch.dylib 
    [ 17] 1B0296B5-3FD0-342C-BCC2-9886351A4391 0x00007fff6eb93000 /usr/lib/system/libmacho.dylib 
    

    3、反汇编分析:找到镜像文件工具hopper反汇编一下,看看__forwarding_prep_0___实现方式

    int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
        var_20 = rax;
        var_30 = zero_extend_64(xmm7);
        var_40 = zero_extend_64(xmm6);
        var_50 = zero_extend_64(xmm5);
        var_60 = zero_extend_64(xmm4);
        var_70 = zero_extend_64(xmm3);
        var_80 = zero_extend_64(xmm2);
        var_90 = zero_extend_64(xmm1);
        var_A0 = zero_extend_64(xmm0);
        var_A8 = arg5;
        var_B0 = arg4;
        var_B8 = arg3;
        var_C0 = arg2;
        var_C8 = arg1;
        rax = ____forwarding___(&var_D0, 0x0);
        if (rax != 0x0) {
                rax = *rax;
        }
        else {
                rax = objc_msgSend(var_D0, var_C8);
        }
        return rax;
    }
    

    这里其实会进一步调用____forwarding___

    int ____forwarding___(int arg0, int arg1) {
        rsi = arg1;
        rdi = arg0;
        r15 = rdi;
        rcx = COND_BYTE_SET(NE);
        if (rsi != 0x0) {
                r12 = *_objc_msgSend_stret;
        }
        else {
                r12 = *_objc_msgSend;
        }
        rax = rcx;
        rbx = *(r15 + rax * 0x8);
        rcx = *(r15 + rax * 0x8 + 0x8);
        var_140 = rcx;
        r13 = rax * 0x8;
        if ((rbx & 0x1) == 0x0) goto loc_649bb;
    
    loc_6498b:
        rcx = **_objc_debug_taggedpointer_obfuscator;
        rcx = rcx ^ rbx;
        rax = rcx >> 0x1 & 0x7;
        if (rax == 0x7) {
                rcx = rcx >> 0x4;
                rax = (rcx & 0xff) + 0x8;
        }
        if (rax == 0x0) goto loc_64d48;
    
    loc_649bb:
        var_148 = r13;
        var_138 = r12;
        var_158 = rsi;
        rax = object_getClass(rbx);
        r12 = rax;
        r13 = class_getName(rax);
        if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;
    
    loc_649fc:
        rdi = rbx;
        rax = [rdi forwardingTargetForSelector:var_140];
        if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
    
    loc_64a19:
        r12 = var_138;
        r13 = var_148;
        if ((rax & 0x1) == 0x0) goto loc_64a5b;
    
    loc_64a2b:
        rdx = **_objc_debug_taggedpointer_obfuscator;
        rdx = rdx ^ rax;
        rcx = rdx >> 0x1 & 0x7;
        if (rcx == 0x7) {
                rcx = (rdx >> 0x4 & 0xff) + 0x8;
        }
        if (rcx == 0x0) goto loc_64d45;
    
    loc_64a5b:
        *(r15 + r13) = rax;
        r15 = 0x0;
        goto loc_64d82;
    
    loc_64d82:
        if (**___stack_chk_guard == **___stack_chk_guard) {
                rax = r15;
        }
        else {
                rax = __stack_chk_fail();
        }
        return rax;
    
    loc_64d45:
        rbx = rax;
        goto loc_64d48;
    
    loc_64d48:
        if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
    
    loc_64d55:
        *(r15 + r13) = _getAtomTarget(rbx);
        ___invoking___(r12, r15);
        if (*r15 == rax) {
                *r15 = rbx;
        }
        goto loc_64d82;
    
    loc_64ed1:
        ____forwarding___.cold.4();
        rax = *(rdi + 0x8);
        return rax;
    
    loc_64a67:
        var_138 = rbx;
        if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
    
    loc_64a8a:
        rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
        r14 = var_138;
        var_148 = r15;
        if (rax == 0x0) goto loc_64dd7;
    
    loc_64ab2:
        rax = [r14 methodSignatureForSelector:var_140];
        rbx = var_158;
        if (rax == 0x0) goto loc_64e3c;
    
    loc_64ad5:
        r12 = rax;
        rax = [rax _frameDescriptor];
        r13 = rax;
        if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
                rax = sel_getName(var_140);
                rcx = "";
                if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
                        rcx = " not";
                }
                r8 = "";
                if (rbx == 0x0) {
                        r8 = " not";
                }
                _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, stack[-360]);
        }
        rax = object_getClass(r14);
        rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
        var_150 = r13;
        if (rax == 0x0) goto loc_64c19;
    
    loc_64b6c:
        if (*0x5c2700 != 0xffffffffffffffff) {
                dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
        }
        r15 = [NSInvocation requiredStackSizeForSignature:r12];
        rsi = *0x5c26f8;
        rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
        r13 = &stack[-360];
        __bzero(r13, rsi);
        ___chkstk_darwin(r13, rsi, r12, rcx);
        rax = objc_constructInstance(*0x5c26f0, r13);
        var_140 = r15;
        [r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
        [var_138 _forwardStackInvocation:r13];
        r14 = 0x1;
        goto loc_64c76;
    
    loc_64c76:
        if (*(int8_t *)(r13 + 0x34) != 0x0) {
                rax = *var_150;
                if (*(int8_t *)(rax + 0x22) < 0x0) {
                        rcx = *(int32_t *)(rax + 0x1c);
                        rdx = *(int8_t *)(rax + 0x20) & 0xff;
                        memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
                }
        }
        rax = [r12 methodReturnType];
        rbx = rax;
        rax = *(int8_t *)rax;
        if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
                r15 = *(r13 + 0x10);
                if (r14 != 0x0) {
                        r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
                        [r13 release];
                        rax = *(int8_t *)rbx;
                }
                if (rax == 0x44) {
                        asm { fld        tword [r15] };
                }
        }
        else {
                r15 = ____forwarding___.placeholder;
                if (r14 != 0x0) {
                        r15 = ____forwarding___.placeholder;
                        [r13 release];
                }
        }
        goto loc_64d82;
    
    loc_64c19:
        if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
    
    loc_64c3b:
        rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
        r13 = rax;
        [r14 forwardInvocation:rax];
        var_140 = 0x0;
        r14 = 0x0;
        goto loc_64c76;
    
    loc_64ec2:
        rdi = &var_130;
        ____forwarding___.cold.3(rdi, r14);
        goto loc_64ed1;
    
    loc_64e3c:
        rax = sel_getName(var_140);
        r14 = rax;
        rax = sel_getUid(rax);
        if (rax != var_140) {
                _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[-360]);
        }
        if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) {
                ____forwarding___.cold.2(var_138);
        }
        (*_objc_msgSend)(var_138, @selector(doesNotRecognizeSelector:));
        asm { ud2 };
        rax = loc_64ec2(rdi, rsi);
        return rax;
    
    loc_64dd7:
        rbx = class_getSuperclass(r12);
        r14 = object_getClassName(r14);
        if (rbx == 0x0) {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[-360]);
        }
        else {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[-360]);
        }
        goto loc_64e3c;
    
    loc_64dc1:
        r14 = @selector(forwardingTargetForSelector:);
        ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
        goto loc_64dd7;
    }
    

    这里其实就是精华了,可以看到这里会有一个forwardingTargetForSelector方法的调用,
    其实这个方法就是消息的快速转发,我们看看官方解释

    消息快速转发
    这里的大意是:方法找不到是,会第一时间跑到这里来,给方法找一个另一个可接受的对象(备胎),只要备胎能完成对方法的接收,那么就不会崩溃

    三、消息的慢速转发

    继续分析____forwarding___汇编
    1、只要实现了-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;返回一个非空值就不会跳到doesNotRecognizeSelector:
    2、想要方法能不报错可以发现forwardInvocation必须实现

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        
    }
    

    当加上这两句后方法找不到就不会报错。

    四、总结

    image.png

    相关文章

      网友评论

        本文标题:iOS 消息转发机制

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