iOS 底层学习8

iOS 底层第8天的学习。今天的内容是分析objc_msgSend有点难,需要一步步去分析,最后得出一个流程。

objc_msgSend 分析

  • 在底层源码中搜索 objc_msgSend 找到 arm64 架构下的 objc-msg-arm64.s 文件,点击进入开始分析汇编。
  • 开始分析前说明一下:所有代码分析都是针对 arm64 架构的。
  • 接下来我们开始分析
cmp p0, #0 
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
// 补充 LNilOrTagged
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

p0 = receivercmp 判断消息接受者是否存在,#0 = 当消息接受者不存在 进入直接返回
LReturnZero。 这里 SUPPORT_TAGGED_POINTERSarm64 就返回 1,也就是会进入 LNilOrTagged,而 LNilOrTagged 也会返回 LReturnZero -> LGetIsaDone 说明已经找到了 isa.

  • 当消息接受者存在的时候继续走下面的代码
ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
  • ldr p13, [x0] 将存储器地址为x0的字数据读入寄存器 p13p13 -> 对象的首地址-> isa, x0 -> isa
  • GetClassFromIsa_p16 p13, 1, x0 把参数传入 GetClassFromIsa_p16
    搜索 GetClassFromIsa_p16 找到 macro 定义

.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
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
    mov p16, \src
.else
    // 64-bit packed isa
    ExtractISA p16, \src, \auth_address
.endif
#else
    // 32-bit raw isa
    mov p16, \src

#endif

.endmacro

GetClassFromIsa_p16 参数表

参数名 参数传入的值
src p13(isa)
needs_auth 1
auth_address x0(isa)
  • 我们看下 SUPPORT_INDEXED_ISA 这个判断条件
#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif
  • 1: __ARM_ARCH_7K__ 根据名称可以得出是定义在目标为 ARM 7k 架构 CPU ,这里应该主要是针对 watchOS的,不满足条件。

  • 2: (__arm64__ && !__LP64__)真机环境 __arm64__= 1, __LP64__ = 1 , 1 && 0 = 0 ,因此SUPPORT_INDEXED_ISA = 0

  • 继续往下分析 needs_auth = 0 肯定不成立,我们看 GetClassFromIsa_p16 参数表可知 needs_auth =1.

  • if判断不成立 =>ExtractISA p16, \src, \auth_address ,把 p16(空地址),src,auth_address 传入到 ExtractISA。接着全局搜索 ExtractISA

// A12 不是普遍版本
#if __has_feature(ptrauth_calls)
// JOP
  // ... 省略部分代码
.macro ExtractISA
    and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
    xpacd   $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    mov x10, $2
    movk    x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
    autda   $0, x10
#endif
.endmacro
// JOP
#else
.macro ExtractISA
    and    $0, $1, #ISA_MASK
.endmacro
// not JOP
#endif
  • #if __has_feature(ptrauth_calls) 只有A12 架构会进入,不是普遍版本,直接看 #else下面的代码

and $0, $1, #ISA_MASK 参数表

参数名 参数传入的值
$0 p16
$1 src= p13(isa)
  • and $0, $1, #ISA_MASK => isa & ISA_MASK = p16
  • 就这样我们就得到了 GetClassFromIsa_p16 p13, 1, x0 => p16 = class
  • p16 = class
  • 继续往下进行分析
LGetIsaDone: // 函数名
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
  • 搜索 CacheLookup 如下
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

    mov x15, x16            // stash the original isa
LLookupStart\Function:          // 函数名
    // 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
    #if CONFIG_USE_PREOPT_CACHES
        #if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        #else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
        #endif
    eor p12, p1, p1, LSR #7
    and  p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
    #else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask

CacheLookup 参数表

参数名 参数传入的值
Mode NORMAL
Function _objc_msgSend
MissLabelDynamic __objc_msgSend_uncached
MissLabelConstant nil = MissLabelConstant
  • mov x15, x16 , 把p16 isa 移到 x15x16 = p16 ,
  • LLookupStart\Function: 查找 Function = _objc_msgSend
  • arm64 架构下直接看 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 判断下的代码
  • ldr p11, [x16, #CACHE]CACHE 多少不知道,搜索找到如下代码
#define CACHE            (2 * __SIZEOF_POINTER__) // __SIZEOF_POINTER__  = 8, CACHE = 16
  • ldr p11, [x16, #CACHE] => x16 class 平移 16 找到 cache_t, 把cache_t 存到 p11 里。
  • p11 = cache_t
  • #if CONFIG_USE_PREOPT_CACHES 是多少,搜索下
#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
#define CONFIG_USE_PREOPT_CACHES 1
#else
#define CONFIG_USE_PREOPT_CACHES 0
#endif
  • arm64 => CONFIG_USE_PREOPT_CACHES = 1,继续看代码
  • #if __has_feature(ptrauth_calls) 只有A12 架构会进入,直接进入 #else
  • and p10, p11, #0x0000fffffffffffe => p11 & #0x0000fffffffffffe => buckets ,
  • p10 = buckets
  • tbnz p11, #0, LLookupPreopt\Function ——> 当前的 p11#0 作比较,不等于 0 就跳转 LLookupPreopt.
  • 继续分析,已经知道 p10 = buckets ,我们的目的是为了找到 buckets 里的 bucket,再根据 x1 = selbucket里的 sel 进行比对, 找到相应的 imp.
 eor   p12, p1, p1, LSR #7
 and   p12, p1, p11, LSR #48` 
  • eor p12, p1, p1, LSR #7 => _cmd ^ _cmd >> 7 = value ^= value >> 7
  • 为什么要在读的时候 _cmd 要右移 7 呢?
  • 因为在 insert 时计算 begin 的时, 在 cache_hash 函数 value ^= value >> 7 ,所以在读的时候也要进行相同的操作。
  • p11, LSR #48 => p11(cache_t) 右移 48 得到了 mask .
  • 为什么 要右移 48 位就得到了 mask呢?
  • 因为 总共 64 位,前 16 位存了 mask ,后 48 位存了 buckets,当右移动 48位 就把buckets 移除了,所以 右移 48 得到了 mask .
  • -> and p12, p1, mask -> p1(_cmd ^ _cmd >> 7) & mask = p12.
  • 那这个p12 存的是什么呢?回到 cache_t 中的源码进行查看
// insert 函数里 计算 begin
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
//  cache_hash 方法
static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
    #if CONFIG_USE_PREOPT_CACHES
        value ^= value >> 7;
    #endif
    return (mask_t)(value & mask);
}
  • 分析源码得知在 insert 时候 begin = cache_hash(sel, m); 根据 mask 后得到的begin, 和 p1 & mask = p12 其实是计算 index, 会根据index去找 bucket.
  • 当我们得到 p12 存了 index 后继续往下分析
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

  • add p13, p10, p12, LSL #(1+PTRSHIFT) ,搜索已知 PTRSHIFT = 3, p10 = buckets
  • => p12, LSL #(1+PTRSHIFT) = index << 4 => 可以理解为地址的平移, 相当于 buckets[i]
  • 为什么 index << 4 要左移 4 位呢?
  • 举个 index = 2
  index = 2 
  index  << 4 =>  00 10 << 4  = 10 0000 = 32
  32 = 2 * 16  => 向左平移了 2 个 bucket(sel,imp) 的大小
 平移的目的就为了找到 bucket
  • => add p13, p10,(index << 4) => p13 = buckets[i] = bucket
  • p13 = bucket
                       // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
   cmp p9, p1              //     if (sel != _cmd) {
   b.ne    3f              //         scan more
                       //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                       //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
   cmp p13, p10            // } while (bucket >= buckets)
   b.hs    1b
  • 这里1:,2:,3: 就相等于一个 do white 循环
  • 1: ldp p17, p9, [x13], #-BUCKET_SIZE ,[x13] = bucket 进行 #-BUCKET_SIZE = bucket-- ,ldp 就是将[x13] 偏移 BUCKET_SIZE = 16个字节的值取出来,放入p17p9。这里的 p9 = sel,p17 = imp.
  • cmp p9, p1 => 比较 sel_cmd
    • 如果不相同调用 b.ne 3f 就会调用 3:cbz p9, \MissLabelDynamic
      • 3:cbz p9, \MissLabelDynamic => 寻找 p9,如果 p9 = 0 调用 MissLabelDynamic = __objc_msgSend_uncached
    • 如果相同调用 CacheHit \Mode ,搜索下 CacheHit 找到 macro
.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

  • 参数Mode = NORMAL ,$0 == NORMAL = true
  • x17 = imp, x10 = buckets, x1 = sel, x16 = isa = class
  • TailCallCachedImp x17, x10, x1, x16
  • 搜索 TailCallCachedImp 继续分析
.macro TailCallCachedImp
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    eor $0, $0, $3
    br  $0
.endmacro
  • eor $0, $0, $3 => $0 = cached imp,$3 = isa, imp ^ isa => imp code 再存入 $0

  • br $0 => call imp

  • 继续 cmp p13, p10 => p13 = bucketp10 = buckets 首地址 进行比较,如果bucket >= buckets 调用 b.hs 1b 继续进行内存平移。

  • 如果 bucket 已经走到了 0 好位置,还不相等 怎么办呢?继续往下分析

add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket
                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b
  • buckets + (mask << 1+PTRSHIFT) => mask 向左移动了 4 位置 => 7 * 16 => p13 定位到最后一个的位置。
  • add p12, p10, p12, LSL #(1+PTRSHIFT) => p12 = first probed bucket
  • ccmp p13, p12, #0, ne // bucket > first_probed)
  • 为什么 bucket > first_probed?
  • 因为之前已经比较过一次,所以这里就必须大于 first_probed 否则还要走一次进行比较

  • 以上分析就是 selimp 的过程

objc_msgSend 流程

  • objc_msgSend主要是通过 SEL _cmd 找到bucketsel,通过compare 最后锁定到 imp,这一步步的查找过程到底是如何的,我们在这里画个流程图再具体梳理下

你可能感兴趣的:(iOS 底层学习8)