在上篇中,我们分析了动态决议流程,并通过重写resolveInstanceMethod动态添加IMP,使程序继续运行。但是resolveInstanceMethod却执行了两次。
1.通过instrumentObjcMessageSends日志
在log_and_fill_cache函数中可以找到instrumentObjcMessageSends的影子
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);
}
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
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;
}
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来判断是否打印日志,所以我们在调用方法前打印日志(/tmp/msgSends目录)
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
LGPerson *person = [LGPerson alloc];
[person sayInstanceMethod];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
复制代码
发现不仅调用了resolveInstanceMethod,也调用了一些其他方法
forwardingTargetForSelector,methodSignatureForSelector,doesNotRecognizeSelector
这里其实是动态方法决议之后,继续快速转发(forwardingTargetForSelector)与慢速转发(methodSignatureForSelector), doesNotRecognizeSelector打印输出提示。
在forwardingTargetForSelector方法中,我们可以返回一个对象,在返回的对象中查找sel,如果返回的对象的class也没有实现,同样也会报错doesNotRecognizeSelector。
在methodSignatureForSelector方法中,返回一个方法的签名,这个方法与 forwardInvocation: 搭配使用。
2.通过断点,查看第二次调用前的堆栈信息:
发现堆栈栈底是 **forwarding_prep_0_**函数,点击发现是 CoreFoundation库下的函数。其实通过堆栈我们就已经知道调用两次的原因了,不过我们还是利用一下反汇编,看看 **forwarding_prep_0_**底层实现。
forwarding
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
rdi = arg0;
r15 = rdi;
rcx = COND_BYTE_SET(NE);
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;
if ((rbx & 0x1) == 0x0) goto loc_649bb;
loc_6498b:
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);
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;
loc_649fc:
rdi = rbx;
rax = [rdi forwardingTargetForSelector:var_140];
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
loc_64a19:
r12 = var_138;
r13 = var_148;
if ((rax & 0x1) == 0x0) goto loc_64a5b;
loc_64a2b:
rdx = **_objc_debug_taggedpointer_obfuscator;
rdx = rdx ^ rax;
rcx = rdx >> 0x1 & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x4 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_64d45;
loc_64a5b:
*(r15 + r13) = rax;
r15 = 0x0;
goto loc_64d82;
loc_64d82:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_64d45:
rbx = rax;
goto loc_64d48;
loc_64d48:
if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
loc_64d55:
*(r15 + r13) = _getAtomTarget(rbx);
___invoking___(r12, r15);
if (*r15 == rax) {
*r15 = rbx;
}
goto loc_64d82;
loc_64ed1:
____forwarding___.cold.4();
rax = *(rdi + 0x8);
return rax;
loc_64a67:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
r14 = var_138;
var_148 = r15;
if (rax == 0x0) goto loc_64dd7;
loc_64ab2:
rax = [r14 methodSignatureForSelector:var_140];
rbx = var_158;
if (rax == 0x0) goto loc_64e3c;
loc_64ad5:
r12 = rax;
rax = [rax _frameDescriptor];
r13 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
rax = sel_getName(var_140);
rcx = "";
if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
r8 = "";
if (rbx == 0x0) {
r8 = " not";
}
_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.", rax, rcx, r8, r9, stack[-360]);
}
rax = object_getClass(r14);
rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
var_150 = r13;
if (rax == 0x0) goto loc_64c19;
loc_64b6c:
if (*0x5c2700 != 0xffffffffffffffff) {
dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
r15 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *0x5c26f8;
rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
r13 = &stack[-360];
__bzero(r13, rsi);
___chkstk_darwin(r13, rsi, r12, rcx);
rax = objc_constructInstance(*0x5c26f0, r13);
var_140 = r15;
[r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
[var_138 _forwardStackInvocation:r13];
r14 = 0x1;
goto loc_64c76;
loc_64c76:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *var_150;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rcx = *(int32_t *)(rax + 0x1c);
rdx = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
}
}
rax = [r12 methodReturnType];
rbx = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (r14 != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
[r13 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm { fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (r14 != 0x0) {
r15 = ____forwarding___.placeholder;
[r13 release];
}
}
goto loc_64d82;
loc_64c19:
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
[r14 forwardInvocation:rax];
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = &var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
loc_64e3c:
rax = sel_getName(var_140);
r14 = rax;
rax = sel_getUid(rax);
if (rax != var_140) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[-360]);
}
if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) {
____forwarding___.cold.2(var_138);
}
(*_objc_msgSend)(var_138, @selector(doesNotRecognizeSelector:));
asm { ud2 };
rax = loc_64ec2(rdi, rsi);
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[-360]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[-360]);
}
goto loc_64e3c;
loc_64dc1:
r14 = @selector(forwardingTargetForSelector:);
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_64dd7;
}
复制代码
从反汇编中我们可以大致看到对应的函数调用流程中有instrumentObjcMessageSends中出现的函数,
流程为:
总结:当对象方法通过慢速查找没找到对应方法的实现时,会进行动态方法决议。
1.在动态方法决议中,我们可以动态添加IMP,来保证程序继续运行。
2.如果未处理,进行第二步,快速转发流程,这快速转发流程中,返回一个对象来接受消息。如果返回的对象任然找不到IMP,同样奔溃。如果快速转发也未处理,
3.将进行第三步,慢速转发流程返回一个方法的签名,然后可以在forwardInvocation进行处理
4.如果慢速转发流程也任为处理,将doesNotRecognizeSelector打印报错,抛出异常。