iOS 开发之 runtime

iOS 消息转发机制

2020-09-24  本文已影响0人  Johnny_Z

一 、动态方法决议

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
上一篇下一篇

猜你喜欢

热点阅读