通过这篇文章可以获得什么
- instrumentObjcMessageSends辅助分析方法的介绍
- 动态消息转发的一般探索思路
- 动态消息转发之快速转发
- 动态消息转发之慢速转发
- 动态消息转发之真实场景防止崩溃
- 动态消息转发之逆向探索方式
- CoreFoundation与Hopper的资源获取
- Hopper伪代码分解CoreFoundation的消息转发流程
- 消息的动态转发机制流程图
instrumentObjcMessageSends辅助分析方法的介绍
作用
可以打印出指定区域内调用的所有的方法
使用方式
// 外部引用定义
extern void instrumentObjcMessageSends(BOOL flag);
//使用区域,将想要查找的方法用instrumentObjcMessageSends圈起来
//开始监听传递参数为YES,结束监听传递参数为NO
FFPerson *person = [FFPerson alloc];
instrumentObjcMessageSends(YES);
[person likeGirls];
instrumentObjcMessageSends(NO);
如果查看结果
此方法将指定函数开始与结束之间调用的函数全部以文件的形式输出,存储路径为:\tmp\msgSends,找到msgSends-xxxx的文件,此文件内就是全部调用的函数了。
查找流程图解:
源码所在位置
当objcMsgLogEnabled为true的时候,将记录这中间调用的所有的方法
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
为什么会关注到instrumentObjcMessageSends
由于消息的动态方法决议之后所有可以看到源码的流程现都已经走完了,那么探索也就中断了,还好有那个骚气的男人发现了这个方法,让探索由可知的源码向未开源的源码探索......
一般探索思路
通过instrumentObjcMessageSends开启上帝视角
快速转发
通过上面msgSends-16848文件看到了熟悉的resolveInstanceMethod,这个方法是动态方法决议,看到下一个方法forwardingTargetForSelector就变成了探究最新的线索,既然没有源码,那么就查看一下官方文档,在Developer Decumentation中查找
通过此文档可以得知
forwardingTargetForSelector为快速转发流程的一个执行者,如果使用此方法对某一个函数进行重定向,那么将非常的有效。forwardingTargetForSelector会返回一个对象,成为未识别的消息的第一继承者,即方法的重定向。如果你想要更多的操作自由,请用forwardInvocation进行操作。
阶段一:代码初步论证
案例代码:
@interface FFPerson : NSObject
- (void)likeGirls;
@end
@implementation FFPerson
//likeGirls方法未实现
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FFPerson *person = [FFPerson alloc];
[person likeGirls];
}
return 0;
}
控制台打印:
2021-07-04 23:58:17.970363+0800 002-instrumentObjcMessageSends辅助分析[17126:1382603] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-04 23:58:17.972060+0800 002-instrumentObjcMessageSends辅助分析[17126:1382603] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x10055fa30
结果是崩溃,但是在崩溃之前打印了-[FFPerson forwardingTargetForSelector:] - likeGirls,证明了上述的探索方向是OK的,在此过程可以对方法进行重定向,以防止程序crash。
阶段二:对代码进行了进一步改动
创建一个FFBoys的类,FFBoys.h文件中不声明任何方法,在FFBoys.m文件中实现likeGirls实例方法
@interface FFBoys : NSObject
//为声明任何方法
@end
@implementation FFBoys
//实现了likeGirls方法
- (void)likeGirls {
NSLog(@"%s",__func__);
}
@end
@interface FFPerson : NSObject
//声明了likeGirls实例方法
- (void)likeGirls;
@end
@implementation FFPerson
//并未实现likeGirls实例方法
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//此刻将FFPerson的实例方法likeGirls发送到了FFBoys,让FFBoys来实现
return [FFBoys alloc];
}
@end
控制台打印结果:
2021-07-05 00:07:17.270104+0800 002-instrumentObjcMessageSends辅助分析[17170:1388173] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:07:17.270736+0800 002-instrumentObjcMessageSends辅助分析[17170:1388173] -[FFBoys likeGirls]
可以很清晰的得知FFPerson的实例方法likeGirls由FFBoys实现了。
疑问点一:为什么好好的方法,不在FFPerson类中自己实现,还要启用快速转发,由FFBoys来实现呢?
阶段三:定制一个背锅侠,动态添加新方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
慢速转发
当FFBoys这个背锅侠不想背锅的时候,他也不想实现这个方法,那么此时就会进入到慢速转发了。也就是msgSends-16848的methodSignatureForSelector方法,使用相同的方式,那么就查看一下官方文档,在Developer Decumentation中查找:
给定一个
sel,返回一个方法的签名,跟forwardInvocation关联使用,那么就查看一下官方文档,在Developer Decumentation中查找:
阶段一:methodSignatureForSelector
案例代码
@implementation FFPerson
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
@end
控制台打印结果:
2021-07-05 00:42:17.146674+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:42:17.147110+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson methodSignatureForSelector:] - likeGirls
2021-07-05 00:42:17.147231+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x103851710
结果依然是崩溃,这里已经打印了methodSignatureForSelector,由于并未返回签名,并且未配合forwardInvocation方法使用。
阶段二:forwardInvocation
案例代码:
@implementation FFPerson
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(likeGirls)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
@end
我这里通过判断sel是否是likeGirls,并为其添加了方法签名,然后添加了forwardInvocation方法,但是并未实现任何事务,这里不会崩溃
事务的概念
关于事务,methodSignatureForSelector方法通过创建并返回一个方法签名之后,进入forwardInvocation,这个方法就是用来处理事务的,当苹果一系列的转发流程来到这里,通过方法签名生成了一个事务,那么这个事务是可做可不做的,会保存anInvocation。比如当你想起我的时候,你问我,在吗,我会回答你,我很好,一直在等你。
代码验证:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
控制台打印
2021-07-05 00:56:28.493284+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:56:28.493856+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] -[FFPerson methodSignatureForSelector:] - likeGirls
2021-07-05 00:56:28.494254+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] <FFPerson: 0x104016a30> - likeGirls
通过打印证明了likeGirls这个事务的存在,只要你想我在的时候我一直都在,这就是forwardInvocation的涵义,当你不想找到我的时候,当前forwardInvocation就会当作什么都没发生过。此事务就会被流失,此为慢速转发流程。
真实场景防止崩溃
将慢速转发流程methodSignatureForSelector + forwardInvocation,放在NSObject的分类中实现,那么程序就不会因为找不到方法而崩溃了。只是假象的消失了,问题还是真实存在的,并且对内存、资源造成了更多的浪费,意味着在这个流程中必然经历了很多的多余的方法和不必要的流程。
慢速转发注意点:
要响应您的对象本身无法识别的方法,除了 forwardInvocation: 之外,您还必须覆盖 methodSignatureForSelector:。 转发消息的机制使用从 methodSignatureForSelector: 获得的信息来创建要转发的 NSInvocation 对象。 您的覆盖方法必须为给定的选择器提供适当的方法签名,或者通过预先制定一个或通过向另一个对象询问一个。
forwardInvocation完成代码
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
FFBoys *boys = [FFBoys alloc];
//如果自己可以处理,直接调用
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invoke];
}
//判断FFBoys是否可以处理,如果可以,直接调用
else if ([boys respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:boys];
}
//没有任何人去实现,那么这里将错误上报
else {
NSLog(@"发生了错误的方法:%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
}
逆向探索方式
通过lldb动态调试指令bt,打印当前的堆栈:
这里可以得知调用likeGirls依然是崩溃的,但是通过堆栈信息可以找打崩溃前都调用了那些函数,doesNotRecognizeSelector,此个函数时当快速查找和慢速查找都失效的时候会调用的方法,是属于coreFoundation框架下的,同时此方法的上两个方法同时也是coreFoundation库内的函数,不由得感觉到有鬼,有可能是线索。
找Apple的CoreFoundation源码
[Souece Browser地址:opensource.apple.com/tarballs/CF…]
找到这里,我觉得我又行了,又站起来了。开始搞CoreFoundation源码
源码搜索:___forwarding___、forwarding
源码搜索:
_CF_forwarding_prep_0
经过一阵瞎逼操作,啥也没找到,最后得出结论CoreFoundation并没有完全的开源,Apple爸爸还是你爸爸。哎~不给你看。
去系统里面偷一个CoreFoundation反汇编调试
CoreFoundation动态库资源获取
Hopper Disassembler反汇编工具介绍
官网地址:https://www.hopperapp.com/
基础功能介绍
探索开始,搜索_CF_forwarding_prep_0与___forwarding___
这里已经在对动态库CoreFoundation的反汇编中发现了_CF_forwarding_prep_0,___forwarding___,并且发现了调用顺序跟报错的堆栈式一致的。但是看反汇编的难度比较大,Hopper给我们提供一个更好的方式,通过伪代码的方式分析一下___forwarding___都做了什么?
Hopper伪代码分解CoreFoundation的消息转发流程
自此,消息的快速转发与慢速转发整体流程已经探索完成。
消息的动态转发机制流程图
解答在源码探索过程中提出的疑问点:
为什么好好的方法,不在FFPerson类中自己实现,还要启用快速转发,由FFBoys来实现呢?
方法的查找过程中,经过前面一些列的过程,到动态方法决议,这里方法找不到也没有关系,再到forwardingTargetForSelector,你告诉我谁有这个方法,谁有相同的方法,即找一个背锅侠。
慢速转发流程与快速转发流程的区别是什么?
慢速转发流程相对于快速转发流程,对于开发者来讲更加的自由、灵活的感觉。开发者只要提供一个签名,这个签名可以通过方法直接得到的,接下来通过forwardInvocation可以想处理也可以不处理`。