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

[图片上传失败...(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中实现。如图:

通过Runtime源码,分析OC消息发送及处理_第1张图片

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__。通过查看伪代码,会有以下发现:

通过Runtime源码,分析OC消息发送及处理_第2张图片

通过Runtime源码,分析OC消息发送及处理_第3张图片

 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;
    }
通过Runtime源码,分析OC消息发送及处理_第4张图片
经典走位图

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

// 只有汇编调用  没有源码实现
+ (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消息发送及处理)