iOS底层原理08: objc_msgSend分析下

359 阅读3分钟

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

这一篇文章我们来完整梳理一遍objc_msgSend的流程

我们以arm64架构为例来分析流程:

objc_msgSend流程

ENTRY _objc_msgSend

cmp p0, #0寄存器p0是objc_msgSend的第一个参数id self,也就是isa,和0比较判断有没有接收者,没有接收者最终执行LReturnZeroSUPPORT_TAGGED_POINTERS是判断是否支持tagged pointer,如果支持执行LNilOrTaggedldr p13, [x0]是将isap13寄存器,然后执行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

最终结果:p16class

CacheLookup

参数赋值:Mode=NORMAL Function=_objc_msgSend MissLabelDynamic=__objc_msgSend_uncachedmov x15, x16:将x16寄存器的值赋值给x15x16p16class,指令结果: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

p11cache_t cache,与掩码#0x0000fffffffffffe进行与运算之后得到bucket放入p10;

tbnz	p11, #0, LLookupPreopt\Function //p11 = cache_t cache是否存在,不为0,存在则执行LLookupPreopt\Function

判断p11cache_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)也就是通过selimp的过程

流程图