iOS底层探索之objc_msgSend流程——慢速查找

在上一篇iOS底层探索之objc_msgSend流程——快速查找文章中,我们分析了快速查找流程,如果快速查不到,则需要进入慢速查找流程,以下是慢速查找的分析过程

objc_msgSend慢速查找流程

在快速查找流程中,如果没有找到方法实现,无论是走到CheckMiss或者JumpMiss,最终会走到__objc_msgSend_uncached的汇编函数。找到源码如下:

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
    
MethodTableLookup
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

通过分析__objc_msgSend_uncached的核心是MethodTableLookup查找方法列表。而MethodTableLookup的实现的核心源码为_lookUpImpOrForward

通过断点调试看是不是会走lookUpImpOrForward这个函数,于是我们在那个调用方法时进行断点调试:

在调用方法时加一个断点,执行断住,按住control + stepinto,进入汇编

image.png

_objc_msgSend_uncached加一个断点,执行断住,按住control + stepinto,进入汇编
image.png

发现lookUpImpOrForward并不是汇编实现而是C++实现。于是我们通过查找源码,全局搜索lookUpImpOrForward

lookUpImpOrForward分析

最终在objc-runtime-new.mm的文件中找到了源码实现,源码如下:

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();

    //快速查找,如果找到则直接返回imp
    //目的:防止多线程操作时,刚好调用函数,此时缓存进来了
    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

//加锁,目的是保证读取的线程安全
    runtimeLock.lock();

    //判断是否是一个已知类:判断当前类是否是已经被认可的类,即已经加载的类
    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.assertLocked();
    curClass = cls;

    //--查找类的缓存
    //unreasonableClassCount--表示类的迭代的上限
    //(猜测这里递归的原因是attempts在第一次循环时作了减一操作,然后再次循环时,仍在上限的范围内,所以可以继续递归)
    for (unsigned attempts = unreasonableClassCount();;) {
        //--当前类方法列表(采用二分查找算法),如果找到,则返回,将方法缓存到cache中。
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //当前类 - 当前类的父类,并判断父类是否nil
        if (slowpath((curClass = curClass->superclass) == nil)) {
            //--未找到方法实现,方法解析器也不行,使用转发
            imp = forward_imp;
            break;
        }

        //如果父类链中存在循环,则停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }
        //objc_msgSend->二分查找->cache_fill写入缓存->objc_msgSend
       // --父类缓存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 如果在父类中找到了forward,则停止查找,且不缓存,首先调用此类的方法解析器
            break;
        }
        if (fastpath(imp)) {
          //如果在父类中,找到了此方法,将其存储到cache中
            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;
}

forward_imp的赋值

forward_imp是通过_objc_msgForward_impcache函数赋值的。全局搜索一下。发现这个函数是个汇编函数,找到其实现:

//入口
STATIC_ENTRY __objc_msgForward_impcache

//跳转__objc_msgForward
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

//__objc_msgForward入口
ENTRY __objc_msgForward
//赋值并且返回x17的地址。
adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward

根据源码分析重点__objc_forward_handler

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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

看着objc_defaultForwardHandler很眼熟,这就是我们在日常开发中最常见的错误:没有实现函数,运行程序,崩溃时报的错误提示
forward_imp的值就是未找到imp的函数实现

cache缓存中进行查找

if (fastpath(behavior & LOOKUP_CACHE)) { 
      imp = cache_getImp(cls, sel);
      if (imp) goto done_nolock;
}

这里的目的是防止多线程操作时,刚好调用了函数,此时缓存进来了,_cache_getImp快速查找,如果找到直接返回imp

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret

    END_ENTRY _cache_getImp

检查类是否合法

 checkIsKnownClass(cls);

确保cls在已知类列表中,避免传入一个非类的二进制文件,进行CFI攻击。

确定当前类的继承链和isa的继承链

确定当前类父类,并且递归执行,最终确定类的继承链
确定元类,并把元类的继承链也递归确定下来;
initlize时,执行addSubClass(),将子类也存入父类中,从而实现了双向链表

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls, nil);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        ASSERT(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        rw = objc::zalloc();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        cls->setInstancesRequireRawIsa();
    } else {
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
#endif

    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    cls->setInstanceSize(ro->instanceSize);

    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls, previously);
    //cls->双向链表结构
    return cls;
}

进入for死循环按照类的继承链或者元类的继承链的顺序查找。

当前cls的方法列表中使用二分查找算法查找方法,如果找到,则进入cache写入流程,并返回imp,如果没有找到,则返回nil

当前cls被赋值为父类,如果父类等于nil,则imp = 消息转发,并终止递归,进入判断是否执行过动态方法解析:如果没有,则执行动态方法解析;如果执行过一次动态方法解析,则走到消息转发流程。

如果父类链中存在循环,则报错,终止循环父类缓存中查找方法, 如果未找到,则直接返回nil,继续循环查找; 如果找到,则直接返回imp,执行cache写入流程

getMethodNoSuper_nolock 方法
getMethodNoSuper_nolock查找本类中的方法列表

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //  getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

search_method_list_inline 方法

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

findMethodInSortedMethodList 方法二分查找

//二分查找
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    //base相当于low,count是max,probe是middle,这就是二分
    for (count = list->count; count != 0; count >>= 1) {
        //从首地址+下标 --> 移动到中间位置(count >> 1 左移1位即 count/2 = 4)
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        //如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
        if (keyValue == probeValue) {
            // -- while 平移 -- 排除分类重名方法
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                //排除分类重名方法(方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
                //如果是两个分类,就看谁先进行加载
                probe--;
            }
            return (method_t *)probe;
        }
         //如果keyValue 大于 probeValue,就往probe即中间位置的右边查找
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

进入动态方法决议

上面for循环跳出来之后,说明没有找到imp,会进入动态方法决议。

 if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //动态方法决议的控制条件,表示流程只走一次
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

查找到imp结果

当我们找到imp时,就会直接进入done流程,将cls的sel和imp写入缓存中(便于下次同样的方法,可以在cache汇编快速查询到)。

done结束后,我们会进入done_nolock流程, 如果imp是forward_imp,就返回nil,否则,返回正常的imp

done:
    // 从将sel和imp写入cls的缓存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    // 运行时解锁
    runtimeLock.unlock();

 done_nolock:
    //如果不需要找了(LOOKUP_NIL),并且imp等于forward_imp,就返回nil。
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    // 返回当前获取的imp
    return imp;

总结

当在objc_msgSend缓存中没有找到方法,就会来到CheckMiss -> __objc_msgSend_uncached -> MethodTableLookup -> lookUpImpOrForward进行慢速查找流程。lookUpImpOrForward中先在本类中查找方法,如果没有找到就会循环的去父类当中找,直到找到NSObject中,如果没有找到,就会进行动态方法决议,动态方法不处理,则进入动态消息转发阶段。此时可以在动态消息转发阶段做一下处理,如果还不进行处理,最后崩溃报错unrecognized selector sent to instance ...

你可能感兴趣的:(iOS底层探索之objc_msgSend流程——慢速查找)