前言
通过 OC – Runimte & objc_msgSend() 文章我们对Runtime
有了初步了解。知道了Runtime
编译时和运行时的区别,以及三种调用方式。最后我还对objc_msgSend
进行了初步分析。
objc_msgSend 汇编代码分析
这篇文章我们继续对 objc_msgSend
汇编代码进行分析。上一篇文章我们分析到 CacheLookup
。
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // stash the original isa // 将x16的值,赋值给x15
LLookupStart\Function:
// p1 = SEL, p16 = isa // => isa -> 类的 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
// x16(isa) 平移 16 就得到了 cache的地址(即为_bucketsAndMaybeMask的地址)
// 将 _bucketsAndMaybeMask的地址存入 p11 寄存器中。
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 = _bucketsAndMaybeMask & #0x0000fffffffffffe
// 如果_bucketsAndMaybeMask第 0 位不等于 0,就跳转到 LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
// 通过哈希求 index 下标
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
// p11 = cache (按位与)-> p10 = buckets
and p10, p11, #0x0000ffffffffffff // p10 = buckets
// p11, LSR #48 => 得到 mask 的值
// p1(_cmd) & mask => index(p12)
and p12, p1, p11, LSR #48 // x12 = _cmd & mask //
#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
// p11 = cache (按位与)-> p10 = buckets
// p1(_cmd) & mask => index(p12)
// PTRSHIFT = 3 (全局搜索)
// (_cmd & mask) << (1+PTRSHIFT) -> (_cmd & mask) << 4
// buckets + 内存平移(1,2,3,4)
// 就得到了 b[i](位移平移) b[i] -> b + i
// p13 当前要查找的 bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// [x13]取 bucket 长度单位 -> *bucket--
// 拿到 bucket 里面的东西 imp(p17) 和 sel(p9)
// 查到的 sel(p9) 和 我们要查的 SEL(p1) 进行比较
// 如果一样就继续往下走,走到 2 里面,CacheHit缓存命中。 如果不等于就走到 3f 里面。其实就是一个循环,直到找到为止。
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;
// p10(当前的第一个地址)和 p13 进行比较。如果 p13 大于 p10,就继续进行平移。再哈希
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
复制代码
PTRSHIFT
的定义
#define PTRSHIFT 3 // 1<<PTRSHIFT == PTRSIZE
复制代码
CacheHit
缓存命中
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
// NORMAL 调用 CacheLookup 方法并带入 NORMAL 参数
.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
复制代码
在 CacheHit
中,通过 if $0 == NORMAL
的判断,进入到了 TailCallCachedImp
中。
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $0(x17) ^ $3(isa) = 编码(imp)
// call imp
eor $0, $0, $3
// 跳转响应的 imp
br $0
.endmacro
复制代码
总结
这就是 objc_msgSend
通过 sel
找 imp
的过程。
-
- 判断
recevier
是否存在
- 判断
-
- 通过
recevier
找到isa
找到class(p16)
- 通过
-
class
内存平移得到cache
,找到bucket
和mask
-
- 通过
bucket
按位左移右移操作(mask)操作
得到bucket
(bucket
掩码得到bucket
)
- 通过
-
- 通过
mask
掩码得到mask
- 通过
-
insert
哈希函数(mask_t)(value & mask)
;
-
- 获取第一次查找哈希下标
index
- 获取第一次查找哈希下标
-
- 通过哈希下标
index
进行平移,把bucket
的首地址 +index
得到整个缓存里的第几个bucket
- 通过哈希下标
-
- 得到
bucket
中存储的imp
和sel
- 得到
- 10.拿到的
sel
与我们查找的sel(_cmd)
进行比较。 -
- 如果相等。就
cacheHit
(缓存命中),就拿到了对应的imp
按位异或isa
就得到了真正imp
的地址。 然后br
调用imp
。
- 如果相等。就
-
- 如果不相等。就
bucket--
继续平移查找。相当于一个“死循环” 变量!
- 如果不相等。就
-
- 如果完全找不到。就会走的
MissLabelDynamic(__objc_msgSend_uncached)
- 如果完全找不到。就会走的
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END