instrumentObjcMessageSends
上一篇有讲到这个方法,那么这个方法的由来?
lookUpImpOrForward->log_and_fill_cache -> objcMsgLogEnabled,全局搜索,发现默认的objcMsgLogEnabled=false,在log_and_fill_cache中,只有当objcMsgLogEnabled=true的时候才会打印方法,所以我们找到全局中对objcMsgLogEnabled赋值的地方,这里就定位到了voidinstrumentObjcMessageSends(BOOL flag)这个方法里面
快速转发forwardingTargetForSelector
上面的代码在
Person里没有实现sayHello的方法,而是新增了forwardingTargetForSelector:的方法指向了student内部实现的sayHello的方法,此时就没有报错了
如果此时没有指定新的消息的接收者也就是
forwardingTargetForSelector:的return = nil,那么就来到了下面
慢速转发methodSignatureForSelector
下面这两个方法要搭配使用forwardInvocation这个方法可实现,可不实现
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");// 可处理 可不处理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
反汇编定位消息转发流程
在报错的时候控制台输入bt,Xcode会自动输出最后一次的调用堆栈
可以知道在
[NSObject(NSObject) doesNotRecognizeSelector:]和main之间又调用了两个比较重要的函数,经过多次查找最终在CoreFoundation的动态库里面找到。使用Hopper打开,定位到伪代码
我们去到
__forwarding__的方法里面
我们肯定聚焦在没有实现这一侧,好的,那就跳转到64a67这里,可以看到这里也定位到了。
上面的反汇编的这些流程也佐证了上面讲的消息转发。
resolveInstanceMethod方法走两次
打开Person里面的消息转发流程的那几个方法,打印
在方法的慢速转发之后,
**_forwardStackInvocation**这个sel再次调用了**[Person resolveInstanceMethod:]**,那么这个sel到底是什么呢?回到Hopper里面,在__forwarding__的方法里面
看伪代码,如果rax不存在,里明显是没有实现的,定位到64c19
1.正常情况下是没有实现的,回到64e2c,最后定位到了这里
2.如果实现了,也就是上面我们写的例子,继续往下走
看到调用了
forwardInvocation的方法,也就是上面控制台日志最后的一个方法,最后也回到了方法1的那个方法。但是____forwarding___.cold.4()具体的实现我们看不到,所以我们可以认为是系统在慢速转发之后触发了**_forwardStackInvocation:**方法,继续走了一遍查找流程,这也就是为什么**resolveInstanceMethod:**方法走两次的原因。
补充
lldb调试命令
| 命令 | 解释 |
|---|---|
| break NUM | 在指定的行上设置断点。 |
| bt | 显示所有的调用栈帧。该命令可用来显示函数的调用顺序。 |
| clear | 删除设置在特定源文件、特定行上的断点。其用法为:clear FILENAME:NUM。 |
| continue | 继续执行正在调试的程序。该命令用在程序由于处理信号或断点而导致停止运行时。 |
| display EXPR | 每次程序停止后显示表达式的值。表达式由程序定义的变量组成。 |
| file FILE | 装载指定的可执行文件进行调试。 |
| help NAME | 显示指定命令的帮助信息。 |
| info break | 显示当前断点清单,包括到达断点处的次数等。 |
| info files | 显示被调试文件的详细信息。 |
| info func | 显示所有的函数名称。 |
| info local | 显示当函数中的局部变量信息。 |
| info prog | 显示被调试程序的执行状态。 |
| info var | 显示所有的全局和静态变量名称。 |
| kill | 终止正被调试的程序。 |
| list | 显示源代码段。 |
| make | 在不退出 gdb 的情况下运行 make 工具。 |
| next | 在不单步执行进入其他函数的情况下,向前执行一行源代码。 |
| print EXPR | 显示表达式 EXPR 的值。 |
| print-object | 打印一个对象 |
| print (int) name | 打印一个类型 |
| set artist = @"test" | 设置变量值 |
| what is | 查看变量的数据类型 |
[