前言
前文iOS类的结构之cache_t分析中介绍了cache_t
的相关信息,了解了方法缓存的相关知识,本文将对方法缓存的读取
、存储
流程进行探索。
想要探索cache
的读写流程可以从我们之前探索过的cache_t::insert
函数开始,首先全局搜索cache_t::insert
函数,发现了苹果在objc-cache.mm
源码里面注释的cache
读写流程。
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
* cache_t::copyCacheNolock (caller must hold the lock)
* cache_t::eraseNolock (caller must hold the lock)
* cache_t::collectNolock (caller must hold the lock)
* cache_t::insert (acquires lock)
* cache_t::destroy (acquires lock)
复制代码
图解:
从这里我们可以看出cache
的读取是从objc_msgSend
开始的,接下来我们就从objc_msgSend
开始探索。
一、runtime
简介
探索objc_msgSend
之前先简单介绍下runtime
。
Objective-C 语言是一门动态语言。它把一些决策从编译阶段、链接阶段推迟到运行时阶段,实现该机制的基础就是
runtime
(又叫作运行时)。
- 静态语言:在编译阶段就已确定所有变量的数据类型,同时也确定要调用的函数,以及函数的实现。常见的静态语言,如:
C/C++、Java和C#
等。 - 动态语言:程序在运行时可以改变其结构。也就是说在运行时检查变量数据类型,同时在运行时才会根据函数名查找要调用的具体函数,如
Objective-C
。
1、runtime
是什么
runtime
是由C
、C++
和汇编
实现的一套API
。runtime
为Objective-C
语言的动态属性提供支持,充当一种用于Objective-C
语言的操作系统,使得该语言正常运转工作。runtime
为OC语言加入了面向对象、运行时的功能。- 平时编写的
Objective-C
代码,在程序运行过程中,最终会转换成runtime
的相关代码-runtime
是Objective-C
的幕后⼯作者。
2、runtime
的版本和平台
Runtime Versions and Platforms
在不同的平台上有不同的Objective-C runtime
版本。runtime开源代码
2.1、版本
Objective-C runtime
有两个版本:modern(现代版本)
和legacy(遗留版本)
。现代版本是在 Objective-C 2.0
中引入的,其中包括许多新功能。旧版运行时的编程接口在 Objective-C 1.0 运行时参考中有所描述;Objective-C Runtime Reference中描述了现代版本的运行时的编程接口。
最值得注意的新功能是现代运行时中的实例变量是non-fragile
(非脆弱的):
- 在旧版运行时中,如果更改类中实例变量的布局,则必须重新编译从其继承的类。
- 在现代运行时中,如果更改类中实例变量的布局,则不必重新编译从其继承的类。
另外,现代的运行时支持声明的属性的实例变量综合(请参见 The Objective-C Programming Language 和 Declared Properties)。
2.2、平台
iPhone
应用程序和OS X v10.5
及更高版本上的64
位程序使用modern
版本的runtime
。- 其他程序(
OS X
桌面上的32
位程序)使用legacy
版本的runtime
。
二、runtime
的消息机制
OC
中方法调用通过runtime
实现,runtime
进行方法调用最重要的底层本质就是消息机制,下面就来探索runtime
的消息机制。
objc_msgSend
函数定义:
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码
1、runtime
架构简单介绍
我们日常写代码在Objective-C Code
层,里面有很多的Framework
、Services
以及API
,Runtime System Library
为runtime
底层系统库,通过complier
(编译器)在中间层进行转换,对上层Objective-C Code
层提供支持。
2、消息发送的三种方式
2.1、OC层面方法调用
XJPerson *p1 = [XJPerson alloc];
[p1 thatGirlSayToMe:@"I love you"];
************************ 底层实现 ************************
// clang 指令编译成.cpp文件
XJPerson *p1 = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("XJPerson"), sel_registerName("alloc"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p1, sel_registerName("thatGirlSayToMe:"), (NSString *)&__NSConstantStringImpl__var_folders_14_tbs866611ll6tx7n__d__41m0000gn_T_main_267efe_mi_0);
复制代码
图解:
2.2、 Framework
&Services
层方法调用
XJPerson *p = [XJPerson performSelector:@selector(alloc)];
[p performSelector:@selector(thatGirlSayToMe:) withObject:@"I love you"];
************************ 底层实现 ************************
+ (id)performSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL))objc_msgSend)((id)self, sel);
}
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
复制代码
图解:
2.3、runtime API
调用
XJPerson *p2 = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("XJPerson"), sel_registerName("alloc"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p2, sel_registerName("thatGirlSayToMe:"), @"I love you");
复制代码
备注:
OC层使用
objc_msgSend
准备工作:1、在文件中导入相关
API
头文件,即#import <objc/message.h>。2、
Xcode
设置:target
->Build Settings
->Apple Clang Preprocessing
->Enable Strict Checking of objc_msgSend Calls
设置为NO
,关闭Clang
编译器对objc_msgSend
的检查。3、
Xcode 12
之后步骤2
可能无效,就需要对objc_msgSend
进行强转,然后使用。例:((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p2, sel_registerName("thatGirlSayToMe:"), @"I love you");
2.4、代码示例
//OC层方法调用
void methodCall(void) {
//给FFPerson分配内存
XJPerson *person = [XJPerson alloc];
//调用方法:OC
[person thatGirlSayToMe:@"I love you"];
//调用方法:Framework
[person performSelector:@selector(thatGirlSayToMe:) withObject:@"I love you"];
}
//objc_msgSend调用
void objc_msgSendCall(void) {
//给FFperson分配内存
XJPerson *person = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)(objc_getClass("XJPerson"), sel_registerName("alloc"));
//调用方法
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("thatGirlSayToMe:"), @"I love you");
}
//objc_msgSendSuper调用
void objc_msgSendSuperCall(void) {
//给FFperson分配内存
XJBoy *boy = ((XJBoy *(*)(id, SEL))(void *)objc_msgSend)(objc_getClass("XJBoy"), sel_registerName("alloc"));
//创建objc_super对象
struct objc_super boySuper;
boySuper.receiver = boy;
// * super_class is the first class to search */
// 这里的super_class可以是当前类XJBoys或者XJPerson,这里的super_class只是指定方法的第一查找类,如果当前类找不到,将会去父类中查找
// boysSuper.super_class = objc_getClass("XJBoys");
boySuper.super_class = objc_getClass("XJPerson");
//调用super方法
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSendSuper)((id)&boySuper, sel_registerName("thatGirlSayToMe:"), @"I love you");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
methodCall();
objc_msgSendCall();
objc_msgSendSuperCall();
}
return 0;
}
************************ 打印输出 ************************
2021-07-01 17:52:26.163465+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
2021-07-01 17:52:26.163880+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
2021-07-01 17:52:26.163944+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
2021-07-01 17:52:26.163980+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
Program ended with exit code: 0
复制代码
总结:
objc_msgSend
函数构成:objc_msgSend(消息接收者, 消息主体(sel + 参数))。objc_msgSendSuper
函数objc_super
结构体里的super_class
指定的是方法第一个查找的类。OC
方法调用的本质就是消息发送。
三、objc_msgSend
汇编分析(arm64
架构)
objc4-818.2
源码里全局搜索objc_msgSend
,然后按住command
,鼠标左键点击文件名旁边的小箭头,收起所有文件,选择objc-msg-arm64.s
文件点开,选择ENTRY _objc_msgSend
开始查看objc_msgSend
的汇编源码。
备注:支持__has_feature(ptrauth_calls)
指针身份验证功能的情况(A12及之后的仿生芯片),本文不做分析。
图解:
1、_objc_msgSend
汇编源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// p0,寄存器第一个值x0(id receiver)的地址
// 检查p0(id receiver)是否为0(nil),
// 如果没有接受者,则此次objc_msgSend无意义
cmp p0, #0 // nil check and tagged pointer check
// 如果支持tagged pointer
#if SUPPORT_TAGGED_POINTERS
// receiver为0,是tagged pointer类型,
// 执行b.le LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// receiver为0,不是tagged pointer类型
// 执行b.eq LReturnZero
b.eq LReturnZero
#endif
// receiver不为空,p13 = x0栈内存中的值,
// 对象和类的首地址就是isa,就相当于将isa给了p13
ldr p13, [x0] // p13 = isa
// 通过isa拿到类或者元类,存入p16(见后文分析)
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
// 通过receiver获取isa结束,
// 之所以这样做,是因为消息发送先查找是否有缓存,
// 而缓存在类或者元类里面
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 调用CacheLookup,见后文分析
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
复制代码
解析:
- 第一步:
cmp p0, #0
检查p0
(x0
的地址,receiver
,消息接收者)是否为空,如果为空,那么此处objc_msgSend
无意义。 - 第二步:判断是否是
tagged pointer
类型(SUPPORT_TAGGED_POINTERS
),如果是就执行LNilOrTagged
,然后里面执行b.eq LReturnZero
,否则就执行LReturnZero
,结束本次objc_msgSend
。 - 第三步:如果
p0
存在,就将x0
存入p13
,对象或类的首地址就是isa
,那么p13 = isa
。 - 第四步:进入宏(macro)
GetClassFromIsa_p16
,传入参数src = p13, needs_auth = 1, auth_address = x0
。 - 第七步:
LGetIsaDone:
获取isa
完成,进入宏CacheLookup
,传入参数传入参数Mode = NORMAL, Function = _objc_msgSend, MissLabelDynamic = __objc_msgSend_uncached
,MissLabelConstant
为默认参数。
2、GetClassFromIsa_p16
汇编源码
// 传入参数 p13即isa(src), 1(needs_auth), x0即isa(auth_address)
// .macro表示方法宏定义
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
// #if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
// # define SUPPORT_INDEXED_ISA 1
// #else
// # define SUPPORT_INDEXED_ISA 0
// #endif
// arm64不是Indexed isa,不走这里
#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__ // 64位类unix系统,arm64走这里
// needs_auth传入为1,不走这里
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else // needs_auth == 1,走这里
// 64-bit packed isa
// 通过isa拿到类或者元类,存入p16(见后文分析)
ExtractISA p16, \src, \auth_address
.endif
#else // 32位,不走这里
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
复制代码
解析:
- 第五步:不满足
SUPPORT_INDEXED_ISA
(Indexed isa
,32
位isa
),进入__LP64__
(Unix 和 Unix 类的系统 (Linux,Mac OS X)
)分支,needs_auth == 1
所以进入宏ExtractISA
,传入参数p16, \src = p13 = isa, \auth_address = isa
。
3、ExtractISA
汇编源码
#if __has_feature(ptrauth_calls) // A12或之后的仿生处理器支持指针身份验证
// JOP
// 传入参数p16($0),src即isa($1),auth_address即isa($2)
.macro ExtractISA
// 取出class(或metaclass),存入p16($0)
// p16 = isa & ISA_MASK,
and $0, $1, #ISA_MASK
// isa指针身份验证相关操作
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
autda $0, x10
#endif
.endmacro
// JOP
#else
// not JOP
// 传入参数p16($0),src即isa($1),auth_address即isa($2)
.macro ExtractISA
// 取出class(或metaclass),存入p16($0)
// p 16 = isa & ISA_MASK,
and $0, $1, #ISA_MASK
.endmacro
// not JOP
#endif
复制代码
解析:
- 第六步:
ExtractISA
将$1
(isa
)和ISA_MASK
做按位与
操作(isa & ISA_MASK
),得到类(class
)或元类(metaclass
),然后把结果存入$0
(p16
)。返回_objc_msgSend
。
4、CacheLookup
汇编源码
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// 传入参数NORMAL, _objc_msgSend, __objc_msgSend_uncached,
// MissLabelConstant未传,为默认参数
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
// 将x16赋值给x15
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS // M1 iMAC
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 // iPhone,arm64
// #define CACHE (2 * __SIZEOF_POINTER__)
// 将x16平移CACHE大小,然后赋值给p11
// class首地址偏移16字节就得到cache_t,
// cache_t首地址就是_bucketsAndMaybeMask
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES // iPhone为1,走这里
#if __has_feature(ptrauth_calls) // A12及之后走这里
// p11 与 0做比较,不为0,就走LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
// p10 = p11 & 0x0000ffffffffffff
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
// p10 = p11 & 0x0000fffffffffffe
and p10, p11, #0x0000fffffffffffe // p10 = buckets
// p11 与 0做比较,不为0,就走LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif
// cache_hash算法,算出bucket的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
// cache_hash算法,算出bucket的index
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
// p12 = _cmd & mask = index
// #define PTRSIZE 8
// #define PTRSHIFT 3 // 1<<PTRSHIFT == PTRSIZE
// 一个bucket_t占用16字节,即 << 4
// p12(index)左移4位就是index * 16
// 其实就是buckets首地址加上index个bucket_t内存大小,
// 找到index位置的bucket,赋值给p13
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// #define BUCKET_SIZE (2 * __SIZEOF_POINTER__)
// 取出当前bucket的imp给p17,sel给p9,然后--
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
// 比较p9和p1,不相等走3:,相等走2:
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
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS // M1版iMac
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 // iPhone,arm64
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
// 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
#if CONFIG_USE_PREOPT_CACHES // iPhone为1
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls) // A12及之后走这里
// p10 = p11 & 0x007ffffffffffffe
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
复制代码
解析:
- 第八步:
mov x15, x16
,隐藏初始isa
,x16
(isa
)赋值给x15
。 - 第九步:
arm64
架构进入CACHE_MASK_STORAGE_HIGH_16
分支,执行ldr p11, [x16, #CACHE]
,即x16
平移CACHE
大小(2
个指针,16
字节,即#define CACHE (2 * __SIZEOF_POINTER__)
),得到cache_t
指针地址,cache_t
指针首地址既是第一个成员变量_bucketsAndMaybeMask
的地址,既是mask|buckets
然后赋值给p11
(p11 = mask|buckets
)。 - 第十步:进入
CONFIG_USE_PREOPT_CACHES
分支,本文不分析__has_feature(ptrauth_calls)
(指针身份验证)的情况,所以进入else
分支,执行and p10, p11, #0x0000fffffffffffe
,即p11
和0x0000fffffffffffe
(preoptBucketsMask
掩码)做按位与操作,得到buckets
赋值给p10
(p10 = buckets
)。 - 第十一步:执行
tbnz p11, #0
,验证p11
是否为空,如果为空,则说明没有缓存,没有继续向下寻找的buckets
的必要了,跳转至LLookupPreopt
。 - 第十二步:执行
eor p12, p1, p1, LSR #7
,p1
就是objc_msgSend
的第二个参数sel
,即_cmd
,所以此指令即p12 = _cmd ^ (_cmd >> 7)
。 - 第十三步:执行
and p12, p12, p11, LSR #48
,p11
右移48
就是获得mask
,所以此指令即p12 = p12 & (_bucketsAndMaybeMask >> 48)
。第十二步和第十三步就是cache_hash
函数的算法(x12 = (_cmd ^ (_cmd >> 7)) & mask
),即通过sel
和mask
算出查找bucket
开始的index
赋值给p12
。 - 第十四步:执行
add p13, p10, p12, LSL #(1+PTRSHIFT)
,arm64
架构下#define PTRSHIFT 3
,所以此指令即p13 = p10 + p12 << (1 + 3)
,一个bucket_t
占用16
字节,p12
(index
)左移4
位就是index * 16
,其实就是buckets
首地址加上index
个bucket_t
内存大小,找到index
位置的bucket
赋值给p13
。 - 第十五步:执行
1: ldp p17, p9, [x13], #-BUCKET_SIZE
,取值x13
,imp
赋值给p17
,sel
赋值给p9
,然后x13--
拿到上一个位置的bucket
(表达式即{imp, sel} = *bucket--
)。 - 第十六步:
cmp p9, p1
,比较当前bucket
的sel
和objc_msgSend
的第二个参数_cmd
。 - 第十七步:不相等就执行
3: cbz p9, \MissLabelDynamic
,判断当前bucket
的sel
是否为空,如果为空,则执行MissLabelDynamic
(传入进来为__objc_msgSend_uncached
函数)。 - 第十八步:如果当前
bucket
的sel
不为空,则执行cmp p13, p10
,比较当前bucket
与buckets
首地址(p13
(当前bucket
)每次取值后都会--
),如果不相等就跳转到b.hs 1b
,再次执行第十五步(表达式即while (bucket >= buckets)
)。 - 第十九步:相等就执行
2: CacheHit \Mode
,传入参数Mode = NORMAL
。
5、CacheHit
汇编源码
// 传入参数NORMAL
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.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
复制代码
- 第二十步:传入
Mode
为NORMAL
,所以进入.if $0 == NORMAL
分支,执行TailCallCachedImp x17, x10, x1, x16
。
6、TailCallCachedImp
汇编源码
#if __has_feature(ptrauth_calls)
// JOP
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $1, $1, $2 // mix SEL into ptrauth modifier
eor $1, $1, $3 // mix isa into ptrauth modifier
brab $0, $1
.endmacro
// JOP
#else
// not JOP
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// cached imp ^= isa,imp解码
eor $0, $0, $3
// 跳转解码后的imp,即call imp
br $0
.endmacro
// not JOP
#endif
复制代码
- 第二十一步:执行
eor $0, $0, $3
,cached imp ^= isa
,imp
解码。 - 第二十二步:执行
br $0
,跳转解码后的imp
,即call imp
。
7、总结
objc_msgSend
就是通过sel
查找imp
的过程。