美文网首页
通过Runtime源码,分析OC消息发送及处理

通过Runtime源码,分析OC消息发送及处理

作者: 妖精的菩萨 | 来源:发表于2019-03-05 17:52 被阅读0次

    [图片上传失败...(image-f0ae06-1556960021893)]

    前言

    日常开发中我们得知,当我们通过对象调用一个方法时,本质是通过objc_msgSend给对象发送消息。这点我们可以通过clang编译后的代码得知。

    MyPerson *p = [MyPerson new];
    

    通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件编译得:

    MyPerson *p = ((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("new"));
    

    可知接收消息的对象是:(id)objc_getClass("MyPerson")
    接收的消息编号:sel_registerName("new") == @selector(new)
    通过分析objc4-750源码,以objc_msgSend为入口,接下来我们开始分析整个消息发送及处理流程。

    整个流程分为快速和慢速两种方式。
    快速:通过汇编,在缓存(cache)的imp哈希表中寻找。这样的好处是C、C++等语言不能通过写一个函数,来直接保留未知的参数,跳转到任意的指针。而汇编通过调用寄存器,可很好的实现这一点。
    慢速: 通过C、C++在方法列表中寻找。找到了会往chche中存。以上方法找不到,就会通过特殊的动态处理。

    0x01 汇编缓存查找

    objc4-750源码中搜索_objc_msgSend,点击查看在arm64架构中的ENTRY _objc_msgSend。代码和注释如下:

    ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
    
    //tagged pointer:特殊的数据类型,更为轻量。
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
        
    LGetIsaDone://isa处理完毕。//这里可以作为后面传参的一个参考。
    
    
    //!!主要函数!!
    //在缓存列表中找imp
    //这里CacheLookup有三种方式:NORMAL|GETIMP|LOOKUP
    //1、成功:call imp 
    //2、失败:objc_msgSend_uncached
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached
    
    #if SUPPORT_TAGGED_POINTERS
    
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
        // tagged
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
        add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
        cmp x10, x16
        b.ne    LGetIsaDone
    
        // ext tagged
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
    
        END_ENTRY _objc_msgSend
    

    通过查看CacheLookup的宏定义代码,得知缓存中寻找的三种形式:
    CacheHit | CheckMiss | add
    //1:找到直接返回
    //2:找不到的话直接checkmiss
    //3:在其它地方找到的话通过汇编直接add进缓存中。

    .macro CacheLookup
        // p1 = SEL, p16 = isa
        ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
    #if !__LP64__
        and w11, w11, 0xffff    // p11 = mask
    #endif
        and w12, w1, w11        // x12 = _cmd & mask
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                         // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    3:  // wrap: p12 = first bucket, w11 = mask
        add p12, p12, w11, UXTW #(1+PTRSHIFT)
                                    // p12 = buckets + (mask << 1+PTRSHIFT)
    

    查看CacheHit的定义文件即可得知找到imp后可直接返回.

    .macro CacheHit
    .if $0 == NORMAL
        TailCallCachedImp x17, x12  // authenticate and call imp
    .elseif $0 == GETIMP
        mov p0, p17
        AuthAndResignAsIMP x0, x12  // authenticate imp and re-sign as IMP
        ret             // return IMP
    .elseif $0 == LOOKUP
        AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
        ret             // return imp via x17
    

    查看CheckMiss的定义文件即可得知找不到imp,便调用__objc_msgSend_uncached

    .macro CheckMiss
        // miss if bucket->sel == 0
    .if $0 == GETIMP
        cbz p9, LGetImpMiss
    //因为前面声明了CacheLookup NORMAL ,所以会走下面这个判断。
    .elseif $0 == NORMAL
        cbz p9, __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        cbz p9, __objc_msgLookup_uncached
    

    查看__objc_msgSend_uncached的代码中发现MethodTableLookup的调用,继续跟进,便发现了__class_lookupMethodAndLoadCache3的调用。

    .macro MethodTableLookup
        
    // receiver and selector already in x0 and x1
        mov x2, x16
        bl  __class_lookupMethodAndLoadCache3//这里跳转到C++中。
    

    0x02 分析C++代码

    继上:

    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
    //第一个YES,接上文,已经完成了isa的初始化,所以为YES.
    //第一个NO,接上文,通过汇编没有在cache中完成查找,所以为NO。
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    

    lookUpImpOrForward:是寻找imp的关键函数。runtime中涉及imp的获取底层都会走这个方法。比如class_getMethodImplementationclass_getInstanceMethodclass_getInstanceMethod也是通过lookUpImpOrNil,最后底层走这个方法的。

    在下面的方法中大致操作为:
    1、首先检测缓存,如果cache有的话直接就在缓存中查找返回imp.
    2、如果类没被创建,便进行实例化操作。
    3、第一次调用类的时候,执行初始化。
    4、为了防止并发,再次从缓存中查找。
    5、遍历当前类的父类,在父类中缓存的imp中查找
    6、在父类的方法列表中,获取method_t对象。如果找到则缓存查找到的IMP
    7、如果都没有找到,就尝试动态方法解析和消息转发。

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // 如果cache是YES,则从缓存中查找IMP。
        if (cache) {
            // 通过cache_getImp函数查找IMP,查找到则返回IMP并结束调用
            // cache_getImp:还是通过汇编来寻找的。
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        runtimeLock.read();
    
        // 判断类是否已经被创建,如果没有被创建,则将类实例化
        if (!cls->isRealized()) {
            // Drop the read-lock and acquire the write-lock.
            // realizeClass() checks isRealized() again to prevent
            // a race while the lock is down.
            runtimeLock.unlockRead();
            runtimeLock.write();
            
            // 对类进行实例化操作
            realizeClass(cls);
    
            runtimeLock.unlockWrite();
            runtimeLock.read();
        }
    
        // 第一次调用当前类的话,执行initialize的代码
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            // 对类进行初始化,并开辟内存空间
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // 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
        }
    
      //以下重点!!!  
     retry:    
        runtimeLock.assertReading();
    
    //再次从缓存中获取的原因:
    //并发-remap(cls)
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        {
            // 如果没有从cache中查找到,则从方法列表中获取Method
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                // 如果获取到对应的Method,则加入缓存并从Method获取IMP
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        //在父类中找。
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            // 循环遍历父类。获取这个类的缓存IMP 或 方法列表的IMP
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                //内存溢出
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                // 获取父类缓存的IMP
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        // 如果发现父类的方法,并且不再缓存中,在下面的函数中缓存方法
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                // 在父类的方法列表中,获取method_t对象。如果找到则缓存查找到的IMP
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        // No implementation found. Try method resolver once.
    
        // 如果没有找到,则尝试动态方法解析
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            //解析。
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            //动态解析只能解析一次。
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        // 如果没有IMP被发现,并且动态方法解析也没有处理,则进入消息转发阶段。只有汇编调用,没有源码实现。
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlockRead();
    
        return imp;
    }
    

    0x03 动态方法解析

     // 如果没有找到,则尝试动态方法解析
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            //解析。
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            //动态解析只能解析一次。
            triedResolver = YES;
            goto retry;
        }
    

    _class_resolveMethod:

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {//解析实例方法。
            // try [cls resolveInstanceMethod:sel]
           // _class_resolveInstanceMethod:接收消息的是类对象。
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {//解析类方法。
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    _class_resolveClassMethod的实现中有如下代码,表示了消息的发送。可知消息的接受者_class_getNonMetaClass(cls, inst)

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                            SEL_resolveClassMethod, sel);
    

    进入class_getNonMetaClass的实现中,得知返回的依旧是类对象,这样是方便能够在同一个类中处理,方便管理,而避免了去虚拟的元类中进行改动。

    static Class getNonMetaClass(Class metacls, id inst)
    {
        static int total, named, secondary, sharedcache;
        runtimeLock.assertLocked();
    
        realizeClass(metacls);
    
        total++;
        
        // metacls 元类
        // metacls 类对象
        
        //判断是否是NSObject
        if (!metacls->isMetaClass()) return metacls;
    
        // metacls really is a metaclass
    
        // special case for root metaclass
        // where inst == inst->ISA() == metacls is possible
        
        // 判断是否是根元类。
        if (metacls->ISA() == metacls) {
            Class cls = metacls->superclass;
            assert(cls->isRealized());
            assert(!cls->isMetaClass());
            assert(cls->ISA() == metacls);
            if (cls->ISA() == metacls) return cls;
        }
    
        // 类对象
        if (inst) {
            Class cls = (Class)inst;
            realizeClass(cls);
            // cls may be a subclass - find the real class for metacls
            // 元类 != 元类
            while (cls  &&  cls->ISA() != metacls) {
                cls = cls->superclass;
                realizeClass(cls);
            }
            // 最终返回的还是类对象
            if (cls) {
                assert(!cls->isMetaClass());
                assert(cls->ISA() == metacls);
                return cls;
            }
    #if DEBUG
            _objc_fatal("cls is not an instance of metacls");
    #else
            // release build: be forgiving and fall through to slow lookups
    #endif
        }
    

    在动态方法解析的过程中,都会调用lookUpImpOrNil来递归查找动态解析方法的imp,而不会发生死递归的原因是在NSObject中实现了动态方法解析,所以最终会找到它。
    同时我们通过重写NSObject中的+ (BOOL)resolveInstanceMethod:(SEL)sel,在这个方法中通过给没有实现的sel添加imp方法避免崩溃,同时也可以将crash传给后台做崩溃统计等工作。

    0x04 消息转发

    以下的_objc_msgForward_impcache因为苹果闭源是无法看到实现的,我们可以通过定义一个instrumentObjcMessageSends,或者通过反编译函数实现的可执行文件来查看其流程。这里简单介绍一下第二种。

     imp = (IMP)_objc_msgForward_impcache;
     cache_fill(cls, sel, imp, inst);
    

    通过实现动态方法解析,未实现转发而崩溃的堆栈信息可以看出_objc_msgForward_impcache具体是在CoreFoundation.framework中实现。如图:


    CoreFoundation.framework的本地地址:
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
    

    通过hopper或者ida打开,搜索_CFInitialize,再依次进入_forwarding_prep_0___forwarding__。通过查看伪代码,会有以下发现:


     var_50 = rbx;
        if (class_respondsToSelector(object_getClass(r12), @selector(_forwardStackInvocation:)) != 0x0) {
                if (*____forwarding___.onceToken != 0xffffffffffffffff) {
                        dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
                }
                r13 = [NSInvocation requiredStackSizeForSignature:r14];
                rdx = *____forwarding___.invClassSize;
                r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
                memset(r12, 0x0, rdx);
                objc_constructInstance(*____forwarding___.invClass, r12);
                var_40 = r13;
                [r12 _initWithMethodSignature:r14 frame:var_48 buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
                [var_38 _forwardStackInvocation:r12];
                r15 = 0x1;
        }
        else {
                rbx = @selector(forwardInvocation:);
                if (class_respondsToSelector(object_getClass(r12), rbx) != 0x0) {
                        rdi = r12;
                        r12 = [NSInvocation _invocationWithMethodSignature:r14 frame:var_48];
                        _objc_msgSend(rdi, rbx);
                }
                else {
                        r12 = 0x0;
                        _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", 0x0, object_getClassName(0x0), r8, r9, stack[2037]);
                }
                var_40 = 0x0;
                r15 = 0x0;
        }
    
    经典走位图

    如下,代码实现消息转发。

    // 只有汇编调用  没有源码实现
    + (id)forwardingTargetForSelector:(SEL)aSelector{
        return [super forwardingTargetForSelector:aSelector];
    }
    
    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(walk)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation{
        //在此切面编程
        NSString *sto = @"这是参数";
        anInvocation.target = [LGStudent class];
        [anInvocation setArgument:&sto atIndex:2];
        NSLog(@"%@",anInvocation.methodSignature);
        anInvocation.selector = @selector(run:);
        [anInvocation invoke];
    }
    

    如果没有实现消息转发,我们再根据源码追踪一下走位。
    进入消息转发的汇编部分。如下:

        STATIC_ENTRY __objc_msgForward_impcache
    
        // No stret specialization.
        b   __objc_msgForward
    
        END_ENTRY __objc_msgForward_impcache
    
        
        ENTRY __objc_msgForward
    
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17
        
        END_ENTRY __objc_msgForward
        
        
        ENTRY _objc_msgSend_noarg
        b   _objc_msgSend
        END_ENTRY _objc_msgSend_noarg
    
        ENTRY _objc_msgSend_debug
        b   _objc_msgSend
        END_ENTRY _objc_msgSend_debug
    
        ENTRY _objc_msgSendSuper2_debug
        b   _objc_msgSendSuper2
        END_ENTRY _objc_msgSendSuper2_debug
    
        
        ENTRY _method_invoke
        // x1 is method triplet instead of SEL
        add p16, p1, #METHOD_IMP
        ldr p17, [x16]
        ldr p1, [x1, #METHOD_NAME]
        TailCallMethodListImp x17, x16
        END_ENTRY _method_invoke
    

    查看__objc_forward_handler回调

    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    如下可以见到我们常见的崩溃信息打印的源头了。

    
    // Default forward handler halts the process.
    __attribute__((noreturn)) void 
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    

    相关文章

      网友评论

          本文标题:通过Runtime源码,分析OC消息发送及处理

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