消息轉(zhuǎn)發(fā)流程

動(dòng)態(tài)方法決議

首先我們?cè)?code>objc_msgSend的快速慢速查找后都沒有找到對(duì)應(yīng)的方法,這時(shí)候我們就會(huì)去調(diào)用resolveMethod_locked膘茎,這是蘋果給提供的一次機(jī)會(huì)(重新查詢一遍)薪夕。

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 動(dòng)態(tài)方法決議 : 給一次機(jī)會(huì) 重新查詢
    if (! cls->isMetaClass()) {  // 對(duì)象 - 類
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 類方法 - 元類
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {  
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

通過上述代碼可知脚草,肯定會(huì)調(diào)用resolveInstanceMethod或者resolveClassMethod,區(qū)別就是類方法實(shí)例方法的的不同原献,因此我們通過重寫這兩個(gè)方法去檢測(cè)崩潰

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 來了",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

截屏2020-09-24 下午5.01.24.png

通過運(yùn)行我們發(fā)現(xiàn)馏慨,雖然程序依然崩潰,但是我們檢測(cè)到say666方法姑隅,因此可以肯定必定會(huì)走此方法写隶,接下來我們就可以在里面處理邏輯,避免崩潰讲仰。

- (void)sayMaster{
    NSLog(@"%s",__func__);
}
if (sel == @selector(say666)) {
        NSLog(@"%@ 來了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }

首先我們實(shí)現(xiàn)一個(gè)方法sayMaster慕趴,獲取sayMasterimpsel,當(dāng)我們檢測(cè)到say666方法時(shí)就可以更改為sayMaster方法的實(shí)現(xiàn)鄙陡,這樣就可以避免指定方法沒有實(shí)現(xiàn)導(dǎo)致的崩潰冕房。

+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ 來了",NSStringFromSelector(sel));
    if (sel == @selector(sayNB)) {

        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return [super resolveClassMethod:sel];
}

同理,我們也可以攔截類方法進(jìn)行更改指定方法的實(shí)現(xiàn)來避免崩潰

思考:

通過在resolveInstanceMethod方法中實(shí)現(xiàn)對(duì)sayNB方法的更改我們發(fā)現(xiàn)還是崩潰了趁矾,我們之前了解到既然類方法元類中存儲(chǔ)的時(shí)候也是實(shí)例方法耙册,那我們?yōu)槭裁床荒茉诋?dāng)前類直接更改resolveInstanceMethod來實(shí)現(xiàn)呢?

解析:

我們?cè)诋?dāng)前類中實(shí)現(xiàn)resolveInstanceMethod時(shí)毫捣,它只是針對(duì)當(dāng)前類實(shí)例方法的檢測(cè)详拙,因此我們檢測(cè)不到類方法帝际,故而我們猜測(cè)因?yàn)?code>類方法是存在元類中,我們需要通過給NSObjcet增加分類方法來實(shí)現(xiàn)resolveInstanceMethod就可以進(jìn)行更改:

#import <objc/message.h>
@implementation NSObject (LG)
// 調(diào)用方法的時(shí)候 - 分類
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
        
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return NO;
}

截屏2020-09-24 下午5.28.54.png

通過增加分類方法我們發(fā)現(xiàn)確實(shí)可以通過resolveInstanceMethod來實(shí)現(xiàn)類方法檢測(cè)饶辙,但是如果在此更改的話會(huì)如上圖所示攔截到許多額外的方法,這樣就可以隱藏更多的問題蹲诀。

消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)也是分為快速轉(zhuǎn)發(fā)慢速轉(zhuǎn)發(fā)兩種情況,首先我們根據(jù)日志追蹤去查找其中的流程

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

我們?cè)?code>lookUpImpOrForward中發(fā)現(xiàn)查找的過程會(huì)通過log_and_fill_cache進(jìn)行日志存儲(chǔ)弃揽,我們通過查找其中存儲(chǔ)的路徑去查看對(duì)應(yīng)的日志:

截屏2020-09-24 下午6.15.38.png

我們通過查找其位置/private/tmp/msgSend-XXX去獲取日志文件
截屏2020-09-24 下午6.17.45.png

通過日志內(nèi)容侧甫,我們可以清晰的看到其中的方法調(diào)用forwardingTargetForSelector(快速流程)與methodSignatureForSelector(慢速流程)

快速流程

因此我們可以通過重寫這兩個(gè)方法去攔截對(duì)應(yīng)的內(nèi)容

截屏2020-09-24 下午6.30.18.png

通過蘋果官方文檔可以發(fā)現(xiàn)forwardingTargetForSelector的方法

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [LGStudent alloc];
}

重新創(chuàng)建一個(gè)類或在其他類中實(shí)現(xiàn)對(duì)應(yīng)的方法,當(dāng)該方法實(shí)現(xiàn)(即能查找到方法imp)時(shí)就不會(huì)崩潰蹋宦。

慢速流程

截屏2020-09-24 下午6.30.42.png

根據(jù)蘋果官方文檔我們可以得出methodSignatureForSelector需要與forwardInvocation進(jìn)行配合使用披粟。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //返回方法簽名,方法簽名可以通過官網(wǎng)type Encoding 進(jìn)行查看
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    // GM  sayHello - anInvocation - 漂流瓶 - anInvocation
    anInvocation.target = [LGStudent alloc];
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}

反編譯

上面的方法我們是通過官網(wǎng)查找進(jìn)行更改的,如果發(fā)現(xiàn)不了對(duì)應(yīng)的方法我們可以通過反編譯的方法進(jìn)行跟蹤冷冗,主要工具是hopper以及IDA守屉,我們通過介紹hopper的方法去進(jìn)行查看。
首先我們通過打印堆棧(bt)發(fā)現(xiàn)崩潰點(diǎn)在CoreFoundation

截屏2020-09-25 上午9.50.42.png

然后通過查看鏡像位置 image list獲取CoreFoundation的存儲(chǔ)位置/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation接下來我們需要找到對(duì)應(yīng)的文件并將執(zhí)行文件添加到hopper
由上面的崩潰信息可知主要是_forwarding_prep_0_forwarding_蒿辙,通過全局查找我們可以看到
截屏2020-09-25 上午9.51.03.png

然后點(diǎn)擊_forwarding_去查看實(shí)現(xiàn)代碼拇泛,通過查找我們發(fā)現(xiàn)重點(diǎn)為
截屏2020-09-25 上午9.51.36.png

這就可以尋找到消息的快速轉(zhuǎn)發(fā),值得注意的是:

loc_64fb7:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
    }
    goto loc_6501c;
    
loc_6501c:
    rax = sel_getName(var_140);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != var_140) {
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
    }
    rbx = @selector(doesNotRecognizeSelector:);
    if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
            ____forwarding___.cold.2(var_138);
    }
    rax = _objc_msgSend(var_138, rbx);
    asm{ ud2 };
    return rax;

如果查找不到就會(huì)返回doesNotRecognizeSelector

總結(jié)

截屏2020-09-25 上午10.31.06.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末思灌,一起剝皮案震驚了整個(gè)濱河市俺叭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泰偿,老刑警劉巖熄守,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異耗跛,居然都是意外死亡裕照,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門调塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晋南,“玉大人,你說我怎么就攤上這事羔砾「杭洌” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵姜凄,是天一觀的道長(zhǎng)政溃。 經(jīng)常有香客問我,道長(zhǎng)檀葛,這世上最難降的妖魔是什么玩祟? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮屿聋,結(jié)果婚禮上空扎,老公的妹妹穿的比我還像新娘。我一直安慰自己润讥,他們只是感情好转锈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著楚殿,像睡著了一般撮慨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脆粥,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天砌溺,我揣著相機(jī)與錄音,去河邊找鬼变隔。 笑死规伐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的匣缘。 我是一名探鬼主播猖闪,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肌厨!你這毒婦竟也來了培慌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤柑爸,失蹤者是張志新(化名)和其女友劉穎吵护,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體表鳍,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡何址,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了进胯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片用爪。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胁镐,靈堂內(nèi)的尸體忽然破棺而出偎血,到底是詐尸還是另有隱情,我是刑警寧澤盯漂,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布颇玷,位于F島的核電站,受9級(jí)特大地震影響就缆,放射性物質(zhì)發(fā)生泄漏帖渠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一竭宰、第九天 我趴在偏房一處隱蔽的房頂上張望空郊。 院中可真熱鬧份招,春花似錦、人聲如沸狞甚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哼审。三九已至谐腰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涩盾,已是汗流浹背十气。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留春霍,地道東北人砸西。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像终畅,于是被迫代替她去往敵國(guó)和親籍胯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353