objc_msgSend流程分析

时间:2021-7-3 作者:qvyue

前言

我们知道OC的上层方法调用时,在底层都会转化为objc_msgSend调用,那么它的流程是怎么样的呢,我们又如何理解它, 我们带着这些疑问来分析objc_msgSend。
首先我们先难证一下,OC的上层方法是不是会转化成objc_msgSend,如下图:

objc_msgSend流程分析
1

objc_msgSend流程分析
2
objc_msgSend流程分析
3

我们定义了一个RoPerson类的对象并调用了saySomething,通过汇编我们可以清楚的看到是转化为了objc_msgSend调用,这也就说明了OC上层的方法会被转化为objc_msgSend调用,并且是在libobjc.A.dylib这个动态库中,objc_msgSend是有汇编写的。

objc_msg_Send汇编分析

我们在objc(818版本)的源码中找到objc_msg_Send的代码,如代码所示:

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check,判断当前的消息接受者是否为0,如果没有消息接受者,就无意义了
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa,把当前的isa给到p13寄存器
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class,p13就是isa,x0也就isa
LGetIsaDone: // 这里获取isa已经完成,开始执行下一步操作
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

// 这几行代码如果是*SUPPORT_TAGGED_POINTERS*(后续补充)执行*b.le   LNilOrTagged*,否则执行*b.eq LReturnZero*。
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    b   LGetIsaDone

我们接着分析 GetClassFromIsa_p16这个函数,看看他到底做了什么,代码如下:

// p13(isa)也就是src参数, 1是needs_auth参数, x0(isa)是auth_address参数
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
    // Indexed isa
    mov p16, src           // optimistically set dst = src
    tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
    // isa in p16 is indexed
    adrp    x10, _objc_indexed_classes@PAGE
    add x10, x10, _objc_indexed_classes@PAGEOFF
    ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
    ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
  // 这里不是index isa,所以不执行,直接执行1
1:

#elif __LP64__
.if needs_auth == 0 // _cache_getImp takes an authed class already,needs_auth传过来的是1,所以下面一行代码不执行,执行.else的代码
    mov p16, src

.else
    // 64-bit packed isa
    ExtractISA p16, src, auth_address  // p16是一个空的的地址,src,auth_address都是isa
.endif
#else
    // 32-bit raw isa
    mov p16, src

#endif

.endmacro

我们再看下ExtractISA这个函数的流程执行,全局搜下:

// p16 = isa & ISA_MASK
.macro ExtractISA
    and    $0, $1, #ISA_MASK  // 这行代码就是, $1 逻辑与(按位)ISA_MASK,然后给到$0 也就是p16,and是逻辑与,
.endmacro

这也就是解释了p16 就是class

接着我们再来分析CacheLookup,我们搜下它的宏:

// Mode就是NORMAL, Function就是_objc_msgSend,__objc_msgSend_uncached, MissLabelDynamic ,  MissLabelConstant
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStartFunction 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 LLookupEndFunction,
    //   then our PC will be reset to LLookupRecoverFunction which forcefully
    //   jumps to the cache-miss codepath which have the following
    //   requirements:
    //
    //   GETIMP:
    //     The cache-miss is just returning NULL (setting x0 to 0)
    //
    //   NORMAL and LOOKUP:
    //   - x0 contains the receiver
    //   - x1 contains the selector
    //   - x16 contains the isa
    //   - other registers are set as per calling conventions
    //
    
    mov x15, x16            // stash the original isa,这里x16就是p16,把x16移到x15寄存器中
LLookupStartFunction: // 开始找_objc_msgSend流程
    // p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets
    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 // 真机的架构。
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets,把x16的地址平移CACHE大小,经过全局搜索CACHE是16字节,就是平移到cahce结构体的位置,这个时候p11=cache_t。
    #if CONFIG_USE_PREOPT_CACHES // 这里从来的没找过,所以不执行,我们可以看下它的else,CONFIG_USE_PREOPT_CACHES这个值为1,可以全局搜下
        #if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreoptFunction
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        #else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets,这里就是p11 & 0x0000fffffffffffe(掩码),并赋给10寄存器
    tbnz    p11, #0, LLookupPreoptFunction,p11与0做比较,如果p11不为0,跳转到LLookupPreopt。
        #endif
    eor p12, p1, p1, LSR #7 // p1右移7位存到p12中
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
    #else

//  p11 cache -> p10 = buckets
//  p11, LSR #48 -> mask, 
//  p1(_cmd) &  mask = index -> p12
    and p10, p11, #0x 000 0ffffffffffff // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask,p11 右移48位得到mask值,然后与p1(sel也就是_cmd)进行&运算,得到index值,并赋给p12寄存器。

#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11           // p11 = mask = 0xffff >> p11
    and p12, p1, p11            // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif

// objc - 源码调试 + 汇编
//  p11 cache -> p10 = buckets
//  p1(_cmd) & mask = index -> p12
//  (_cmd & mask)  b + i
//  p13 当前要查找的bucket
//  PTRSHIFT=3
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) = buckets)
    b.hs    1b // 跳转到1,继续执行

    // wrap-around:
    //   p10 = first bucket
    //   p11 = mask (and maybe other bits o n LP64)
    //   p12 = _cmd & mask
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask  first_probed)
    b.hi    4b

LLookupEndFunction:
LLookupRecoverFunction:
    b   MissLabelDynamic

#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreoptFunction:
#if __has_feature(ptrauth_calls)
    and p10, p11, #0x007ffffffffffffe   // p10 = buckets
    autdb   x10, x16            // auth as early as possible
#endif

    // x12 = (_cmd - first_shared_cache_sel)
    adrp    x9, _MagicSelRef@PAGE
    ldr p9, [x9, _MagicSelRef@PAGEOFF]
    sub p12, p1, p9

    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
    // bits 63..60 of x11 are the number of bits in hash_mask
    // bits 59..55 of x11 is hash_shift

    lsr x17, x11, #55           // w17 = (hash_shift, ...)
    lsr w9, w12, w17            // >>= shift

    lsr x17, x11, #60           // w17 = mask_bits
    mov x11, #0x7fff
    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
    and x9, x9, x11         // &= mask
#else
    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
    lsr w9, w12, w17            // >>= shift
    and x9, x9, x11, LSR #53        // &=  mask
#endif

    ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs 

这里我们分析下LLookupPreopt这个函数,搜到如下:

LLookupPreoptFunction:
#if __has_feature(ptrauth_calls)
    and p10, p11, #0x007ffffffffffffe   // p10 = buckets, 这里是p11寄存器逻辑与(按位)0x007ffffffffffffe,给到p10,也就是说p10=buckets。
    autdb   x10, x16            // auth as early as possible
#endif

    // x12 = (_cmd - first_shared_cache_sel)
    adrp    x9, _MagicSelRef@PAGE
    ldr p9, [x9, _MagicSelRef@PAGEOFF]
    sub p12, p1, p9

    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
    // bits 63..60 of x11 are the number of bits in hash_mask
    // bits 59..55 of x11 is hash_shift

    lsr x17, x11, #55           // w17 = (hash_shift, ...)
    lsr w9, w12, w17            // >>= shift

    lsr x17, x11, #60           // w17 = mask_bits
    mov x11, #0x7fff
    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
    and x9, x9, x11         // &= mask
#else
    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
    lsr w9, w12, w17            // >>= shift
    and x9, x9, x11, LSR #53        // &=  mask
#endif

    ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs 

我们再来看下CacheHit这个函数,代码如下:

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
    TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
    mov p0, p17
    cbz p0, 9f          // don't ptrauth a nil imp
    AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9:  ret             // return IMP
.elseif $0 == LOOKUP
    // No nil check for ptrauth: the caller would crash anyway when they
    // jump to a nil IMP. We don't care if that jump also fails ptrauth.
    AuthAndResignAsIMP x17, x10, x1, x16    // authenticate imp and re-sign as IMP
    cmp x16, x15
    cinc    x16, x16, ne            // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
    ret             // return imp via x17
.else
.abort oops
.endif
.endmacro

TailCallCachedImp这个函数,就是对imp进行编码,并跳转到imp

未完待续

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。