再探cache_t的底层分析

cache_t的底层分析续集

cache_t的底层分析已经知道Cache_t的底层结构, 了解在insetbuckets的创建机制。很好奇的是何时调用的insert的呢?这也是今天的重点,探寻cache何时insert

通过设置断点,查看调用栈的方式,寻找inset是何时被调用。

image.png

image.png
通过查看调用栈,会发现inset之前调用了log_and_fill_cache、lookUpImpOrForward、_objc_msgSend_uncache,可以看出,对象调用方法时,内部调用执行函数流程为:_objc_msgSend_uncache -> lookUpImpOrForward -> log_and_fill_cache -> inset

全局查找_objc_msgSend_uncache

image.png
发现_objc_msgSend_uncache调用是在一个汇编文件中。(懵。。。, 先放一边,后面就迎刃而解)

我们现在清楚当调用[p saySomething]时,内部调用了_objc_msgSend_uncache,那我们使用clang将OC文件重写,看看底层C++的实现。

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 






        LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")) ;



        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySomething"));


    }
    return 0;
}
复制代码

发现[p saySomething]在底层c++实现是调用了objc_msgSend这个函数。分析一下
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySomething"))
其中(void (*)(id, SEL))代表函数指针类型,返回值为void,需要2个参数,类型分别为id,SEL。

(void *)objc_msgSend其中objc_msgSend就是函数指针名,将objc_msgSend强转为(void )指针,然后再其强转为(void ()(id, SEL))函数指针。

((id)p, sel_registerName(“saySomething”))就是对象函数的参数列表。也就是id类型的p,以及通过sel_registerName得到的SEL。

由此可见,对象调用方法的底层是通过调用函数objc_msgSend,也就是消息发送。尝试直接使用底层RuntimeAPI给对象发消息。

image.png调用结果一致。
言归正传,开篇通过查看cacheinsert的调用堆栈,发现调用方法时,调用的_objc_msgSend_uncache是在汇编中被调用, 于是通过对OC文件的重写, 发现底层C++实现时,对象调用方法其实是调用了objc_msgSend函数。

下一步,探寻objc_msgSend

全局查找

image.png
发现objc_msgSend也在汇编文件中,由此可见objc_msgSend是结合了汇编实现(可能是为了运行效率吧)。

分析objc_msgSend汇编代码实现

ENTRY _objc_msgSend:ENTRY代表程序的入口,也就是_objc_msgSend的入口

UNWIND _objc_msgSend, NoFrame:UNWIND 就是一个栈帧寄存器,记录当前 栈顶的指针。

cmp p0, #0: cap 比较指令, 将po 也就是objc_msgSend的第一个参数,消息接受者的指针与0比较,

#if SUPPORT_TAGGED_POINTERS 当支持TAGGED_POINTERS类型

b.le LNilOrTagged 如果比较的数小于0 就执行LNilOrTagged

#else

b.eq LReturnZero 如果比较数等于0,就执行LReturnZero

#endif

ldr p13, [x0]// p13 = isa, ldr 代表加载数据, 也就是将当前x0寄存器中的值读入到p13。 x0此时就是消息接受者的指针, 也就是isa(对象的首地址存的就就是isa的地址)。

GetClassFromIsa_p16 p13, 1, x0 // p16 = class 掉了的GetClassFromIsa_p16 传入了3个参数

GetClassFromIsa_p16

.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
复制代码

SUPPORT_INDEXED_ISA由于当前不是arm架构,SUPPORT_INDEXED_ISA == 0, 所以我们分析 LP64,由于needs_auth 传进来的是1, 所以执行else。
ExtractISA p16, \src, \auth_address

.macro ExtractISA
	and    $0, $1, #ISA_MASK
.endmacro
复制代码

ExtractISA中, $1就是isa,然后和ISA_MASK进行与操作,也就得到了当前的class地址, 然后将class地址写入到$0,也就是p16。

LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
复制代码

得到class 之后,就执行 CacheLookup,传入了三个参数,其中包括我们在断点查看insert调用时,在堆栈中发现的__objc_msgSend_uncached方法。说明当对象发送消息时,也就是调用 _objc_msgSend中,内部的CacheLookup中,会有可能执行
__objc_msgSend_uncached,然后进行class的缓存添加。

总结

在分析cache_t后, 我们了解了cache_t的结构,以及insert中扩展buckets的机制,通过对insert前调用堆栈的查看, 发现对象调用方法在底层是通过执行 _objc_msgSend。进一步对 _objc_msgSend的分析,我们找到了 _objc_msgSend汇编实现,分析了 _objc_msgSend汇编实现逻辑,找到了在 CacheLookup中 有对 __objc_msgSend_uncached的调用。(未完待续,分析CacheLookup

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享