iOS底層-Runtime及objc_msgSend快速查找

前言

在分析 cache_t原理 時,提及很多次的 objc_msgSend 函數(shù),以及在真機環(huán)境下关面,cache緩存中多了個 maskZeroBits 字段演怎,只知道 objc_msgSend 使用的,確不知道如何使用捅厂。

下面將探索 objc_msgSendruntime 的原理贯卦。

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)分享庫利职,基本是用 CC++匯編 寫的瘦癌,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力猪贪。平時的業(yè)務中主要是使用官方Api,解決我們框架性的需求讯私。

Runtime 版本

  • Legacy 版本:早期版本热押,對應的編程接口:Objective-C 1.032 位的 Mac OS X 的平臺

  • Modern 版本:現(xiàn)行版本妄帘,對應的編程接口:Objective-C 2.0楞黄,iPhoneMac 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. 核心邏輯:

  • 獲取低 3tag 和 高 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)用 ExtractISAisa & ISA_MASK 結(jié)果賦值給 p16;否則將 isa 的值存入 p16 寄存器庄蹋。( _objc_msgSendneeds_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. 核心邏輯如下:

  • 總體流程:CacheLookupbucketsbucketsel & imp

  • 判斷架構(gòu),并找到 buckets普办,以及 bucketssel 對應的 index工扎。p10 = buckets,p11 = mask衔蹲,p12 = index肢娘。

  • arm64 64位 的情況下如果 _bucketsAndMaybeMask0 位不為 0 則執(zhí)行 LLookupPreopt()

  • 兩個循環(huán)查找過程:

    • 首先 buckets & index << 4 找到對應的 bucketdo-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 分別代表 frontback迅耘,跳轉(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分蓖。

  • GETIMPLOOKUP 都會授權(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棠耕,其中涉及到了慢速查找。下篇進行分析柠新。

objc_msgSend快速查找流程圖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窍荧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恨憎,更是在濱河造成了極大的恐慌蕊退,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憔恳,死亡現(xiàn)場離奇詭異瓤荔,居然都是意外死亡,警方通過查閱死者的電腦和手機钥组,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門输硝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人者铜,你說我怎么就攤上這事腔丧。” “怎么了作烟?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵愉粤,是天一觀的道長。 經(jīng)常有香客問我拿撩,道長衣厘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任压恒,我火速辦了婚禮影暴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘探赫。我一直安慰自己型宙,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布伦吠。 她就那樣靜靜地躺著妆兑,像睡著了一般魂拦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搁嗓,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天芯勘,我揣著相機與錄音,去河邊找鬼腺逛。 笑死荷愕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的棍矛。 我是一名探鬼主播安疗,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼够委!你這毒婦竟也來了茂契?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慨绳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后真竖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脐雪,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年恢共,在試婚紗的時候發(fā)現(xiàn)自己被綠了战秋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡讨韭,死狀恐怖脂信,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情透硝,我是刑警寧澤狰闪,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站濒生,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谭跨,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一蜕衡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧觉义,春花似錦雁社、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磺浙。三九已至,卻和暖如春喊巍,著一層夾襖步出監(jiān)牢的瞬間屠缭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工崭参, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呵曹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓何暮,卻偏偏與公主長得像奄喂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子海洼,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容