前言
在分析 cache_t原理 時,提及很多次的 objc_msgSend
函數(shù),以及在真機環(huán)境下关面,cache緩存中多了個 maskZeroBits
字段演怎,只知道 objc_msgSend
使用的,確不知道如何使用捅厂。
下面將探索 objc_msgSend
及 runtime
的原理贯卦。
Runtime
Runtime 簡介
編譯時 (building)
:顧名思義就是正在編譯
的時候,編譯器把源代碼翻譯成機器能識別的代碼焙贷,實際上只是翻譯成某個中間狀態(tài)的語?撵割。最重要的是進行詞法分析
、語法分析
辙芍,并且檢查代碼是否符合規(guī)范
啡彬,這個過程就叫編譯時類型檢查
,也叫靜態(tài)類型檢查
故硅。這個過程代碼沒有加載到內(nèi)存中庶灿,?只是把代碼當作?本來掃描下。
運行時 (running)
:代碼跑起來吃衅,裝載到內(nèi)存
中往踢。運行時檢查錯誤和編譯時檢查錯誤 (或者靜態(tài)類型檢查) 不一樣,不是簡單的代碼掃描徘层,而是在內(nèi)存中做操作和判斷峻呕。
Runtime
提供了一套公共接口(函數(shù)和數(shù)據(jù)結(jié)構(gòu))的動態(tài)分享庫利职,基本是用 C
,C++
和 匯編
寫的瘦癌,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力猪贪。平時的業(yè)務中主要是使用官方Api,解決我們框架性的需求讯私。
Runtime 版本
Legacy
版本:早期版本热押,對應的編程接口:Objective-C 1.0
,32
位的Mac OS X
的平臺
Modern
版本:現(xiàn)行版本妄帘,對應的編程接口:Objective-C 2.0
楞黄,iPhone
和Mac OS X 10.5
及以后的系統(tǒng)中的64
位系統(tǒng)。
Runtime 調(diào)用方式
Runtime 調(diào)用方式有三種:
Objective-C 代碼方式
需要編寫和編譯 Objective-C
源碼抡驼,runtime
將完成 OC
編譯成 C
的過渡鬼廓,實現(xiàn)底層 runtime API
的轉(zhuǎn)變,比如:
編譯成底層源碼:
方法的調(diào)用其實是底層在調(diào)用
objc_msgSend
函數(shù)致盟,下面會詳細分析其原理碎税。
Framework & Serivce 等系統(tǒng)API
調(diào)用 isKindOfClass
的方法分析其原理:
編譯成底層源碼:
NSObject
的方法底層也是在調(diào)用objc_msgSend
函數(shù)。sel_registerName
獲取對應的SEL
馏锡;objc_getClass
獲取實例對象雷蹂。其實不只是
isKindOfClass:
方法,respondsToSelector:
是否接收特定消息杯道;conformsToProtocol:
是否聲明實現(xiàn)協(xié)議方法匪煌;methodForSelector:
方法地址的獲取。
Runtime API
直接調(diào)用 runtime API
的方式
-
objc_msgSend
函數(shù)需要導入<objc/message.h>
- 需要修改
objc_msgSend
配置党巾,Enable Strict Checking of objc_msgSend Calls
設置為NO
萎庭,默認值為YES
,意思是編譯器會檢查齿拂,objc_msgSend
只接收1個參數(shù)驳规。此處目的不讓編譯器檢查。
打印結(jié)果:
許多
Objective-C
代碼可以通過runtime API
替換為C
實現(xiàn)署海。
方法的本質(zhì)
objc_msgSend
方法的調(diào)用就是消息發(fā)送吗购,調(diào)用底層 objc_msgSend
函數(shù),例如:
消息發(fā)送:
objc_msgSend(消息接收者砸狞,消息的主體(sel + 參數(shù)))
objc_msgSendSuper
添加一個 ZLObject
的子類 ZLSubObject
捻勉,并重寫doSomething
,如下:
編譯成源碼:
父類消息發(fā)送:
objc_msgSendSuper(父類接收者刀森,消息主體(sel + 參數(shù)))
子類對象可以通過
objc_msgSendSuper
方式調(diào)用父類的方法贯底。
objc_msgSendSuper
定義如下:
如果想要模擬調(diào)用 objc_msgSendSuper
函數(shù),就必須了解其方法參數(shù)意義。最主要的還是 super
結(jié)構(gòu)體禽捆,結(jié)構(gòu)如下:
當前版本是 __OBJC2__
,最終簡化為:
調(diào)用 objc_msgSendSuper
函數(shù):
打印結(jié)果:
方法的本質(zhì):消息接收者通過
sel
查找imp
的過程飘哨。
objc_msgSend 快速查找
objc_msgSend 匯編源碼:
// objc_msgSend 匯編入口 (主要是通過 isa 拿到接收者類胚想,進而拿到cache)
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// cmp: 比較指令(其本質(zhì)是做減法), 不影響寄存器的值.
// 此處判斷接收者是否存在, p0是接收者, objc_msgSend的第一個參數(shù): (id)receiver
cmp p0, #0
#if SUPPORT_TAGGED_POINTERS (__LP64__架構(gòu)執(zhí)行)
// le: 小于等于0 (p0不存在)
b.le LNilOrTagged
#else (!__LP64__架構(gòu)執(zhí)行)
// 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__架構(gòu)執(zhí)行)
LNilOrTagged:
// 清空寄存器孽拷,跳出 _objc_msgSend函數(shù)
b.eq LReturnZero
// 通過tagged pointer 獲取class
GetTaggedClass
// 進入緩存查找流程
b LGetIsaDone
#endif
LReturnZero:
// x0寄存器存的是0 (接收者不存在)
// 清空數(shù)據(jù)
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
// 跳出 _objc_msgSend函數(shù)
ret
END_ENTRY _objc_msgSend
1. 核心邏輯:
先判斷
receiver
是否<=0
。如果是
<= 0
半抱,再判斷架構(gòu)脓恕,如果是__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的基址偏移量(內(nèi)存偏移, 獲取 classes 數(shù)組)
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
內(nèi)存平移量通過平移量偏移,獲取
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則跳轉(zhuǎn)抬吟。
// 判斷 p16 的第0位為0,如果成立則跳轉(zhuǎn)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的基址偏移量(內(nèi)存偏移, 獲取 classes 數(shù)組)
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 成立跳轉(zhuǎn)
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp 已經(jīng)授權(quán)了類
// 無需授權(quán)將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
)金麸,如果開啟擎析,根據(jù)偏移量找到indexcls
,再通過與
操作,得到cls
并賦值給p16
揍魂;否則桨醋,直接取cls
賦值給p16
。
__LP64__
:判斷是否需要授權(quán)(
needs_auth
)现斋,如果需要喜最,調(diào)用ExtractISA
將isa & ISA_MASK
結(jié)果賦值給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時都授權(quán) (當前架構(gòu)會執(zhí)行)
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// 授權(quán)所有的isa (不會執(zhí)行)
mov x10, $2
// movk: 位移指令瞬内,movk移動高x位的到寄存器,其余空位保持原樣限书;
// x10 = #0x6AE1 << 48 (ISA_SIGNING_DISCRIMINATOR = 0x6AE1)
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
// autda: 授權(quán)向后讀取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: 第二個參數(shù)SEL秦躯,w11 = p11 = mask ( 執(zhí)行cache_hash()函數(shù),獲取插入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則跳轉(zhuǎn)(與tbz相反)裆装。
// p11第0位不為0則跳轉(zhuǎn) 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則跳轉(zhuǎn) LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif // __has_feature(ptrauth_calls)
// eor: 異或指令
// p12 = sel ^ (sel >> 7) ( 執(zhí)行cache_hash()函數(shù)踱承,真機執(zhí)行 >>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--] (內(nèi)存平移)
1: ldp p17, p9, [x13], #-BUCKET_SIZE
// 比較得到的p9 == p1 ?
cmp p9, p1
// 如果不相等(沒找到), 跳轉(zhuǎn)第3步
b.ne 3f
// 如果命中茎活,執(zhí)行 CacheHit
2: CacheHit \Mode
// cbz: 為0跳轉(zhuǎn)。
// 判斷得到的sel == 0琢唾,如果成立载荔,跳轉(zhuǎn)MissLabelDynamic
3: cbz p9, \MissLabelDynamic
// 判斷 bucket >= buckets
cmp p13, p10
// 如果大于等于(bucket >= buckets 成立), 跳轉(zhuǎn)第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的位數(shù)
// 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--] (內(nèi)存平移)
4: ldp p17, p9, [x13], #-BUCKET_SIZE
// 判斷 p9(sel) = _cmd
cmp p9, p1
// 如果相等,跳轉(zhuǎn)第2步 CacheHit
b.eq 2b
// 如果不相等采桃,判斷p9(sel) 是否等于0
cmp p9, #0
// 并且 p13(bucket) > p12(first_probed bucket)
ccmp p13, p12, #0, ne
// hi: 無符號大于懒熙,再次執(zhí)行第4步
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
1. 核心邏輯如下:
總體流程:
CacheLookup
→buckets
→bucket
→sel & imp
判斷架構(gòu),并找到
buckets
普办,以及buckets
中sel
對應的index
工扎。p10 = buckets,p11 = mask衔蹲,p12 = index
肢娘。
arm64 64位
的情況下如果_bucketsAndMaybeMask
第0
位不為0
則執(zhí)行LLookupPreopt()
。兩個循環(huán)查找過程:
首先
buckets & index << 4
找到對應的bucket
。do-while
循環(huán)判斷buckets[index]
橱健,如果能找到sel
而钞,說明命中,直接執(zhí)行CacheHit
拘荡;如果存在sel為空笨忌,則說明是沒有緩存的,就直接__objc_msgSend_uncached()
俱病。平移獲得
p13 = buckets[mask]
對應的元素,也就是倒數(shù)第二個袱结。p13 = buckets & mask << 4
找到mask
對應的bucket
亮隙,do-while
循環(huán)判斷buckets[mask]
的sel
,直到index
垢夹。如果命中溢吻,執(zhí)行CacheHit
;如果存在sel為空果元,則說明是沒有緩存的促王,就結(jié)束循環(huán)。最終仍然沒有找到則執(zhí)行
__objc_msgSend_uncached()
真機環(huán)境下而晒,
maskZeroBits
始終是4位0 (0x0000ffffffffffff | 0x0000fffffffffffe)蝇狼,獲取bucket
時,_bucketsAndMaybeMask >> 44
后就不用再<<4
找到對應bucket
的地址倡怎。
f
b
分別代表front
與back
迅耘,跳轉(zhuǎn)順序往前(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;
// 循環(huán)
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;
// 循環(huán)
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
// 授權(quán)向前讀取 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
// 如果不等于, 跳轉(zhuǎn)5
b.ne 5f
// p17 = isa - p17 >> 32 = imp
sub x17, x16, x17, LSR #32
.if \Mode == NORMAL
// 跳轉(zhuǎn)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
// 跳轉(zhuǎn) 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
// 授權(quán)向前存儲到$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, 跳轉(zhuǎn)到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.
// 授權(quán)簽名imp
AuthAndResignAsIMP x17, x10, x1, x16
// 比較p16和p15, (p15是開始調(diào)用_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
會執(zhí)行NORMAL
邏輯彰触,會直接執(zhí)行TailCallCachedImp
梯投,其內(nèi)部執(zhí)行(buckets ^ SEL) ^ isa
,對imp
進行了解碼,并返回imp
分蓖。
GETIMP
和LOOKUP
都會授權(quán)注冊imp
尔艇,并返回imp
。不同的是GETIMP
是判斷之后再授權(quán)imp
么鹤;LOOKUP
先授權(quán)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
在沒有命中緩存時,會執(zhí)行 __objc_msgSend_uncached
函數(shù)蒸甜。
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
// 調(diào)用 MethodTableLookup (涉及到慢速查找邏輯)
MethodTableLookup
// 返回 imp
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
主要執(zhí)行
MethodTableLookup
棠耕,其中涉及到了慢速查找。下篇進行分析柠新。