消息转发流程

动态方法决议

首先我们在objc_msgSend快速慢速查找后都没有找到对应的方法,这时候我们就会去调用resolveMethod_locked,这是苹果给提供的一次机会(重新查询一遍)。

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

    runtimeLock.unlock();
    // 动态方法决议 : 给一次机会 重新查询
    if (! cls->isMetaClass()) {  // 对象 - 类
        // 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);
}

通过上述代码可知,肯定会调用resolveInstanceMethod或者resolveClassMethod,区别就是类方法实例方法的的不同,因此我们通过重写这两个方法去检测崩溃

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

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

通过运行我们发现,虽然程序依然崩溃,但是我们检测到say666方法,因此可以肯定必定会走此方法,接下来我们就可以在里面处理逻辑,避免崩溃。

- (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);
    }

首先我们实现一个方法sayMaster,获取sayMasterimpsel,当我们检测到say666方法时就可以更改为sayMaster方法的实现,这样就可以避免指定方法没有实现导致的崩溃。

+ (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];
}

同理,我们也可以拦截类方法进行更改指定方法的实现来避免崩溃

思考:

通过在resolveInstanceMethod方法中实现对sayNB方法的更改我们发现还是崩溃了,我们之前了解到既然类方法元类中存储的时候也是实例方法,那我们为什么不能在当前类直接更改resolveInstanceMethod来实现呢?

解析:

我们在当前类中实现resolveInstanceMethod时,它只是针对当前类实例方法的检测,因此我们检测不到类方法,故而我们猜测因为类方法是存在元类中,我们需要通过给NSObjcet增加分类方法来实现resolveInstanceMethod就可以进行更改:

#import <objc/message.h>
@implementation NSObject (LG)
// 调用方法的时候 - 分类
+ (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

通过增加分类方法我们发现确实可以通过resolveInstanceMethod来实现类方法检测,但是如果在此更改的话会如上图所示拦截到许多额外的方法,这样就可以隐藏更多的问题。

消息转发

消息转发也是分为快速转发慢速转发两种情况,首先我们根据日志追踪去查找其中的流程

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;
}

我们在lookUpImpOrForward中发现查找的过程会通过log_and_fill_cache进行日志存储,我们通过查找其中存储的路径去查看对应的日志:

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

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

通过日志内容,我们可以清晰的看到其中的方法调用forwardingTargetForSelector(快速流程)与methodSignatureForSelector(慢速流程)

快速流程

因此我们可以通过重写这两个方法去拦截对应的内容

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

通过苹果官方文档可以发现forwardingTargetForSelector的方法

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

重新创建一个类或在其他类中实现对应的方法,当该方法实现(即能查找到方法imp)时就不会崩溃。

慢速流程

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

根据苹果官方文档我们可以得出methodSignatureForSelector需要与forwardInvocation进行配合使用。

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

反编译

上面的方法我们是通过官网查找进行更改的,如果发现不了对应的方法我们可以通过反编译的方法进行跟踪,主要工具是hopper以及IDA,我们通过介绍hopper的方法去进行查看。
首先我们通过打印堆栈(bt)发现崩溃点在CoreFoundation

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

然后通过查看镜像位置 image list获取CoreFoundation的存储位置/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation接下来我们需要找到对应的文件并将执行文件添加到hopper
由上面的崩溃信息可知主要是_forwarding_prep_0_forwarding_,通过全局查找我们可以看到
截屏2020-09-25 上午9.51.03.png

然后点击_forwarding_去查看实现代码,通过查找我们发现重点为
截屏2020-09-25 上午9.51.36.png

这就可以寻找到消息的快速转发,值得注意的是:

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;

如果查找不到就会返回doesNotRecognizeSelector

总结

截屏2020-09-25 上午10.31.06.png
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容