美文网首页
九 、消息转发

九 、消息转发

作者: Mjs | 来源:发表于2020-09-25 16:06 被阅读0次

    动态方法决议

    上文中本类和父类中都没找到方法时候,会在报错前调用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);
    }
    

    进行检测是否在类中调用了resolveInstanceMethod或者类方法的resolveClassMethod
    根据isa走位图

    isa流程图.png
    
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(say666)) {
            NSLog(@"%@ ",NSStringFromSelector(sel));
            IMP imp = class_getMethodImplementation(self, @selector(sayHello));
            Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
            return class_addMethod(self, sel, imp, method_getTypeEncoding(sayMethod));
        }else if (sel == @selector(sayNB)) {
            NSLog(@"%@ ",NSStringFromSelector(sel));
            IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(classMethod));
            Method sayMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(classMethod));
            return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, method_getTypeEncoding(sayMethod));
        }
        return NO;
    }
    
    

    因为NSObject为根元类,在方法中以及实现了resolveInstanceMethod,所以这里要return NO.我们可以在NSObject中添加分类处理所有的方法切面.
    但在写在这里封装成SDK,如果其他人在变成的时候同时也处理了,会导致自己写的浪费.我以我们一般不处理.

    快速转发

    我们在找到IMP时候

     done:
        log_and_fill_cache(cls, imp, sel, inst, curClass);
        runtimeLock.unlock();
    
    
    void instrumentObjcMessageSends(BOOL flag)
    {
        bool enable = flag;
    
        // Shortcut NOP
        if (objcMsgLogEnabled == enable)
            return;
    
        // If enabling, flush all method caches so we get some traces
        if (enable)
            _objc_flush_caches(Nil);
    
        // Sync our log file
        if (objcMsgLogFD != -1)
            fsync (objcMsgLogFD);
    
        objcMsgLogEnabled = enable;
    }
    
    static void
    log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
    {
    #if SUPPORT_MESSAGE_LOGGING
        if (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    #endif
        cache_fill(cls, sel, imp, receiver);
    }
    
    static int objcMsgLogFD = -1;
    
    bool logMessageSend(bool isClassMethod,
                        const char *objectsClass,
                        const char *implementingClass,
                        SEL selector)
    {
        char    buf[ 1024 ];
    
        // Create/open the log file
        if (objcMsgLogFD == (-1))
        {
            snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
            objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
            if (objcMsgLogFD < 0) {
                // no log file - disable logging
                objcMsgLogEnabled = false;
                objcMsgLogFD = -1;
                return true;
            }
        }
    
        // Make the log entry
        snprintf(buf, sizeof(buf), "%c %s %s %s\n",
                isClassMethod ? '+' : '-',
                objectsClass,
                implementingClass,
                sel_getName(selector));
    
        objcMsgLogLock.lock();
        write (objcMsgLogFD, buf, strlen(buf));
        objcMsgLogLock.unlock();
    
        // Tell caller to not cache the method
        return false;
    }
    
    extern:该变量或函数定义在别的文件中,先不会报错
    extern void instrumentObjcMessageSends(BOOL flag);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            LGPerson *person = [LGPerson alloc];
            instrumentObjcMessageSends(YES);
            [person say666];
            instrumentObjcMessageSends(NO);
    //        [LGPerson performSelector:@selector(sayNB)];
        }
        return 0;
    }
    

    我们通过instrumentObjcMessageSends打开开关可以打印信息查看,运行之后我们就可以前往/tmp/文件夹中查看到多了一个msgSends -xxxx文件,打开

    + LGPerson NSObject resolveInstanceMethod:
    + LGPerson NSObject resolveInstanceMethod:
    - LGPerson NSObject forwardingTargetForSelector:
    - LGPerson NSObject forwardingTargetForSelector:
    - LGPerson NSObject methodSignatureForSelector:
    - LGPerson NSObject methodSignatureForSelector:
    - LGPerson NSObject class
    + LGPerson NSObject resolveInstanceMethod:
    + LGPerson NSObject resolveInstanceMethod:
    - LGPerson NSObject doesNotRecognizeSelector:
    - LGPerson NSObject doesNotRecognizeSelector:
    - LGPerson NSObject class
    

    说明我们之后会执行forwardingTargetForSelector,我们在LGPerson中添加该方法,把他指向LGStudent,就会在LGStudent中查找该方法,或者在该方法中runtime给自己添加方法,return self,这就是快速转发

    -(id)forwardingTargetForSelector:(SEL)aSelector{
        NSLog(@"%@ ",NSStringFromSelector(aSelector));
        
        return [LGStudent alloc];
    }
    

    慢速转发

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSLog(@"%s--%@",__func__,NSStringFromSelector(aSelector));
        
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    -(void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%s--%@",__func__,anInvocation);
        anInvocation.target = [LGStudent alloc];
        [anInvocation invoke];
    }
    

    anInvocation就是接受的事务,可以在这里更改接受者和调用的方法,在通过[anInvocation invoke]激活事务

    KC消息转发机制.png

    查看消息转发

    我们回到最初的错误代码

    [person say666];
    

    因为没有实现代码,运行到这里会发生报错,我们通过lldb 打印bt(堆栈信息)

    
    * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
        frame #0: 0x00007fff734347fa libsystem_kernel.dylib`__pthread_kill + 10
        frame #1: 0x000000010034b719 libsystem_pthread.dylib`pthread_kill + 432
        frame #2: 0x00007fff733bba1c libsystem_c.dylib`abort + 120
        frame #3: 0x00007fff70455be8 libc++abi.dylib`abort_message + 231
        frame #4: 0x00007fff70455d9c libc++abi.dylib`demangling_terminate_handler() + 262
        frame #5: 0x00007fff71f8178a libobjc.A.dylib`_objc_terminate() + 96
        frame #6: 0x00007fff70462dc7 libc++abi.dylib`std::__terminate(void (*)()) + 8
        frame #7: 0x00007fff70462b6c libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
        frame #8: 0x00007fff7045445d libc++abi.dylib`__cxa_throw + 113
        frame #9: 0x00007fff71f7f933 libobjc.A.dylib`objc_exception_throw + 350
        frame #10: 0x00007fff3bd44b61 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
        frame #11: 0x00007fff3bc29adf CoreFoundation`___forwarding___ + 1427
        frame #12: 0x00007fff3bc294b8 CoreFoundation`__forwarding_prep_0___ + 120
      * frame #13: 0x0000000100000cc0 struct`main(argc=1, argv=0x00007ffeefbff480) at main.m:32:9
        frame #14: 0x00007fff732ed7fd libdyld.dylib`start + 1
    

    我们看到报错前还有两个方法:___forwarding_____forwarding_prep_0___,我们点击左边的__forwarding_prep_0___,我们发现这个方法来自CoreFoundation, 但是我们
    CoreFoundation并未开源,我们只能通过反汇编来查看.
    这里反汇编用的是Hopper Disassembler,在lldb中打印 image list打印所有堆栈.我们找到CoreFoundation来自/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation我们把这个文件拖入Hopper Disassembler,查找__forwarding_prep_0___,查看伪代码

    int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
        *(rsp + 0xa0) = zero_extend_64(xmm7);
        *(rsp + 0x90) = zero_extend_64(xmm6);
        *(rsp + 0x80) = zero_extend_64(xmm5);
        *(rsp + 0x70) = zero_extend_64(xmm4);
        *(rsp + 0x60) = zero_extend_64(xmm3);
        *(rsp + 0x50) = zero_extend_64(xmm2);
        *(rsp + 0x40) = zero_extend_64(xmm1);
        *(rsp + 0x30) = zero_extend_64(xmm0);
        stack[2021] = arg0;
        rax = ____forwarding___(rsp, 0x0);
        if (rax != 0x0) {
                rax = *rax;
        }
        else {
                rax = objc_msgSend(stack[2021], stack[2021]);
        }
        return rax;
    }
    

    双击____forwarding___继续查看

        r14 = @selector(forwardingTargetForSelector:);
        if (class_respondsToSelector(r12, r14) == 0x0) goto loc_6459b;
    
    loc_6459b:
        var_138 = rbx;
        if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_648f5;
    
    loc_6490b:
        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[2003]);
        }
        else {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
        }
        goto loc_64970;
    
    loc_648f5:
        ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
        goto loc_6490b;
    

    如果一直没有找到,就会发出报错信息

    
    loc_645e6:
        rax = [r14 methodSignatureForSelector:var_140];
        rbx = var_158;
        if (rax == 0x0) goto loc_64970;
    
    loc_6476f:
        rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
        r13 = rax;
        [r14 forwardInvocation:rax];
        var_140 = 0x0;
        r14 = 0x0;
        goto loc_647aa;
    

    最后直接调用forwardInvocation

    我们可以通过查看汇编和lldb 打印 register read读取寄存器和Hopper Disassembler对比来一步步对比排除,除了Hopper Disassembler,也可以通过IDA查看

    相关文章

      网友评论

          本文标题:九 、消息转发

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