淺談ios的hook原理

今天在看ReactiveCocoa的rac_signalForSelector源碼時,很好奇它們是怎么做到的??吹剿鋵嵑虯spects、jspatch的hook原理其實是一樣的。好吧。。。走起。

oc中的Method實際是這樣一個結(jié)構(gòu)。

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}   

SEL是方法名,IMP其實就是一個C函數(shù)指針,可以直接強制轉(zhuǎn)換的。runtime 提供了一些api,比如method_getImplementation,可以直接操作這些函數(shù)。

方法調(diào)用流程

在 Objective-C 中,所有的 [receiver message] 都會轉(zhuǎn)換為 objc_msgSend(receiver, @selector(message));

尋找 IMP 的過程(查找過程):

  1. 在當(dāng)前 class 的方法緩存里尋找(cache methodLists)
    找到了跳到對應(yīng)的方法實現(xiàn),沒找到繼續(xù)往下執(zhí)行
  2. 從當(dāng)前 class 的 方法列表里查找(methodLists),找到了添加到緩存列表里,然后跳轉(zhuǎn)到對應(yīng)的方法實現(xiàn);沒找到繼續(xù)往下執(zhí)行
  3. 從 superClass 的緩存列表和方法列表里查找,直到找到基類為止
    以上步驟還找不到 IMP,則用_objc_msgForward函數(shù)指針代替 IMP 。最后,執(zhí)行這個 IMP 。
    這里是關(guān)鍵點。
    貼個偽代碼:
//  objc-runtime-new.mm 文件里與 _objc_msgForward 有關(guān)的三個函數(shù)使用偽代碼展示
//  Created by https://github.com/ChenYilong
//  Copyright (c)  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
//  同時,這也是 obj_msgSend 的實現(xiàn)過程

id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //調(diào)用這個函數(shù),偽代碼...
}

//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息轉(zhuǎn)發(fā)
    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }

    Class curClass = cls;
    IMP imp = nil;
    do { //先查緩存,緩存沒有時重建,仍舊沒有則向父類查詢
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);

    return imp;
}

下面是消息轉(zhuǎn)發(fā)的流程(轉(zhuǎn)發(fā)過程):

  1. 第一個接手的具體方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,當(dāng)方法是實例方法時調(diào)用前者,當(dāng)方法為類方法時,調(diào)用后者。這個方法設(shè)計的目的是為了給類利用 class_addMethod 添加方法的機會。(這不是重點)
  2. 第二個階段是備援接收者階段,對象的具體方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此時,運行時詢問能否把消息轉(zhuǎn)給其他接收者處理,也就是此時系統(tǒng)給了個將這個 SEL 轉(zhuǎn)給其他對象的機會。(這也不是重點)
  3. 第三個階段是完整消息轉(zhuǎn)發(fā)階段,對應(yīng)方法-(void)forwardInvocation:(NSInvocation *)anInvocation,這是消息轉(zhuǎn)發(fā)流程的最后一個環(huán)節(jié)。(這個是重點,hook方案的核心集中在這里)
    用圖表示如下:
aa.png
  1. 如果這里還處理不了。那只能拋出沒有找到方法的異常了

=================================================
流程講完了,我們開始探討hook到底是怎么實現(xiàn)的。第二次進(jìn)入正題。

hook大體分兩步:

  1. 直接替換原方法的實現(xiàn)為_objc_msgForward。當(dāng)原來的函數(shù)被調(diào)用時,就不會在類方法,父類方法列表里查找實現(xiàn)了,直接表示找不到,進(jìn)入轉(zhuǎn)發(fā)流程。代碼如下:
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));

用_objc_msgForward來代替原來的函數(shù)。

  1. 替換forwardInvocation:的實現(xiàn),當(dāng)進(jìn)入轉(zhuǎn)發(fā)流程時,階段一和階段二都不接手,在階段三forwardInvocation:里會接手。
    代碼如下:
    id newForwardInvocation = ^(id self, NSInvocation *invocation) {
        //hook時要添加的代碼放在這里

        if (originalForwardInvocation == NULL) {
            [self doesNotRecognizeSelector:invocation.selector];
        } else {
            originalForwardInvocation(self, forwardInvocationSEL, invocation);
        }
    };

    class_replaceMethod(class, @selector(forwardInvocation:), imp_implementationWithBlock(newForwardInvocation), "v@:@");

這里會替換forwardInvocation:的實現(xiàn)。用newForwardInvocation代替。這樣就可以hook啦,完成自己的邏輯后,還要調(diào)用被hook的函數(shù)原來的邏輯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容