动态方法决议
首先我们在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];
}

通过运行我们发现,虽然程序
依然崩溃,但是我们检测到了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,获取sayMaster的imp与sel,当我们检测到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;
}

通过增加
分类方法我们发现确实可以通过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进行日志存储,我们通过查找其中存储的路径去查看对应的日志:

我们通过查找其位置
/private/tmp/msgSend-XXX去获取日志文件
通过日志内容,我们可以清晰的看到其中的方法调用
forwardingTargetForSelector(快速流程)与methodSignatureForSelector(慢速流程)
快速流程
因此我们可以通过重写这两个方法去拦截对应的内容

通过苹果官方文档可以发现
forwardingTargetForSelector的方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [LGStudent alloc];
}
重新创建一个类或在其他类中实现对应的方法,当该方法有实现(即能查找到方法的imp)时就不会崩溃。
慢速流程

根据苹果官方文档我们可以得出
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中

然后通过查看
镜像位置 image list获取CoreFoundation的存储位置/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation接下来我们需要找到对应的文件并将执行文件添加到hopper中由上面的崩溃信息可知主要是
_forwarding_prep_0与_forwarding_,通过全局查找我们可以看到
然后点击
_forwarding_去查看实现代码,通过查找我们发现重点为
这就可以寻找到消息的
快速转发,值得注意的是:
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
总结
