instrumentObjcMessageSends函数补充
instrumentObjcMessageSends
可以打印出指定区域内调用的所有方法。并且以文件的形式输出,输出路径为:\tmp\msgSends
。找到msgSends-xxxx文件
,此文件内就是全部调用的函数了。
<!-- msgSends-xxxx文件 -->
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject methodSignatureForSelector:
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
......
消息转发流程
快速转发(resolveInstanceMethod)
从msgSends-xxxx
文件看到了熟悉的resolveInstanceMethod
,这个方法是动态方法决议。接着看到下一个方法forwardingTargetForSelector
就变成了探究最新的线索,既然没有源码,那么就查看一下官方文档,在Developer Decumentation
中查找
![](https://img.haomeiwen.com/i1212147/7e20477f60105038.png)
由上面文档可知forwardingTargetForSelector
为快速转发流程的一个执行者,如果使用此方法对某一个函数进行重定向
,将非常有效。forwardingTargetForSelector会返回一个对象,成为未识别的消息的第一继承者,即方法的重定向。如果你想要更多的操作自由,请用forwardInvocation
进行操作。
探索一
<!-- LGPerson.h文件 -->
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)sayHello;
@end
NS_ASSUME_NONNULL_END
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
@implementation LGPerson
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
@end
<!-- main.m文件 -->
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayHello];
}
return 0;
}
//控制台打印
2021-08-03 20:51:34.917264+0800 002-instrumentObjcMessageSends辅助分析[12247:10008920] -[LGPerson forwardingTargetForSelector:] - sayHello
2021-08-03 20:51:34.920136+0800 002-instrumentObjcMessageSends辅助分析[12247:10008920] -[LGPerson sayHello]: unrecognized selector sent to instance 0x100524f20
崩溃之前打印了-[LGPerson forwardingTargetForSelector:] - sayHello
,证明在此过程可以对方法进行重定向
,以防止程序crash
探索二
添加LGStudent
类,在LGStudent.m
文件中实现sayHello
实例方法
<!-- LGStudent.h文件 -->
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGStudent : NSObject
- (void)sayHello;
@end
NS_ASSUME_NONNULL_END
<!-- LGStudent.m文件 -->
#import "LGStudent.h"
@implementation LGStudent
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
#import "LGStudent.h"
@implementation LGPerson
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [LGStudent alloc];
}
@end
//控制台打印
2021-08-03 21:04:10.823489+0800 002-instrumentObjcMessageSends辅助分析[12333:10017517] -[LGPerson forwardingTargetForSelector:] - sayHello
2021-08-03 21:04:10.824215+0800 002-instrumentObjcMessageSends辅助分析[12333:10017517] -[LGStudent sayHello]
发现LGPerson
的实例方法sayHello
由LGStudent
实现了
探索三
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// LGPerson没有实现,让LGStudent来实现sayHello方法,动态添加新方法
LGStudent * student = [LGStudent alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
慢速转发(methodSignatureForSelector)
如果LGStudent
也不想实现这个方法,那么此时就会进入到慢速转发
。也就是msgSends-xxxx
文件中的methodSignatureForSelector方法
,在Developer Decumentation
中查看文档
![](https://img.haomeiwen.com/i1212147/c58a92f80d77dccd.png)
给定参数sel返回一个方法的签名,跟forwardInvocation
关联使用。可以在Developer Decumentation
中查看forwardInvocation
方法。
探索一
LGStudent
类中的sayHello
方法实现屏蔽掉,LGPerson.m
添加慢速转发方法
<!-- LGPerson.m文件 -->
@implementation LGPerson
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [LGStudent alloc];
}
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
@end
//控制台打印
2021-08-03 21:41:50.839340+0800 002-instrumentObjcMessageSends辅助分析[12578:10038368] -[LGPerson forwardingTargetForSelector:] - sayHello
2021-08-03 21:41:50.840679+0800 002-instrumentObjcMessageSends辅助分析[12578:10038368] -[LGStudent sayHello]: unrecognized selector sent to instance 0x10381a800
程序崩溃打印了forwardingTargetForSelector
,为什么没有执行消息慢速转发呢?
原因是执行到消息快速转发这一步变成了LGStudent
,接下来就会去查找LGStudent
的消息慢速转发,而不会执行LGPerson
的慢速转发方法。
探索二
<!-- LGPerson.m文件 -->
@implementation LGPerson
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
@end
//控制台打印
2021-08-03 21:50:17.643313+0800 002-instrumentObjcMessageSends辅助分析[12634:10043869] -[LGPerson methodSignatureForSelector:] - sayHello
2021-08-03 21:50:17.644181+0800 002-instrumentObjcMessageSends辅助分析[12634:10043869] -[LGPerson sayHello]: unrecognized selector sent to instance 0x100563050
这里已经打印了methodSignatureForSelector
,结果依然崩溃。原因是未配合forwardInvocation
方法使用。
探索三
添加forwardInvocation
方法,与methodSignatureForSelector
方法成对使用
<!-- LGPerson.m文件 -->
@implementation LGPerson
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
@end
//控制台打印
2021-08-03 21:54:06.061891+0800 002-instrumentObjcMessageSends辅助分析[12650:10046182] -[LGPerson methodSignatureForSelector:] - sayHello
2021-08-03 21:54:06.062656+0800 002-instrumentObjcMessageSends辅助分析[12650:10046182] -[LGPerson sayHello]: unrecognized selector sent to instance 0x10052d290
依然崩溃,原因是未返回签名
探索四
<!-- LGPerson.m文件 -->
@implementation LGPerson
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
@end
//控制台打印
2021-08-03 22:00:56.467916+0800 002-instrumentObjcMessageSends辅助分析[12694:10051106] -[LGPerson methodSignatureForSelector:] - sayHello
通过判断sel是否是sayHello
,为其添加了方法签名
,并添加了forwardInvocation
方法,这里不会崩溃,但是并未实现任何事务。
慢速转发流程与快速转发流程的区别是什么?
慢速转发流程相对于快速转发流程,对于开发者来讲更加的自由灵活
。开发者只要提供一个签名,这个签名可以通过方法直接得到,接下来通过forwardInvocation
可以处理也可以不处理`。
关于事务
比如methodSignatureForSelector
方法通过创建并返回一个方法签名之后,会进入forwardInvocation
,这个方法就是用来处理事务的。当苹果一系列的转发流程来到这里,通过方法签名生成了一个事务,那么这个事务是可做可不做的,会保存anInvocation
。比如当你想起我的时候,你问我,在吗,我会回答你,我很好,一直在等你。
事务验证
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
//控制台打印
2021-08-03 22:10:48.735821+0800 002-instrumentObjcMessageSends辅助分析[12731:10056027] -[LGPerson methodSignatureForSelector:] - sayHello
2021-08-03 22:10:48.736625+0800 002-instrumentObjcMessageSends辅助分析[12731:10056027] <LGPerson: 0x10058fa30> - sayHello
通过打印证明了sayHello这个事务的存在,只要你想我在的时候我一直都在,这就是forwardInvocation
的涵义。当你不想找到我的时候,当前forwardInvocation
就会当作什么都没发生过。此事务就会被流失,此为慢速转发流程
。
慢速转发流程防止崩溃
慢速转发流程methodSignatureForSelector + forwardInvocation
放在NSObject的分类
中实现,程序就不会因为找不到方法而崩溃
了。但这只是假象的消失了,问题还是真实存在的,并且对内存、资源
造成了更多的浪费,意味着在这个流程中必然经历了很多多余的方法和不必要的流程
。
forwardInvocation
方法优化
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%@ - %@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
LGStudent *s = [LGStudent alloc];
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invoke];
}else if ([s respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:s];
}else{
NSLog(@"%s - %@",__func__,NSStringFromSelector(anInvocation.selector));
}
}
//控制台打印
2021-08-03 22:30:41.252499+0800 002-instrumentObjcMessageSends辅助分析[12853:10067942] -[LGPerson methodSignatureForSelector:] - sayHello
2021-08-03 22:30:41.253172+0800 002-instrumentObjcMessageSends辅助分析[12853:10067942] <LGPerson: 0x100644f30> - sayHello
2021-08-03 22:30:41.253282+0800 002-instrumentObjcMessageSends辅助分析[12853:10067942] -[LGStudent sayHello]
hoper反汇编/IDA反汇编
- 运行程序崩溃,通过
lldb动态调试指令bt
,查看堆栈信息
![](https://img.haomeiwen.com/i1212147/c84879e261a41c38.png)
通过堆栈信息查找崩溃前都调用了哪些函数?发现当快速查找和慢速查找都失效的时候会调用doesNotRecognizeSelector
函数,该函数属于coreFoundation框架
,同时此方法的上两个方法也是coreFoundation库内的函数,我们就以此为入口探索......
- 找Apple的
CoreFoundation源码
Souece Browser地址
![](https://img.haomeiwen.com/i1212147/f08c914f630b11fa.png)
- 下载并打开
CoreFoundation源码
,搜索___forwarding___、forwarding
![](https://img.haomeiwen.com/i1212147/5edd352a3954ed57.png)
各种搜索都没找到,最后得出结论CoreFoundation并没有完全的开源
- 去系统里面找
CoreFoundation动态库
CoreFoundation动态库资源和官网源码
Hopper反汇编工具
- 打开
hopper
选择Try the Demo
,然后将上一步的可执行文件CoreFoundation
拖入hopper进行反汇编,选择x86(64 bits)
,全局搜索_CF_forwarding_prep_0
与___forwarding___
![](https://img.haomeiwen.com/i1212147/b6e2bed40bc6a6f3.png)
- 查看
__forwarding_prep_0___
的汇编伪代码
,跳转至___forwarding___
![](https://img.haomeiwen.com/i1212147/11d581cfdb99b850.png)
- 通过伪代码的方式分析
___forwarding___
,首先是查看是否实现forwardingTargetForSelector
方法,如果没有响应,跳转至loc_64a67
即快速转发没有响应,进入慢速转发流程
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
rdi = arg0;
r15 = rdi;
var_30 = *___stack_chk_guard;
rcx = COND_BYTE_SET(NE);
// 根据参数arg1判断是通过_objc_msgSend_stret或者_objc_msgSend发送消息
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rax = rcx;
rbx = *(r15 + rax * 0x8);
rcx = *(r15 + rax * 0x8 + 0x8);
var_140 = rcx;
r13 = rax * 0x8;
// 如果判断为0,跳转至loc_649bb
if ((rbx & 0x1) == 0x0) goto loc_649bb;
loc_6498b:
// 判断指针taggedpointer
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = rcx ^ rbx;
rax = rcx >> 0x1 & 0x7;
if (rax == 0x7) {
rcx = rcx >> 0x4;
rax = (rcx & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_64d48;
loc_649bb:
var_148 = r13;
var_138 = r12;
var_158 = rsi;
// 获取当前类
rax = object_getClass(rbx);
r12 = rax;
r13 = class_getName(rax);
r14 = @selector(forwardingTargetForSelector:);
// 判断当前类是否可以响应方法forwardingTargetForSelector
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;
loc_649fc:
rdi = rbx;
rax = _objc_msgSend(rdi, r14);
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
- 跳转至
loc_64a67
,在其下方判断是否响应methodSignatureForSelector
方法,如果没有响应跳转至loc_64dd7
直接报错
loc_64a67:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
rbx = @selector(methodSignatureForSelector:);
r14 = var_138;
var_148 = r15;
if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;
loc_64ab2:
rax = _objc_msgSend(r14, rbx);
rbx = var_158;
if (rax == 0x0) goto loc_64e3c;
- 如果获取
methodSignatureForSelector
的方法签名为nil,也是直接报错
loc_64dd7:
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_64e3c;
loc_64dc1:
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_64dd7;
}
- 如果
methodSignatureForSelector
返回值不为空,则在forwardInvocation
方法中对invocation
进行处理
loc_64c19:
r15 = @selector(forwardInvocation:);
if (class_respondsToSelector(object_getClass(r14), r15) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
_objc_msgSend(0x0, r15);
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
通过上面查找可以验证,消息转发的方法有3个
- 快速转发
forwardingTargetForSelector
- 慢速转发
methodSignatureForSelector
,forwardInvocation
消息转发流程图
![](https://img.haomeiwen.com/i1212147/9aa2bdbbe8c169ac.png)
动态方法决议执行两次的探究
修改LGPerson.m文件
代码如下
<!-- LGPerson.m文件 -->
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
<!-- main.m文件 -->
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayHello];
}
return 0;
}
//控制台打印
2021-08-04 12:11:14.125273+0800 002-instrumentObjcMessageSends辅助分析[14509:10261254] +[LGPerson resolveInstanceMethod:] - sayHello
2021-08-04 12:11:14.126655+0800 002-instrumentObjcMessageSends辅助分析[14509:10261254] +[LGPerson resolveInstanceMethod:] - sayHello
2021-08-04 12:11:14.126965+0800 002-instrumentObjcMessageSends辅助分析[14509:10261254] -[LGPerson sayHello]: unrecognized selector sent to instance 0x10050ed40
通过控制台打印信息,我们发现动态决议方法resolveInstanceMethod
执行了两次,这是为什么呢?下面使用objc4-818.2源码
进行探索
- 在慢速查找流程中,我们了解到
resolveInstanceMethod
方法的执行是通过lookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethod
来到resolveInstanceMethod源码
,在源码中通过发送resolve_sel
消息触发,如下所示
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 发送resolve_sel消息
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 在
resolveInstanceMethod
方法的bool resolved = msg(cls, resolve_sel, sel);
处加一个断点,通过bt打印第二次调用动态决议的堆栈信息
![](https://img.haomeiwen.com/i1212147/259c7c44f3881c33.png)
- 查看上面的
hopper反汇编代码
,配合上面堆栈信息,发现会执行到class_respondsToSelector_inst -> _lookUpImpTryCache
,也就是消息转发完都没有找到,会来到_lookUpImpTryCache
loc_64e3c:
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;
loc_64dd7:
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_64e3c;
- 通过hopper反汇编
CoreFoundation
的可执行文件,查看methodSignatureForSelector
方法的伪代码 - 通过
methodSignatureForSelector
伪代码进入___methodDescriptionForSelector
的实现 - 进入
___methodDescriptionForSelector
的伪代码实现,结合汇编的堆栈打印,可以看到在___methodDescriptionForSelector
这个方法中调用了objc4-818
的class_getInstanceMethod
- 这一点可以通过代码调试来验证,在
class_getInstanceMethod
方法处加一个断点,在执行了methodSignatureForSelector
方法后,返回了签名,说明方法签名是生效的,苹果在走到invocation
之前,给了开发者一次机会再去查询,所以走到class_getInstanceMethod
这里,又去走了一遍方法查询sayHello
,然后会再次走到动态方法决议
objc_msgSend发送消息的流程总结
-
快速查找流程
在类的缓存cache中查找指定方法的实现 -
慢速查找流程
如果缓存中没有找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找 -
动态方法决议
如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod
方法 -
消息转发
如果动态方法决议还是没有找到,则进行消息转发
,消息转发中有两次补救机会:快速转发+慢速转发
如果转发之后也没有,则程序崩溃unrecognized selector sent to instance
网友评论