前言
在分析 cache_t原理 时,提及很多次的 objc_msgSend
函数,以及在真机环境下,cache缓存中多了个 maskZeroBits
字段,只知道 objc_msgSend
使用的,确不知道如何使用。
下面将探索 objc_msgSend
及 runtime
的原理。
Runtime
Runtime 简介
编译时 (building)
:顾名思义就是正在编译
的时候,编译器把源代码翻译成机器能识别的代码,实际上只是翻译成某个中间状态的语⾔。最重要的是进行词法分析
、语法分析
,并且检查代码是否符合规范
,这个过程就叫编译时类型检查
,也叫静态类型检查
。这个过程代码没有加载到内存中,⽽只是把代码当作⽂本来扫描下。
运行时 (running)
:代码跑起来,装载到内存
中。运行时检查错误和编译时检查错误 (或者静态类型检查) 不一样,不是简单的代码扫描,而是在内存中做操作和判断。
Runtime
提供了一套公共接口(函数和数据结构)的动态分享库,基本是用 C
,C++
和 汇编
写的,可见苹果为了动态系统的高效而作出的努力。平时的业务中主要是使用官方Api,解决我们框架性的需求。
Runtime 版本
Legacy
版本:早期版本,对应的编程接口:Objective-C 1.0
,32
位的Mac OS X
的平台
Modern
版本:现行版本,对应的编程接口:Objective-C 2.0
,iPhone
和Mac OS X 10.5
及以后的系统中的64
位系统。
Runtime 调用方式
Runtime 调用方式有三种:
Objective-C 代码方式
需要编写和编译 Objective-C
源码,runtime
将完成 OC
编译成 C
的过渡,实现底层 runtime API
的转变,比如:
编译成底层源码:
方法的调用其实是底层在调用
objc_msgSend
函数,下面会详细分析其原理。
Framework & Serivce 等系统API
调用 isKindOfClass
的方法分析其原理:
编译成底层源码:
NSObject
的方法底层也是在调用objc_msgSend
函数。sel_registerName
获取对应的SEL
;objc_getClass
获取实例对象。其实不只是
isKindOfClass:
方法,respondsToSelector:
是否接收特定消息;conformsToProtocol:
是否声明实现协议方法;methodForSelector:
方法地址的获取。
Runtime API
直接调用 runtime API
的方式
-
objc_msgSend
函数需要导入<objc/message.h>
- 需要修改
objc_msgSend
配置,Enable Strict Checking of objc_msgSend Calls
设置为NO
,默认值为YES
,意思是编译器会检查,objc_msgSend
只接收1个参数。此处目的不让编译器检查。
打印结果:
许多
Objective-C
代码可以通过runtime API
替换为C
实现。
方法的本质
objc_msgSend
方法的调用就是消息发送,调用底层 objc_msgSend
函数,例如:
消息发送:
objc_msgSend(消息接收者,消息的主体(sel + 参数))
objc_msgSendSuper
添加一个 ZLObject
的子类 ZLSubObject
,并重写doSomething
,如下:
编译成源码:
父类消息发送:
objc_msgSendSuper(父类接收者,消息主体(sel + 参数))
子类对象可以通过
objc_msgSendSuper
方式调用父类的方法。
objc_msgSendSuper
定义如下:
如果想要模拟调用 objc_msgSendSuper
函数,就必须了解其方法参数意义。最主要的还是 super
结构体,结构如下:
当前版本是 __OBJC2__
,最终简化为:
调用 objc_msgSendSuper
函数:
打印结果:
方法的本质:消息接收者通过
sel
查找imp
的过程。
objc_msgSend 快速查找
objc_msgSend 汇编源码:
// objc_msgSend 汇编入口 (主要是通过 isa 拿到接收者类,进而拿到cache)
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// cmp: 比较指令(其本质是做减法), 不影响寄存器的值.
// 此处判断接收者是否存在, p0是接收者, objc_msgSend的第一个参数: (id)receiver
cmp p0, #0
#if SUPPORT_TAGGED_POINTERS (__LP64__架构执行)
// le: 小于等于0 (p0不存在)
b.le LNilOrTagged
#else (!__LP64__架构执行)
// eq: 等于0, 接收者不存在。
b.eq LReturnZero
#endif
// ldr: 取值指令, x0是receiver,拿出isa,即从x0寄存器指向的地址取出isa,存入p13寄存器
// p13 = isa
ldr p13, [x0]
// 通过 p13(isa) & ISA_MASK,得到class信息。
// p16 = class
GetClassFromIsa_p16 p13, 1, x0
LGetIsaDone:
// 如果有isa,走到 CacheLookup 即缓存查找流程,也就是sel->imp 快速查找流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS (__LP64__架构执行)
LNilOrTagged:
// 清空寄存器,跳出 _objc_msgSend函数
b.eq LReturnZero
// 通过tagged pointer 获取class
GetTaggedClass
// 进入缓存查找流程
b LGetIsaDone
#endif
LReturnZero:
// x0寄存器存的是0 (接收者不存在)
// 清空数据
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
// 跳出 _objc_msgSend函数
ret
END_ENTRY _objc_msgSend
1. 核心逻辑:
先判断
receiver
是否<=0
。如果是
<= 0
,再判断架构,如果是__LP64__
则通过GetTaggedClass
获取class
,进入CacheLookup
查找流程;否则直接清空寄存器退出。如果
receiver
存在,通过GetClassFromIsa_p16
获取class
,进入CacheLookup
查找流程。
2. 伪代码:
void objc_msgSend(receiver, _cmd) {
if (receiver > 0) {
// 获取cls
cls = GetClassFromIsa_p16(isa,1,isa)
} else {
if (__LP64__ && receiver <= 0) {
// 获取cls
cls = GetTaggedClass();
} else {
return;
}
}
// 快速查找
CacheLookup(NORMAL, _objc_msgSend, __objc_msgSend_uncached)
}
GetTaggedClass 汇编源码:
.macro GetTaggedClass
// target pointer 的占位分布为: 1(标记位) + 8(extended) + 52(data) + 3(tag) = 64位
// x10 = isa & 111 : 获取后3位tag
and x10, x0, #0x7
// x11 = isa >> 55 : 获取前9位(1(标记位) + 8(extended))
asr x11, x0, #55
// 判断x10(标记位) 是否等于7
cmp x10, #7
// 三目运算
// 如果 (x10 == 7) ? x12 = x11 = 1(标记位) + 8(extended)
// 否则 x12 = x10 = 3(tag)
csel x12, x11, x10, eq
// x16 = _objc_debug_taggedpointer_classes[x12]
// 将_objc_debug_taggedpointer_classes的基址 读入x10寄存器
adrp x10, _objc_debug_taggedpointer_classes@PAGE
// x10 = x10 + _objc_debug_taggedpointer_classes的基址偏移量(内存偏移, 获取 classes 数组)
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
// x16 = x10 + x12 << 3 = class
ldr x16, [x10, x12, LSL #3]
.endmacro
1. 核心逻辑:
获取低
3
位tag
和 高9
位 (标志位+extended位) 。获取
taggedpointer
内存平移量通过平移量偏移,获取
class
。
2. 伪代码:
Class GetTaggedClass() {
x10 = isa & 111;
x11 = isa >> 55;
x12 = x10 == 7 ? x11 : x10;
x10 = x10 << (x12 & @PAGE);
x10 = x10 & @PAGEOFF;
// 通过偏移找到cls
cls = x16 = x10 + x12 << 3;
return cls;
}
GetClassFromIsa_p16 汇编源码:
.macro GetClassFromIsa_p16 src, needs_auth, auth_address
// armv7k or arm64_32
#if SUPPORT_INDEXED_ISA
// 将isa的值存入p16寄存器
mov p16, \src
// tbz: 标记位为0则跳转。
// 判断 p16 的第0位为0,如果成立则跳转1f。(ISA_INDEX_IS_NPI_BIT = 0)
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f
// 将_objc_indexed_classes的基址 读入x10寄存器
adrp x10, _objc_indexed_classes@PAGE
// x10 = x10 + _objc_indexed_classes的基址偏移量(内存偏移, 获取 classes 数组)
add x10, x10, _objc_indexed_classes@PAGEOFF
// ubfx: 无符号位域提取指令
// 从p16第2位开始提取,提取15位。目的提取的indexcls, 剩余位补0。
// (armv7k or arm64_32下,ISA_INDEX_SHIFT = 2 和 ISA_INDEX_BITS = 15)
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS
// p16 = x10[indexcls & PTRSHIFT] (PTRSHIFT在 __LP64__ 下是3, 其余是2)
// p16 = class
ldr p16, [x10, p16, UXTP #PTRSHIFT]
1: // tbz 成立跳转
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp 已经授权了类
// 无需授权将isa的值存入p16寄存器
mov p16, \src
.else
// p16 = isa & ISA_MASK = class
ExtractISA p16, \src, \auth_address
.endif
#else
// 将isa的值存入p16寄存器
mov p16, \src
#endif
.endmacro
1. 核心逻辑如下:
GetClassFromIsa_p16
的目的就是获取class
给到p16
。
armv7k or arm64_32
:判断标记位是否为
0
(是否开启了nonpointer
),如果开启,根据偏移量找到indexcls
,再通过与
操作,得到cls
并赋值给p16
;否则,直接取cls
赋值给p16
。
__LP64__
:判断是否需要授权(
needs_auth
),如果需要,调用ExtractISA
将isa & ISA_MASK
结果赋值给p16
;否则将isa
的值存入p16
寄存器。(_objc_msgSend
的needs_auth
为1 )
2. 伪代码:
Class GetClassFromIsa_p16 (isa, needs_auth, auth_address) {
Class cls = NULL;
if (arm64_32 || armv7k) {
if (nonpointer) {
class[] = x10 << (12 & @PAGE);
indexcls = ubfx isa[2-15]
cls = class[indexcls]
} else {
cls = isa;
}
} else if (__LP64__) {
if (needs_auth) {
cls = ExtractISA();
} else {
cls = isa;
}
} else {
cls = isa;
}
return cls;
}
ExtractISA 汇编源码:
// ISA signing authentication modes. Set ISA_SIGNING_AUTH_MODE to one
// of these to choose how ISAs are authenticated.
#define ISA_SIGNING_STRIP 1 // Strip the signature whenever reading an ISA.
#define ISA_SIGNING_AUTH 2 // Authenticate the signature on all ISAs.
// ISA signing modes. Set ISA_SIGNING_SIGN_MODE to one of these to
// choose how ISAs are signed.
#define ISA_SIGNING_SIGN_NONE 1 // Sign no ISAs.
#define ISA_SIGNING_SIGN_ONLY_SWIFT 2 // Only sign ISAs of Swift objects.
#define ISA_SIGNING_SIGN_ALL 3 // Sign all ISAs.
#if __has_feature(ptrauth_objc_isa_strips) || __has_feature(ptrauth_objc_isa_signs) || __has_feature(ptrauth_objc_isa_authenticates)
# if __has_feature(ptrauth_objc_isa_authenticates)
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
# else
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
# endif
# if __has_feature(ptrauth_objc_isa_signs)
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
# else
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
# endif
#else
# if __has_feature(ptrauth_objc_isa)
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
# else
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
# endif
#endif
// A12以后机型
#if __has_feature(ptrauth_calls)
.macro ExtractISA
// $0 = $1 & ISA_MASK
and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
// 每次读isa时都授权 (当前架构会执行)
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// 授权所有的isa (不会执行)
mov x10, $2
// movk: 位移指令,movk移动高x位的到寄存器,其余空位保持原样;
// x10 = #0x6AE1 << 48 (ISA_SIGNING_DISCRIMINATOR = 0x6AE1)
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
// autda: 授权向后读取x10给p16
autda $0, x10
#endif
.endmacro
#else
.macro ExtractISA
// $0 = $1 & ISA_MASK
and $0, $1, #ISA_MASK
.endmacro
#endif
核心逻辑如下:
ExtractISA
的目的通过isa & ISA_MASK
获取class
。
A12
机型做了认证逻辑的判断,其中xpacd
指令不是很熟悉。
CacheLookup 汇编源码(重点):
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
// p16存储的是cls,x15 = [#x16] = cls (将cls复制一份存到x15)
mov x15, x16
// 开始查找
LLookupStart\Function:
// p1 = SEL, p16 = isa
// arm64 64位 OSX | SIMULATOR
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// 其中 (CACHE = 2 * __SIZEOF_POINTER__ = 2 * 8 = 16位,__SIZEOF_POINTER__是指针,占8位)
// p10 = isa & #CACHE = _bucketsAndMaybeMask(cache_t首地址)
ldr p10, [x16, #CACHE]
// lsr: 逻辑右移
// p11 = p10(_bucketsAndMaybeMask) >> 48 = mask。(获取mask)
lsr p11, p10, #48
// p10 = p10(_bucketsAndMaybeMask) & 0xffffffffffff(12位) = buckets。(只保留48位,获取buckets)
and p10, p10, #0xffffffffffff
// w1: 第二个参数SEL,w11 = p11 = mask ( 执行cache_hash()函数,获取插入begin )
// p12 = SEL & mask = 插入begin
and w12, w1, w11
// arm64 64位 真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// p11 = isa & #CACHE = _bucketsAndMaybeMask(cache_t首地址)
ldr p11, [x16, #CACHE]
// arm64 IOS && !模拟器 && !mac应用
#if CONFIG_USE_PREOPT_CACHES
// A12真机
#if __has_feature(ptrauth_calls)
// tbnz: 标记位不为0则跳转(与tbz相反)。
// p11第0位不为0则跳转 LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
// p10 = p11(_bucketsAndMaybeMask) & 0x0000ffffffffffff = buckets
and p10, p11, #0x0000ffffffffffff
// 其他真机
#else
// p10 = p11(_bucketsAndMaybeMask) & 0x0000fffffffffffe = buckets
and p10, p11, #0x0000fffffffffffe
// p11第0位不为0则跳转 LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif // __has_feature(ptrauth_calls)
// eor: 异或指令
// p12 = sel ^ (sel >> 7) ( 执行cache_hash()函数,真机执行 >>7操作 )
eor p12, p1, p1, LSR #7
// p11(_bucketsAndMaybeMask) >> 48 = mask
// p12 = (sel ^ (sel >> 7)) & mask
and p12, p12, p11, LSR #48
#else
// p10 = p11(_bucketsAndMaybeMask) & 0x0000ffffffffffff = buckets
and p10, p11, #0x0000ffffffffffff
// p11(_bucketsAndMaybeMask) >> 48 = mask
// p12 = sel & mask
and p12, p1, p11, LSR #48
#endif // CONFIG_USE_PREOPT_CACHES
// 非64位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// p11 = isa & #CACHE = _bucketsAndMaybeMask(cache_t首地址)
ldr p11, [x16, #CACHE]
// p10 = p11 & ~0xf = buckets(前28位), 相当于后4位置为0,取前28位
and p10, p11, #~0xf
// p11 = p11 & 0xf = maskShift(后4位), 相当于前28位置为0,取后4位
and p11, p11, #0xf
// p12 = 0xffff
mov p12, #0xffff
// p11 = p12 >> p11 = 0xffff >> maskShift = mask
lsr p11, p12, p11
// p12 = sel & mask
and p12, p1, p11
#else
#error Unsupported cache mask storage for ARM64.
#endif
// p10 = buckets; p12 = sel & mask; 64位下PTRSHIFT = 3, 其他PTRSHIFT = 2
// p13 = buckets & ((sel & mask) << (1 + PTRSHIFT)) = bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// {p17(imp), p9(sel)} = *p13[BUCKET_SIZE--] (内存平移)
1: ldp p17, p9, [x13], #-BUCKET_SIZE
// 比较得到的p9 == p1 ?
cmp p9, p1
// 如果不相等(没找到), 跳转第3步
b.ne 3f
// 如果命中,执行 CacheHit
2: CacheHit \Mode
// cbz: 为0跳转。
// 判断得到的sel == 0,如果成立,跳转MissLabelDynamic
3: cbz p9, \MissLabelDynamic
// 判断 bucket >= buckets
cmp p13, p10
// 如果大于等于(bucket >= buckets 成立), 跳转第1步
b.hs 1b
// 如果都没有命中 bucket, 查找 p13 = mask对应的元素
// p10 = buckets; p11 = mask;
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// UXTW: 扩展指令, 将w11左移1+PTRSHIFT后扩展为64位
// p13 = buckets & (mask << 1+PTRSHIFT) = bucket
add p13, p10, w11, UXTW #(1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// 此处忽略了maskZeroBits的位数
// p13 = buckets & (mask >> (48 - (1+PTRSHIFT)) = bucket
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// p13 = buckets & (mask << 1+PTRSHIFT) = bucket
add p13, p10, p11, LSL #(1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// p12 = sel & mask
// p12 = buckets & (sel & mask << (1+PTRSHIFT)) = first_probed bucket
add p12, p10, p12, LSL #(1+PTRSHIFT)
// {p17(imp), p9(sel)} = *p13[BUCKET_SIZE--] (内存平移)
4: ldp p17, p9, [x13], #-BUCKET_SIZE
// 判断 p9(sel) = _cmd
cmp p9, p1
// 如果相等,跳转第2步 CacheHit
b.eq 2b
// 如果不相等,判断p9(sel) 是否等于0
cmp p9, #0
// 并且 p13(bucket) > p12(first_probed bucket)
ccmp p13, p12, #0, ne
// hi: 无符号大于,再次执行第4步
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
1. 核心逻辑如下:
总体流程:
CacheLookup
→buckets
→bucket
→sel & imp
判断架构,并找到
buckets
,以及buckets
中sel
对应的index
。p10 = buckets,p11 = mask,p12 = index
。
arm64 64位
的情况下如果_bucketsAndMaybeMask
第0
位不为0
则执行LLookupPreopt()
。两个循环查找过程:
首先
buckets & index << 4
找到对应的bucket
。do-while
循环判断buckets[index]
,如果能找到sel
,说明命中,直接执行CacheHit
;如果存在sel为空,则说明是没有缓存的,就直接__objc_msgSend_uncached()
。平移获得
p13 = buckets[mask]
对应的元素,也就是倒数第二个。p13 = buckets & mask << 4
找到mask
对应的bucket
,do-while
循环判断buckets[mask]
的sel
,直到index
。如果命中,执行CacheHit
;如果存在sel为空,则说明是没有缓存的,就结束循环。最终仍然没有找到则执行
__objc_msgSend_uncached()
真机环境下,
maskZeroBits
始终是4位0 (0x0000ffffffffffff | 0x0000fffffffffffe),获取bucket
时,_bucketsAndMaybeMask >> 44
后就不用再<<4
找到对应bucket
的地址。
f
b
分别代表front
与back
,跳转顺序往前(f
)往后(b
)的意思。
2. 伪代码:
// CacheLookup(NORMAL, _objc_msgSend, __objc_msgSend_uncached, NULL)
void CacheLookup(Mode, Function, MissLabelDynamic, MissLabelConstant) {
x15 = x16;
if (__LP64__ && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) {
_bucketsAndMaybeMask = isa & 16;
mask = _bucketsAndMaybeMask >> 48;
buckets = _bucketsAndMaybeMask & 0xffffffffffff;
index = sel & mask;
} else if (__LP64__) {
// _bucketsAndMaybeMask
_bucketsAndMaybeMask = isa & 16;
if (IOS && !SIMULATOR && !MACCATALYST) {
// A12
if (__has_feature(ptrauth_calls)) {
// 判断标记位
if (_bucketsAndMaybeMask[第0位] != 0) {
LLookupPreopt();
} else {
buckets = _bucketsAndMaybeMask & 0x0000ffffffffffff
}
} else {
buckets = _bucketsAndMaybeMask & 0x0000fffffffffffe;
// 判断标记位
if (_bucketsAndMaybeMask[第0位] != 0) {
LLookupPreopt();
}
}
// 计算index
mask = _bucketsAndMaybeMask >> 48;
index = sel ^ (sel >> 7) & mask;
} else {
buckets = _bucketsAndMaybeMask & 0x0000ffffffffffff;
mask = _bucketsAndMaybeMask >> 48;
// 计算index
index = sel & mask;
}
} else if (!__LP64__) {
_bucketsAndMaybeMask = isa & 16;
buckets = _bucketsAndMaybeMask & ~0xf;
maskShift = _bucketsAndMaybeMask & 0xf;
mask = 0xffff >> maskShift;
index = sel & mask;
}
// 获取目标
bucket = buckets & (index << 4);
i = index;
// 循环
do {
bucket = buckets[i--];
if (bucket.sel() == _cmd) {
// 命中
CacheHit(NORMAL);
return;
}
if (bucket.sel() == 0) {
// __objc_msgSend_uncached()
MissLabelDynamic();
return;
}
} while(bucket >= buckets);
// 如果没找到缓存
if (__LP64__ && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) {
bucket = buckets & (mask << 4);
} else if (__LP64__) {
bucket = buckets & (mask >> 44);
} else if (!__LP64__) {
bucket = buckets & (mask << 3);
}
index = sel & mask;
first_probed = buckets & (index << 4);
i = index;
// 循环
do {
bucket = buckets[i--];
if (bucket.sel() == _cmd) {
// 命中
CacheHit(NORMAL);
return;
}
} while(bucket.sel() != 0 && bucket > first_probed);
// 仍然没有找到缓存,缓存彻底不存在。
__objc_msgSend_uncached()
}
LLookupPreopt 汇编源码:
LLookupPreopt\Function:
// A12机型
#if __has_feature(ptrauth_calls)
// p10 = _bucketsAndMaybeMask & 0x007ffffffffffffe = buckets
and p10, p11, #0x007ffffffffffffe
// 授权向前读取 x10 = cls
autdb x10, x16
#endif
// p9 = _cmd
adrp x9, _MagicSelRef@PAGE
// _cmd = (_cmd >> 12 + @PAGE) << 12 + PAGEOFF
ldr p9, [x9, _MagicSelRef@PAGEOFF]
// p12 = _cmd - p9 = index
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
// p17 = _bucketsAndMaybeMask >> 55 = hash_shift
lsr x17, x11, #55
// p9 = index >> hash_shift
lsr w9, w12, w17
// p17 = _bucketsAndMaybeMask >> 60 = mask_bits = 4
lsr x17, x11, #60
// p11 = #0x7fff
mov x11, #0x7fff
// p11 = #0x7fff >> mask_bits = mask
lsr x11, x11, x17
// p9 = p9 & mask
and x9, x9, x11
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
// p17 = _bucketsAndMaybeMask >> 48 = hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
// p9 = index >> hash_shift
lsr w9, w12, w17
// p11(_bucketsAndMaybeMask) >> 53 = mask
// p9 = p9 & mask
and x9, x9, x11, LSR #53
#endif
// x17 == sel_offs | (imp_offs << 32)
ldr x17, [x10, x9, LSL #3]
// 比较index 和 p17
cmp x12, w17, uxtw
.if \Mode == GETIMP
// 如果不等于, cache miss
b.ne \MissLabelConstant
// p0 = isa - p17 >> 32 = imp
sub x0, x16, x17, LSR #32
// 寄存imp到p0,并返回
SignAsImp x0
ret
.else
// 如果不等于, 跳转5
b.ne 5f
// p17 = isa - p17 >> 32 = imp
sub x17, x16, x17, LSR #32
.if \Mode == NORMAL
// 跳转p17寄存器
br x17
.elseif \Mode == LOOKUP
// p16 = p16 | 0x11
orr x16, x16, #3
// 寄存imp到p17,并返回
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
// p9 = *buckets--
5: ldursw x9, [x10, #-8]
// x16 = x16 & x9
add x16, x16, x9
// 跳转 LLookupStart
b LLookupStart\Function
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
1. 核心逻辑如下:
LLookupPreopt
目的就是获取imp
并返回。如果找不到,再次进行缓存查找。
2. 伪代码:
void LLookupPreopt() {
if (__has_feature(ptrauth_calls)) {
buckets = _bucketsAndMaybeMask & 0x007ffffffffffffe;
}
// 计算差值index
index = (_cmd - first_shared_cache_sel);
if (__has_feature(ptrauth_calls)) {
hash_shift = _bucketsAndMaybeMask >> 55;
mask_bits = _bucketsAndMaybeMask >> 60;
mask = 0x7fff >> mask_bits;
_cmd = (index >> hash_shift) && mask;
} else {
hash_shift = _bucketsAndMaybeMask >> 48;
mask = _bucketsAndMaybeMask >> 53;
_cmd = (index >> hash_shift) && mask;
}
// 判断模式
if (Mode == GETIMP) {
if (index == sel_offs) {
imp = isa - p17 >> 32;
return imp;
} else {
return 0;
}
} else {
if (index == sel_offs) {
imp = isa - p17 >> 32;
if (Mode == NORMAL) {
return imp;
} else if (Mode == LOOKUP) {
isa = isa | 0x11;
return imp;
}
} else {
isa = isa & *buckets--;
LLookupStart();
}
}
}
CacheHit 汇编源码:
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// A12以后机型
#if __has_feature(ptrauth_calls)
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $1 = buckets ^ SEL;
eor $1, $1, $2 // mix SEL into ptrauth modifier
// $1 = (buckets ^ SEL) ^ isa;
eor $1, $1, $3 // mix isa into ptrauth modifier
// $0(p17) = (buckets ^ SEL) ^ isa;
brab $0, $1
.endmacro
.macro AuthAndResignAsIMP
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $1 = buckets ^ SEL;
eor $1, $1, $2 // mix SEL into ptrauth modifier
// $1 = (buckets ^ SEL) ^ isa;
eor $1, $1, $3 // mix isa into ptrauth modifier
// 授权向前存储到$0中 $0 = $1
autib $0, $1
// xar: 零寄存器
// 读取$0到零寄存器,如果读取成功,说明是验证失败的
ldr xzr, [$0]
// paciza: 对地址进行 paciza 签名
paciza $0
.endmacro
#else
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $0 = imp ^ isa
eor $0, $0, $3
br $0
.endmacro
.macro AuthAndResignAsIMP
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $0 = imp ^ isa
eor $0, $0, $3
.endmacro
#endif
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
// 验证并记录imp
// TailCallCachedImp(imp, buckets, sel, isa)
TailCallCachedImp x17, x10, x1, x16
.elseif $0 == GETIMP
mov p0, p17
// 比较p0是不是等于0, 如果是0, 跳转到9
cbz p0, 9f
// 如果不为0,验证签名为imp
AuthAndResignAsIMP x0, x10, x1, x16
// return IMP
9: ret
.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.
// 授权签名imp
AuthAndResignAsIMP x17, x10, x1, x16
// 比较p16和p15, (p15是开始调用_objc_msgSend时,p15 = p16赋值的)
cmp x16, x15
// 当p15 != p16,时p16++;
cinc x16, x16, ne
// return imp
ret
.else
.abort oops
.endif
.endmacro
1. 核心逻辑如下:
_objc_msgSend
会执行NORMAL
逻辑,会直接执行TailCallCachedImp
,其内部执行(buckets ^ SEL) ^ isa
,对imp
进行了解码,并返回imp
。
GETIMP
和LOOKUP
都会授权注册imp
,并返回imp
。不同的是GETIMP
是判断之后再授权imp
;LOOKUP
先授权imp
再判断。
2. 伪代码:
IMP CacheHit(mode) {
if (mode == NORMAL) {
return TailCallCachedImp(imp, buckets, sel, isa);
} else if (mode == GETIMP) {
if (imp == 0) {
return imp;
} else {
return AuthAndResignAsIMP(imp, buckets, sel, isa);
}
} else if (mode == LOOKUP) {
imp = AuthAndResignAsIMP(imp, buckets, sel, isa);
if (curCls != firstCls) {
curCls++;
}
return imp;
} else {
return 0;
}
}
IMP TailCallCachedImp(imp, buckets, sel, isa) {
if (__has_feature(ptrauth_calls)) {
imp = buckets ^ sel;
imp = imp ^ isa;
return imp;
} else {
imp = imp ^ isa;
return imp;
}
}
IMP AuthAndResignAsIMP(imp, buckets, sel, isa) {
if (__has_feature(ptrauth_calls)) {
imp = buckets ^ sel;
imp = imp ^ isa;
if (imp == 0) {
paciza imp;
}
return imp;
} else {
imp = imp ^ isa;
return imp;
}
}
__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 (涉及到慢速查找逻辑)
MethodTableLookup
// 返回 imp
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
主要执行
MethodTableLookup
,其中涉及到了慢速查找。下篇进行分析。