objc_msgSend方法查找-慢速查找

243 阅读5分钟
  • 快速查找的核心方法lookUpImpOrForward源码分析

    • __objc_msgSend_uncached
      通过juejin.cn/post/688866…这篇文章我们可以知道在快速查找找不到的时候会进入到__objc_msgSend_uncached方法中去代码如下:
      .macro CheckMiss
        // miss if bucket->sel == 0
      .if $0 == GETIMP
          cbz	p9, LGetImpMiss
      .elseif $0 == NORMAL
          cbz	p9, __objc_msgSend_uncached
      .elseif $0 == LOOKUP
          cbz	p9, __objc_msgLookup_uncached
      .else
      .abort oops
      .endif
      .endmacro
      
    • MethodTableLookup
      通过上面的步骤我们可以知道再方法没有快速查找成功的时候最后会进入到__objc_msgSend_uncached方法,再看__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
      
      发现核心代码就是MethodTableLookup(这里也可以断点验证一下)
      1. 在调用方法出给个断点如下图 因为该方法是第一次调用所以快速找找一定查找不到会进入到__objc_msgSend_uncached方法,此时control + stepinto会进入到objc_msgSend汇编如下图
      2. __objc_msgSend_uncached方法出给个断点
        进入到objc_msgSend汇编之后往下翻找到__objc_msgSend_uncached然后在给个断点如下图 然后过掉一个断点果不其然走到__objc_msgSend_uncached断点处了,继续control + stepinto回来到_objc_msgSend_uncached的汇编代码如下图
      3. lookUpImpOrForward所在行给个断点
        同样的进入到_objc_msgSend_uncached的汇编代码之后呢继续lookUpImpOrForward所在行给个断点然后过掉一个断点果不其然最后走入到了lookUpImpOrForward方法中去
    • lookUpImpOrForward
      从上面的源码探索不难看出lookUpImpOrForward并不是一个汇编实现的方法,因为写在.mm文件中然后此时在全局搜索lookUpImpOrForward查找到lookUpImpOrForward的源码实现(其实上个步骤的汇编代码中也有告诉你就在objc-runtime-new.mm文件中的第6095行)源码分析如下:
      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();
      
          // Optimistic cache lookup
          //首先去缓存中查找如果有的话直接返回
          //LOOKUP_CACHE 目的是方式多线程访问的时候刚好调用函数,然后缓存进来
          if (fastpath(behavior & LOOKUP_CACHE)) {
              imp = cache_getImp(cls, sel);
              if (imp) goto done_nolock;
          }
      
          // runtimeLock is held during isRealized and isInitialized checking
          // to prevent races against concurrent realization.
      
          // runtimeLock is held during method search to make
          // method-lookup + cache-fill atomic with respect to method addition.
          // Otherwise, a category could be added but ignored indefinitely because
          // the cache was re-filled with the old value after the cache flush on
          // behalf of the category.
      
          //加锁,目的是保证读取的线程安全
          runtimeLock.lock();
      
          // We don't want people to be able to craft a binary blob that looks like
          // a class but really isn't one and do a CFI attack.
          //
          // To make these harder we want to make sure this is a class that was
          // either built into the binary or legitimately registered through
          // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
          //
          // TODO: this check is quite costly during process startup.
      
          //判断当前的类是不是已知的类、即已经加载的类
          checkIsKnownClass(cls);
      
          //判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链、ro、以及rw等,方法后续的循环
          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 may have been dropped but is now locked again
      
              // 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
          }
      
          runtimeLock.assertLocked();
          curClass = cls;
      
          // The code used to lookpu the class's cache again right after
          // we take the lock but for the vast majority of the cases
          // evidence shows this is a miss most of the time, hence a time loss.
          //
          // The only codepath calling into this without having performed some
          // kind of cache lookup is class_getInstanceMethod().
      
          //表示类的迭代上线
          for (unsigned attempts = unreasonableClassCount();;) {
              // curClass method list.
              //在当前类的方法列表中查找,如果查找到了直接返回(二分查找)
              Method meth = getMethodNoSuper_nolock(curClass, sel);
              if (meth) {
                  imp = meth->imp;
                  goto done;
              }
      
              //没有查找到的话将当前类的父类赋值给curClass
              //然后再判断当前类的父类是否位nil  如果是的话则未找到方法的实现,然后实现消息转发
              if (slowpath((curClass = curClass->superclass) == nil)) {
                  // No implementation found, and method resolver didn't help.
                  // Use forwarding.
                  imp = forward_imp;
                  break;
              }
      
              // Halt if there is a cycle in the superclass chain.
              // 如果在父类中存在循环,则停止
              if (slowpath(--attempts == 0)) {
                  _objc_fatal("Memory corruption in class list.");
              }
      
              // Superclass cache.
              // 在父类的缓存中查找
              imp = cache_getImp(curClass, sel);
              if (slowpath(imp == forward_imp)) {
                  // 如果在父类中找到了forward,则停止查找,且不缓存,首先调用此类的方法解析器
                  // Found a forward:: entry in a superclass.
                  // Stop searching, but don't cache yet; call method
                  // resolver for this class first.
                  break;
              }
              if (fastpath(imp)) {
                  // Found the method in a superclass. Cache it in this class.
                  // 在父类中找到该方法,并缓存到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;
      }
      
      
      流程图如下:
  • _objc_msgForward_impcache探索

    源码解析
    STATIC_ENTRY __objc_msgForward_impcache
    
     // No stret specialization.
     //直接跳转到该方法
     b	__objc_msgForward
    
     END_ENTRY __objc_msgForward_impcache
    
     
     ENTRY __objc_msgForward
    
     //赋值到x17寄存器中
     adrp	x17, __objc_forward_handler@PAGE
     //返回x17寄存器的地址
     ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
     TailCallFunctionPointer x17
     
     END_ENTRY __objc_msgForward
    
    到这里并没有结束继续全文搜索__objc_forward_handler发现并没有搜索成功,猜测可能是c/c++方法,然后去掉一个下划线继续检索找到如下代码:
    // Default forward handler halts the process.
     __attribute__((noreturn, cold)) 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);
     }
     void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    
    发现_objc_msgForward_impcache其实就是没有找到imp的最终实现,程序中断报错找不到该方法