若是在动态方法决议的流程仍是没有找到方法呢?最后会返回nil
或者_objc_msgForward_impcache
!markdown
那么是否是就没挽救的余地了呢?app
咱们能够经过instrumentObjcMessageSends
来打印objc
在底层的相关日志
:函数
@interface HPerson : NSObject
- (void)sayNO;
@end
@implementation HPerson
@end
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPerson * p = [HPerson alloc];
instrumentObjcMessageSends(YES);
[p sayNO];
instrumentObjcMessageSends(NO);
}
return 0;
}
复制代码
在objc
源码搜索instrumentObjcMessageSends
:oop
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;
}
复制代码
这里主要是给objcMsgLogEnabled
赋值,而objcMsgLogEnabled
则影响日志打印:网站
/*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
复制代码
再进入到logMessageSend
:ui
bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector) {
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
复制代码
发现日志写入到了/tmp
文件夹,运行后就能够看到日志文件:this
咱们发现resolveInstanceMethod
是动态方法决议的过程,可是以后的forwardingTargetForSelector
又是什么呢?url
咱们能够先command + shift + 0
,来打开开发文档进行查阅:spa
能够得知这个方法是一个重定向的过程!3d
在类中先重写forwardingTargetForSelector
方法,由于咱们调用的是对象方法,因此这里就是重写-方法
并运行:
发现确实进入到了forwardingTargetForSelector
方法!
那么咱们就能够把这个方法转交给其余类进行执行!
建立一个HClass
类,实现sayNO
方法:
这样就完成了消息转发,而且不像动态方法决议那样臃肿!
以上就是快速转发流程了!
若是HClass
类并无实现sayNO
方法呢?
那么就会进入到methodSignatureForSelector
方法,即慢速转发流程!
依旧先打开开发文档进行查阅:
可得知这是一个返回方法签名的过程!
在类中先重写methodSignatureForSelector
方法,由于咱们调用的是对象方法,因此这里就是重写-方法
并运行:
发现确实进入到了methodSignatureForSelector
方法!
感受开发文档可知这个方法须要搭配NSInvocation
,以及返回适当的方法签名,即NSMethodSignature
:
成功执行,可是却没有任何的实现!
由于在iOS中有事务这个概念,便可执行也可不执行,所以方法保存到了签名里面,在有须要的时候便可提取:
或者按照开发文档的案例进行处理:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
复制代码
虽然消息转发流程以及有所了解了,可是咱们并无在源码中看到调用的过程,那么究竟是怎么被调用的呢?
先用lldb
中用bt
查看堆栈:
能够看到在方法报错前执行了3
个方法:
__forwarding_prep_0___
--> ___forwarding___
--> doesNotRecognizeSelector
并且这3个方法都属于CoreFoundation
动态库!
咱们能够在苹果开源网站下载,可是在CoreFoundation
动态库内并无找到相对应的方法,说明苹果并无彻底的开源!
那么咱们只能进行逆向了!
要逆向首先要有可执行文件!
用模拟器在lldb
中用image list
进行查看:
而后把CoreFoundation拖入Hopper中,打开伪代码模式,搜索__forwarding_prep_0___
函数:
和咱们在bt
中看到流程同样,接着进入到了 ___forwarding___
函数!
进入 ___forwarding___
函数:
先判断是否有forwardingTargetForSelector
方法。
若是forwardingTargetForSelector
方法存在,即进入快速转发流程,调用forwardingTargetForSelector
方法。
接着判断返回值:
若是返回值为空或者和当前对象同样,则与没有forwardingTargetForSelector
方法同样,进入到loc_115baf
!
若是有返回值或者和当前对象不同,则通过处理后直接返回结果!
若是没有forwardingTargetForSelector
方法!
则进入到loc_115baf
:
1:判断是不是僵尸对象,不是则继续,是则跳转到
loc_115f34
,即14
处。2:判断是否有
methodSignatureForSelector:
方法,有则继续,没有则跳转到loc_115f4a
,即13
处。3:执行
methodSignatureForSelector:
方法,即慢速转发流程
,并判断返回值,有值则继续,值为空则跳转到loc_115fc5
,即10
处。4:判断是否有
_forwardStackInvocation:
方法,有则继续,没有则跳转到loc_115d65
,即7
处。5:执行
_forwardStackInvocation:
方法。6:跳转到
loc_115ef5
。7:没有
_forwardStackInvocation:
方法,即跳转到此,判断是否有forwardInvocation:
方法,有则继续,没有则跳转到loc_115f92
,即9
处。8:执行
forwardInvocation:
方法,并跳转到loc_115dd2
,和执行_forwardStackInvocation:
方法同样。9:没有
forwardInvocation:
方法,打印错误并继续。10 - 12:判断是否有
doesNotRecognizeSelector:
方法,并执行,这就是最后的找不到方法的报错
!13:打印错误并跳转到
loc_115fbe
,即10
处。14:打印错误并跳转到
loc_115f4a
,即13
处
能够看到不管是_forwardStackInvocation:
方法仍是forwardInvocation:
方法,最后都会到loc_115ef5
:
loc_115ef5:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
复制代码
即直接返回处理过的结果。
而全部的没有找到相对应的方法最终都会执行doesNotRecognizeSelector
方法:
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
复制代码
即方法没有找到的报错!
在反汇编流程中能够看到系统调用了一个_forwardStackInvocation
方法,这个方法并无对外暴露,可是咱们也能够经过重写这个方法来看看效果:
确实和咱们看到的流程同样,有_forwardStackInvocation
方法的时候就不会在走forwardInvocation:
方法了!
以前在动态方法决议的时候,发现动态方法决议会被调用2次,这是为何呢?
在objc源码的对象动态方法决议里面打上断点,bt查看堆栈:
发现第二进来是由于在CoreFoundation
库中的methodSignatureForSelector
方法里的__methodDescriptionForSelector
方法调用了objc库中的class_getInstanceMethod
方法!
咱们先看看objc源码中的methodSignatureForSelector
方法:
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
复制代码
能够发现是没有CoreFoundation
库不可用!
说明真正的源码是在CoreFoundation
库中,去反汇编
中进行查看:
发现确实有__methodDescriptionForSelector
方法,进一步跟进:
发现确实调用了class_getInstanceMethod
方法,再去objc源码中进行查看:
/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel) {
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
复制代码
发现的确调用了lookUpImpOrForward
函数!
因此动态方法决议
会被调用2
次!