从objc_msgSend入口分析方法查找(上)

358 阅读3分钟

从上一节中我们可以看到cache的insert流程是从objc_msgSend中发出的,我们来分析objc_msgSend的过程。前面分析中有一些没有没在流程中的代码,也可以直接看总结部分。

汇编分析objc_msgSend

demo代码

        Person *p  = [Person alloc];
        [p sayHappy];

[p sayHappy];会调用objc_msgSend这个方法,这是一个消息查找的过程,消息查找的本质是通过sel来找到imp这样一个流程。 搜索818源码,objc_msgSend发现有很多,我们选取objc-msg-arm64.s的源码分析。

        //入口
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
        //p0是p,消息的接受者
        //对比p0是否为空
	cmp	p0, #0			// nil check and tagged pointer check
        //如果是tagged_pointers, 走LNilOrTagged
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else   //否则 LReturnZero
	b.eq	LReturnZero
#endif
       //x0是对象的首地址,即isa
	ldr	p13, [x0]// p13 = isa
        //查找class
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class 
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

我们来看一下GetClassFromIsa_p16的过程

// src=isa, needs_auth=1, auth_address=isa
//走ExtractISA
.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

// src=isa auth_address=isa 
//isa &  #ISA_MASK = $0 查找class的过程
.macro ExtractISA
	and    $0, $1, #ISA_MASK
.endmacro

我们看到GetClassFromIsa_p16是一个查找class的过程,为什么找class呢,因为从前面的分析中我们知道class中才存在cache_t这些信息。

截屏2021-06-28 下午2.30.19.png

找到了class开始走CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached流程

//CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
//mode = NORMAL
//Function = _objc_msgSendSuper2
//MissLabelDynamic = __objc_msgSend_uncached
.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 //走这里
//-(cache) & 0x0000ffffffffffff ,mask高16位抹零,得到buckets 存入p10寄存器-- 即去掉mask,留下buckets
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\Function
#endif
      //走这里,找index
	eor	p12, p1, p1, LSR #7
	and	p12, 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
#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)
						// 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

#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 我们选的是arm64走ldr p11, [x16, #CACHE]

#define CACHE            (2 * __SIZEOF_POINTER__)
#define CLASS            __SIZEOF_POINTER__

ldr p11, [x16, #CACHE] 就为 ldr p11, [x16, 16] 即x16平移16存到p11里面。这是一个找cache_t的类型,p11存的是cache_t

#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

#define CONFIG_USE_PREOPT_CACHES 1则走

and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
tbnz	p11, #0, LLookupPreopt\Function

❓为什么是p10 = buckets (cache) & 0x0000ffffffffffff ,mask高16位抹零,得到buckets 存入p10寄存器-- 即去掉mask,留下buckets p11的0号位置不为0则走LLookupPreopt


//--- PTRSHIFT=3 LSL #(1+PTRSHIFT) 实际含义是<<4 即*16
//(_cmd & mask)是取index,取出index index << 4即index *16得到bucket
//--- p13是下标 p10是buckets数组首地址,下标 * 1<<4(即16) 得到实际内存的偏移量,通过buckets的首地址偏移,获取bucket存入p13寄存器

add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

//从x13(即p13)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel						// do {
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	//比较sel和p1
        cmp	p9, p1				//     if (sel != _cmd) {
	//如果不相等,即么有找到,跳转3f
        b.ne	3f				//         scan more
	
        //缓存命中					//     } else {
2:	CacheHit \Mode				// hit:    call or return imp
						//     }
3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) gotoMiss;                                           
	cmp	p13, p10                        // } while (bucket >= buckets)不满足则跳出while循环走到下面
        //跳转到第一步,继续对比                                      
	b.hs	1b
        
        #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
        #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        //走这里,p13指向了最后一个bucket
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
	// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
         #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
         #else
         #error Unsupported cache mask storage for ARM64.
        #endif
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

//开启了一轮从最后面到第一个	first_probed的遍历循环					// 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 
        
        LLookupEnd\Function:
        LLookupRecover\Function:
	b	\MissLabelDynamic 
     

如果没有找到,则走MissLabelDynamic ,即调用CacheLoopup的时候传递的__objc_msgSend_uncached 我们找到__objc_msgSend_uncached

       STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p15 is the class to search
	
	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached

看一下TailCallFunctionPointer

.macro TailCallFunctionPointer
	// $0 = function pointer value
	br	$0
.endmacro

只是一个返回,所以重点应该在MethodTableLookup

.macro MethodTableLookup
	
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

	RESTORE_REGS MSGSEND

.endmacro

我们看到调用了_lookUpImpOrForward,然后全局没有搜到,说明接下来的过程已经不在汇编里面,搜索lookUpImpOrForward,则看到在objc-runtime-new.mm中

总结

上面的整体来说比较乱,我们只把有用的摘出来进行一个总结:

ENTRY _objc_msgSend
//如果接收者不为空
//获取到接收者对应的class
ldr	p13, [x0]		// p13 = isa
GetClassFromIsa_p16 p13, 1, x0	// p16 = class
//开始进行缓存查找
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
//平移16找到cache
ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
//根据buckets存在cache中的后48位,取出buckets 补充中有解释(补充1)
and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
//取index 补充中有源码解释(补充2)
eor	p12, p1, p1, LSR #7
and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
//获取到对应的bucket
add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
 //下面进行循环遍历查找,是从index一直--往前查找, 补充中有解释(补充3)                                           
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
  //如果没有找到,则将p13移到最后一个bucket的位置      
  add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
                                                

add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket
//从最后一个向前--遍历到firstprobed
 						// 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
        
LLookupEnd\Function:
//没有找到走__objc_msgSend_uncached(MethodTableLookup的实参)
LLookupRecover\Function:
	b	\MissLabelDynamic  
 //__objc_msgSend_uncached流程  
 //走MethodTableLookup
 MethodTableLookup
 //进入lookUpImpOrForward方法
 bl	_lookUpImpOrForward     

补充

1.找bucket
//根据buckets存在cache中的后48位,取出buckets
and	p10, p11, #0x0000fffffffffffe	// p10 = buckets

_bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits,所以& #0x0000fffffffffffe可以得到buckets

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
            "Bucket field doesn't have enough bits for arbitrary pointers.");
2.找index
//取index
eor	p12, p1, p1, LSR #7
and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask

对应objc源码中 mask_t begin = cache_hash(sel, m);找index的算法

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);
}
3.遍历过程
   bucket_t *b = buckets();
    mask_t m = capacity - 1; // 4-1=3
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
 do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

我们看下cache_next的源码

#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

eg:加入mask = 7,i第一次等于3,第一次调用cache_next之后,i = 2,依次--,当i=0时,i就会赋值为7,在--依次向前查找,直到i==3,结束