「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。
上一篇对消息发送,方法查找 了解了,这一篇就来看看当在缓存,类,所有父类里都找不到方法时,进入动态方法解析的流程。
基于objc4-779.1源码
当我们调用一个不存在的方法时\
可以看出__forwarding_prep_0___ 函数调用了 ___forwarding___函数,接着调用了 doesNotRecognizeSelector方法,最后抛出异常.而且是发生在CoreFoundation。(这些先放一放)
接着上一篇先看看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);
}
当我们调用一个方法,哪都没有找到的时候,这个时候就会调用resolveMethod_locked,尝试动态方法解析。
在方法里加个断点一步步往下看看
if (sel == @selector(tttt)) {
}
通过堆栈信息可以看到流程_objc_msgForward_impcache->_objc_msgForward->_objc_forward_handler->__forwarding_prep_0___->___forwarding___
又是头疼的汇编。
看看_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
流程 确实如此 _objc_msgForward_impcache->_objc_msgForward->_objc_forward_handler
这个handler回调 容易理解
那么这个回调什么时候设置的呢。
源码里面搜索可以发现 默认的_objc_forward_handler是objc_defaultForwardHandler,它干的活就是打印日志触发 crash。所以要实现消息转发,就得给它赋予新的值,这个时候就要用到objc_setForwardHandler。
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
#if SUPPORT_STRET
_objc_forward_stret_handler = fwd_stret;
#endif
}
而这一部分是发生在CoreFoundation中,而其源码里面这些部分却被删掉了,根本看不见。这个时候我们只能通过反编译看看了。
将CoreFoundation的可执行文件放到Hopper中。可以看到一个初始化___CFInitialize。
我们看看它的伪代码
果然 可以看到初始化的时候将___forwarding_prep_0___和___forwarding_prep_1___,作为参数调用了objc_setForwardHandler。(___forwarding_prep_1___是结构体相关的)
so我们来看看___forwarding_prep_0___的实现
这一下前面我们通过断点抓到的堆栈流程_objc_msgForward_impcache->_objc_msgForward->_objc_forward_handler->__forwarding_prep_0___->___forwarding___ 就通了。
接下来就是___forwarding___了,消息转发的逻辑就在这了
先看它的伪代码。
int ____forwarding___(int arg0, int arg1) {
var_38 = arg1;
r15 = arg0;
rax = COND_BYTE_SET(NE);
r13 = _objc_msgSend;
if (arg1 != 0x0) {
r13 = _objc_msgSend_stret;
}
var_30 = *(r15 + (rax & 0xff) * 0x8 + 0x8);
rbx = *(r15 + (rax & 0xff) * 0x8);
r14 = (rax & 0xff) * 0x8;
if ((rbx >= 0x0) || ((rbx & 0x7000000000000000) != 0x0)) goto loc_9cc0d;
loc_9ce24:
r12 = _getAtomTarget(rbx);
*(r15 + r14) = r12;
___invoking___(r13, r15, r15, 0x400, 0x0);
if (*r15 == r12) {
*r15 = rbx;
}
goto loc_9ce51;
loc_9ce51:
rax = r15;
return rax;
loc_9cc0d:
var_40 = r14;
r12 = object_getClass(rbx);
r14 = class_getName(r12);
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_9cc80;
loc_9cc3a:
rax = [rbx forwardingTargetForSelector:var_30];
if ((rax == 0x0) || (rax == rbx)) goto loc_9cc80;
loc_9cc58:
r14 = var_40;
if ((rax >= 0x0) || ((rax & 0x7000000000000000) != 0x0)) goto loc_9cc74;
loc_9ce21:
rbx = rax;
goto loc_9ce24;
loc_9cc74:
*(0x0 + r14) = rax;
r15 = 0x0;
goto loc_9ce51;
loc_9cc80:
var_40 = rbx;
if (strncmp(r14, "_NSZombie_", 0xa) == 0x0) goto loc_9ce63;
loc_9cca0:
r14 = var_40;
r13 = var_38;
if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_9ceb4;
loc_9ccbf:
r12 = [r14 methodSignatureForSelector:var_30];
if (r12 == 0x0) goto loc_9cf11;
loc_9ccdf:
rbx = [r12 _frameDescriptor];
if (((*(int16_t *)(*rbx + 0x22) & 0xffff) >> 0x6 & 0x1) != r13) {
rdx = sel_getName(var_30);
rsi = "";
r8 = " not";
rcx = r8;
if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) != 0x0) {
rcx = rsi;
}
if (r13 != 0x0) {
r8 = rsi;
}
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[2039]);
}
r13 = rbx;
rbx = [NSInvocation _invocationWithMethodSignature:r12 frame:r15];
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) != 0x0) {
[r14 forwardInvocation:rbx];
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", r14, object_getClassName(r14), r8, r9, stack[2039]);
}
if (rbx->_retainedArgs != 0x0) {
rax = *r13;
if ((*(int8_t *)(rax + 0x22) & 0x80) != 0x0) {
rcx = rbx->_frame;
rdx = *(int32_t *)(rax + 0x1c);
rsi = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(r15 + rsi + rdx), *(rcx + rsi + rdx), *(int32_t *)(*rax + 0x10));
}
}
r15 = rbx->_retdata;
if ((*(int8_t *)[r12 methodReturnType] & 0xff) == 0x44) {
asm{ fld tword [r15] };
}
goto loc_9ce51;
loc_9cf11:
r15 = sel_getName(var_30);
r8 = sel_getUid(r15);
r12 = var_30;
if (r8 != var_30) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", r12, r15, r8, r9, stack[2039]);
}
if (class_respondsToSelector(object_getClass(r14), @selector(doesNotRecognizeSelector:)) != 0x0) {
rax = [r14 doesNotRecognizeSelector:r12];
asm{ int3 };
}
else {
rax = _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", r14, object_getClassName(r14), r8, r9, stack[2039]);
asm{ int3 };
}
return rax;
loc_9ceb4:
rbx = class_getSuperclass(r12);
r15 = 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'?", r14, r15, object_getClassName(r14), r9, stack[2039]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", r14, r15, r8, r9, stack[2039]);
}
goto loc_9cf11;
loc_9ce63:
if (*(int8_t *)___CFOASafe != 0x0) {
___CFRecordAllocationEvent();
}
rax = _CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p", r14 + 0xa, sel_getName(var_30), var_40, r9, stack[2039]);
asm{ int3 };
return rax;
}
虽然不懂汇编,但是结合伪代码,大致也能看看。
首先是查看是否实现forwardingTargetForSelector方法,没有就去loc_9cc80
这里有个僵尸对象的判断,我们往下,查看是否实现methodSignatureForSelector,
-
如果没有响应,跳转至
loc_9ceb4,则直接报错 -
如果获取
methodSignatureForSelector是nil,也直接报错\
如果methodSignatureForSelector不为空,则调用 forwardInvocation执行 NSInvocation
至此 动态方法解析流程 分析完毕,有误请大佬指正。