这是我参与更文挑战的第10天,活动详情查看: 更文挑战
这一篇文章我们来完整梳理一遍
objc_msgSend
的流程
我们以arm64
架构为例来分析流程:
objc_msgSend流程
ENTRY _objc_msgSend
cmp p0, #0
寄存器p0是objc_msgSend的第一个参数id self,也就是isa,和0比较判断有没有接收者,没有接收者最终执行LReturnZero
,SUPPORT_TAGGED_POINTERS
是判断是否支持tagged pointer
,如果支持执行LNilOrTagged
;ldr p13, [x0]
是将isa
给p13
寄存器,然后执行GetClassFromIsa_p16 p13, 1, x0
;
GetClassFromIsa_p16
ExtractISA p16, \src, \auth_address
:
.macro ExtractISA
and $0, $1, #ISA_MASK // $1是src也就是p13=isa,与ISA_MASK进行与操作得到class赋值给$0
.endmacro
最终结果:p16
为class
CacheLookup
参数赋值:Mode
=NORMAL
Function
=_objc_msgSend
MissLabelDynamic
=__objc_msgSend_uncached
;mov x15, x16
:将x16
寄存器的值赋值给x15
,x16
即p16
为class
,指令结果:x15
=class
,然后调用LLookupStart
LLookupStart
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 // arm64系统
ldr p11, [x16, #CACHE] // x16的地址平移CACHE=(2 * __SIZEOF_POINTER__)大小 放入p11 = cache_t cache
#if CONFIG_USE_PREOPT_CACHES // #define CONFIG_USE_PREOPT_CACHES 1
#if __has_feature(ptrauth_calls) // A12,X系列,不看
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p11=cache_t与掩码与操作之后放入p10 = bucketsMask
tbnz p11, #0, LLookupPreopt\Function //p11 = cache_t cache是否存在,不为0,存在则执行LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7 // p11=0 执行此处
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask = begin
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
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
add p13, p10, p12, LSL #(1+PTRSHIFT) // PTRSHIFT = 3, p12 << 4,放入p10
// p13 = bucketsMask + ((_cmd & mask) << (1+PTRSHIFT)) = 当前要查找的bucket
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // p17,p9 同时存储 = *bucket-- = {imp,sel},p17=imp,p9=sel
cmp p9, p1 // 判断 (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp 缓存命中 mode = normal
// }
3: cbz p9, \MissLabelDynamic // p9=sel是否存在,if (sel == 0) goto MissLabelDynamic = __objc_msgSend_uncached
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
获取cache_t
ldr p11, [x16, #CACHE]
将x16
也就是class
地址平移CACHE
也就是16字节
得到cache_t cache
放入p11
获取bucket
and p10, p11, #0x0000fffffffffffe // p11=cache_t与掩码与操作之后放入p10 = bucketsMask
p11
为cache_t cache
,与掩码#0x0000fffffffffffe
进行与运算
之后得到bucket
放入p10
;
tbnz p11, #0, LLookupPreopt\Function //p11 = cache_t cache是否存在,不为0,存在则执行LLookupPreopt\Function
判断p11
的cache_t cache
是否存在,存在则执行LLookupPreopt
,如果获取不到,则执行
eor p12, p1, p1, LSR #7 // p11=0 执行此处
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask = begin
通过右移
和与运算
最终结果找到下标begin
,
LLookupPreopt
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls) // X系列处理不看
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 << 32)
cmp x12, w17, uxtw
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
CacheHit
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL // 走次判断分支
TailCallCachedImp x17, x10, x1, x16 // p17=imp,p10=buckets的地址,x1=sel,x16=isa
.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
,传入参数x17
=imp
,x10
=bucket
地址,x1
=sel
,x16
=isa
TailCallCachedImp
.macro TailCallCachedImp // 看此处 p17=imp,p10=buckets的地址,x1=sel,x16=isa
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3 // imp ^ isa 编码
br $0
.endmacro
$0
=imp
,$3
=isa
; 进行哈希编码结果赋值给$0
,然后跳转$0
,也就是imp
的方法
objc_msgSend(id self, SEL _cmd)
也就是通过sel
找imp
的过程