美文网首页
方法查找流程 objc-msg-x86_64.s msg_se

方法查找流程 objc-msg-x86_64.s msg_se

作者: 答案不止一个 | 来源:发表于2020-09-16 12:14 被阅读0次

先记录一部分笔记,剩下的继续学习补充

//  movb(8位)、movw(16位)、movl(32位)、movq(64位)
// 最后的b,w,l,q 是区分不同的位数

cache_getImp

  • a1 = class whose cache is to be searched a1 是class
  • a2 = selector to search for a2 是sel
/********************************************************************
 * IMP cache_getImp(Class cls, SEL sel)
 *
 * On entry:    a1 = class whose cache is to be searched
 *      a2 = selector to search for
 *
 * If found, returns method implementation.
 * If not found, returns NULL.
 ********************************************************************/

    STATIC_ENTRY _cache_getImp

// do lookup
    // 将class 复制给 r10 位 CacheLookup 做准备
    movq    %a1, %r10       // move class to r10 for CacheLookup
    // returns IMP on success
    CacheLookup NORMAL, GETIMP, _cache_getImp

// _cache_getImp 查找缓存是没有找到的跳转
LCacheMiss_cache_getImp:
// cache miss, return nil
    xorl    %eax, %eax
    ret

    END_ENTRY _cache_getImp

CacheLookup

调用方式: CacheLookup NORMAL, GETIMP, _cache_getImp
所以 0 是就是 NORMAL,1 就是GETIMP $2就是 _cache_getImp
第一个参数 normal 或者 Strat 表示 isa 和cm的参数的位置不同
第二个参数 GETIMP : 返回计算后的img , CALL 直接调用imp。 LOOKUP 将计算得到的imp 存入r11
第三个参数: 这是一个标记, 比如cachemiss 是跳转到哪里

参考

  • r11 64-bit register
  • r11d 是Lower 32 bits
.macro  CacheLookup
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStart$1 label we may have loaded
    //   an invalid cache pointer or mask.
    //
    //   When task_restartable_ranges_synchronize() is called,
    //   (or when a signal hits us) before we're past LLookupEnd$1,
    //   then our PC will be reset to LCacheMiss$1 which forcefully
    //   jumps to the cache-miss codepath which have the following
    //   requirements:
    //
    //   GETIMP:
    //     The cache-miss is just returning NULL (setting %rax to 0)
    //
    //   NORMAL and STRET:
    //   - a1 or a2 (STRET) contains the receiver  class 
    //   - a2 or a3 (STRET) contains the selector  
    //   - r10 contains the isa
    //   - other registers are set as per calling conventions
    //
LLookupStart$2:

// 将sel 复制给 r11
.if $0 != STRET
    movq    %a2, %r11       // r11 = _cmd 
.else
    movq    %a3, %r11       // r11 = _cmd
.endif
    // r11d -> r11去8位数据     24(%r10) r10  isa的指针 偏移 24个字节(isa 8, superclass 8 , class->cache.buckets 8) class->cache.mask
    // 根据 cmd 和 _cmd & class->cache.mask 计算hashkey 并复制给r11
    andl    24(%r10), %r11d     // r11 = _cmd & class->cache.mask  
    
    // $$4  -> 4   这是根据hashkey << 4 计算hashkey个  16位/2字节   bucket是2个字节大小(sel 8, imp 8)
    // 根据hashkey 之后计算 hashkey对应的偏移量
    shlq    $$4, %r11       // r11 = offset = (_cmd & mask)<<4
    
    // 类似 24(%r10)  16(%r10) 就是 class->cache.bucket
    //  根据偏移量,找到对应hashkey的 bucket地址
    addq    16(%r10), %r11      // r11 = class->cache.buckets + offset
    
    // 判断当权位置是否等于sel hash冲突判断
.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    
    // 如果不想等 则去下一个查找 (根据cache的存储逻辑)
    jne     1f          //     scan more
    // 相等 跳转CacheHit $0 , $1 ; $0,$1 是传入的参数
    CacheHit $0, $1         // call or return imp

//  检查是否已经到了最后一位/或者下一位是空的。就返回查不到
//  否则, 计算下一个bucket的地址
1:  
    // loop
    // r11 是刚才计算到hashkey对应的 bucket
    // cache_sel 位0  =>   0(%r11)  就是 bucket.sel
    cmpq    $$1, cached_sel(%r11)
    
    // if (bucket->sel <= 1) 就是说查不到, 跳转到退出
    jbe 3f          // if (bucket->sel <= 1) wrap or miss
    
    // 计算下一个入职的bucket地址 复制给r11
    addq    $$16, %r11      // bucket++

// 
2: 
    // 比较 cmd 和当前 bucket->sel 是否相同
.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    // 不相同 hashkey冲突,继续乡下解决
    jne     1b          //     scan more
    // 相同,则就是跳转cachehit
    CacheHit $0, $1         // call or return imp

// 查找下一个的bucket出现:
// 空的存储  -> 跳转cacheMiss
// end标志  -> end标志的imp (第一个bucket的地址)复制给r11 ,继续查找
3:
    // 根据上面的判断,cmpq $$1, cached_sel(%r11) 再判断
    // 如果是小于, 这属于 下一个是空的,找不到了
    // wrap or miss
    jb  LCacheMiss$2        // if (bucket->sel < 1) cache miss
    
    //  否则 下一个 bucket->sel == 1 表示到了最后的结尾,其结尾的imp 存的是开始的bucket地址
    // 将下一个bucket设置位 第一个bucket 的地址,再次查询比较
    movq    cached_imp(%r11), %r11  // bucket->imp is really first bucket
    jmp     2f

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

1:
    // loop
    cmpq    $$1, cached_sel(%r11)
    jbe 3f          // if (bucket->sel <= 1) wrap or miss

    addq    $$16, %r11      // bucket++
2:  
.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    CacheHit $0, $1         // call or return imp

3:
    // double wrap or miss
    jmp LCacheMiss$2

LLookupEnd$2:
.endmacro

CacheHit

就是找到了imp。然后根据类型返回或者执行.其中r11 是在之前CacheLookup中找到的 bucket的地址

  1. CALL : 执行IMP,并返回imp 放在r11
  2. LOOKUP: 返回IMP ^ isa
.macro CacheHit
    // r11 是在 
.if $1 == GETIMP
    // r11  是bucket  r10  isa
    // #define cached_imp   8  cached_imp(%r11)就是bucket的imp地址
    // %rax
    % rax 作为函数返回值使用。
    movq    cached_imp(%r11), %rax  // return imp
    //  判断imp 是否为0
    cmpq    $$0, %rax   // $$0  就是直接取值 0 就是判断imp 是否为nil
    // 为0 直接返回
    jz  9f          // don't xor a nil imp 
    //  如果rmp 不为nil ,则按照原先存的的逻辑,, 在xor 计算回来。参考cache_t存储imp的存储逻辑
    // r10 是isa
    xorq    %r10, %rax      // xor the isa with the imp
9:  ret

.else
.if $1 == CALL
    movq    cached_imp(%r11), %r11  // load imp
    xorq    %r10, %r11          // xor imp and isa
.if $0 != STRET
    // ne already set for forwarding by `xor`
.else
    cmp %r11, %r11      // set eq for stret forwarding
.endif
    // 跳转到imp的地址 去执行imp
    jmp *%r11           // call imp

.elseif $1 == LOOKUP
    movq    cached_imp(%r11), %r11
    xorq    %r10, %r11      
    ret
    
.else
.abort oops
.endif

.endif

.endmacro

GetIsaFast

调用: GetIsaFast NORMAL
根据isa的 nonpointer 位的标示,获取正确的isa地址
将isa的地址 保存到 r10上

  • a1 or a2 (STRET) contains the receiver
  • a2 or a3 (STRET) contains the selector

MethodTableLookup

去执行 lookUpImpOrForward 方法 。能查到方法,放回方法。
没有查到方法,返回的是_objc_msgForward_impcache 消息转发流程

// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// 参数的赋值  a1 obj, a2 sel  a3 cls  a4 behavior
if $0 == NORMAL
    // receiver already in a1
    // selector already in a2
.else
    movq    %a2, %a1
    movq    %a3, %a2
.endif
    movq    %r10, %a3
    movl    $$3, %a4d 
    call    _lookUpImpOrForward

其他的是传参数

__objc_msgSend_uncached

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
    MethodTableLookup NORMAL    // r11 = IMP
    jmp *%r11           // goto *imp

    END_ENTRY __objc_msgSend_uncached

_objc_msgLookup

先去缓存查找 CacheLookup NORMAL, LOOKUP, _objc_msgLookup

  1. 缓存中找到imp,放入r11 然后判断是否是null 再将isa 放入r10
  2. 找不到跳转 LCacheMiss_objc_msgLookup
    cachemiss_objc_msgLookup 再去方法列表中查找 -> __objc_msgLookup_uncached -> MethodTableLookup
ENTRY _objc_msgLookup
    //  #define NORMAL 0
    NilTest NORMAL

    // 就是获取对应的isa的值 (如果是优化过的,需要使用ISA_MASK去获取)然后赋值到 r10上
    GetIsaFast NORMAL       // r10 = self->isa
    // returns IMP on success
    // 去缓存查找
    CacheLookup NORMAL, LOOKUP, _objc_msgLookup
    // returns an IMP in r11 that returns zero.
    NilTestReturnIMP NORMAL
    // 再将isa 放入r10
    GetIsaSupport NORMAL
    
// cache miss: go search the method lists
LCacheMiss_objc_msgLookup:
    // isa still in r10
    jmp __objc_msgLookup_uncached

    END_ENTRY _objc_msgLookup

_objc_msgSend

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    NilTest NORMAL
    
    //对象/类 isa数据根据 nonpointer位,获取其class的地址 / mataclass 的地址
    GetIsaFast NORMAL       // r10 = self->isa
    
    // calls IMP on success
    // 去cache中查找 ,如果找到直接执行imp,并放回imp, (参考 CacheHit NORMAL CALL)
    // 如果没有查找到imp, 就会 跳转到 LCacheMiss_objc_msgSend ($2 _objc_msgSend) 
    CacheLookup NORMAL, CALL, _objc_msgSend
    
    // 判断返回是否是nil
    NilTestReturnZero NORMAL
    
    GetIsaSupport NORMAL

// cache miss: go search the method lists
// 
LCacheMiss_objc_msgSend:
    // isa still in r10
    // __objc_msgSend_uncached 就回去执行MethodTableLookup 
    // 然后执行 lookUpImpOrForward 就是去查方法列表,
    // 然后执行返回的方法
    jmp __objc_msgSend_uncached 

    END_ENTRY _objc_msgSend

总结

msg_send.jpg

重点:

  1. 先查找缓存,找到直接执行
  2. cache_t 的方法存储逻辑,以及查找逻辑
  3. cacheHit 根据参数 $1 参数 处理(CALL 直接执行, GET_IMP 判断是否nil,返回IMP,LOOKUP (不判断nil, r11 存储IMP))。
  4. LCacheMiss_objc_msgSend 去类的方法列表中去查,执行 _lookUpImpOrForward, behavior 设为LOOKUP_INITIALIZE | LOOKUP_RESOLVER

相关文章

  • 方法查找流程 objc-msg-x86_64.s msg_se

    先记录一部分笔记,剩下的继续学习补充 cache_getImp a1 = class whose cache is...

  • iOS 消息转发流程

    runtime方法查找流程及消息转发 方法查找 方法查找的流程:缓存查找-->当前类查找-->父类逐级查找 1.缓...

  • (2021 objc4-818源码分析)方法查找流程-动态决议&

    方法查找流程 【第一步】 方法查找流程-快速查找流程[https://www.jianshu.com/p/8cfa...

  • runtime方法查找流程及消息转发

    方法查找 方法查找的流程:缓存查找-->当前类查找-->父类逐级查找 1.缓存 看看缓存中是否有对应的方法实现...

  • OC 消息转发流程分析

    上一篇消息查找流程 探索了消息查找流程:快速查找和慢速查找以及动态方法解析。但消息机制还不完整,如果动态方法解析之...

  • 方法查找流程-快速查找流程

    上一篇我们讲了方法写入流程:objc_class cache分析[https://www.jianshu.com/...

  • iOS消息转发

    我们已经研究了objc_msgSend从汇编快速查找缓存流程,慢速查找流程,动态方法决议流程,如果这几个流程下来都...

  • iOS消息转发及其应用

    什么是方法 方法查找流程[https://www.jianshu.com/p/62ecc3f31467]方法查找慢...

  • iOS 消息转发机制

    上节(iOS 消息查找流程)我们讲到,在iOS中对象调用方法,会经历方法的查找,如果查找到方法的IMP,那么就返回...

  • 消息动态决议

    前言 前面我们分析了消息的查找流程,快速查找流程,慢速查找流程。如果这两种方式都没找到方法的实现,苹果给了两个建议...

网友评论

      本文标题:方法查找流程 objc-msg-x86_64.s msg_se

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