美文网首页
九 、消息转发

九 、消息转发

作者: 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.jianshu.com/p/f537ce749639]中本类和父类中...

  • Runtime

    相关简单介绍 消息机制消息传递机制消息转发机制-动态添加方法消息转发机制-快速转发消息转发机制-慢速转发消息转发机...

  • 消息转发机制(动态消息转发)

    例子分析 1)在给程序添加消息转发功能以前,必须覆盖两个方法,即methodSignatureForSelecto...

  • Runtime 消息转发

    目录 消息转发背景知识 消息转发使用方式 消息转发常见问题 消息转发背景知识 1.消息转发的定义Objective...

  • 消息转发

    参考:https://www.jianshu.com/p/76ed71216cde

  • 消息转发

    执行一个没有实现的方法,程序会在运行时挂掉并抛出 unrecognized selector sent to … ...

  • 消息转发

    OC中的方法调用,其实都是转化成objc_msgSend函数调用 1.信息发送 2.动态方法解析 /// 对象消息解析

  • 消息转发

    1. 消息查找 Objective-C 具有很强的动态性,它将静态语言在编译和链接时期做的工作,放置到运行时来处理...

  • 消息转发

    一张图说明消息转发 例如我们进行这样一个操作:People这个类并没有实现perfectGotoSchool这个函...

  • 消息转发

网友评论

      本文标题:九 、消息转发

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