一、instrumentObjcMessageSends 系统日志探索
1. 它是什么?
instrumentObjcMessageSends是系统的日志,是苹果的私有API,我们可以控制log开关,打印日志信息。
2. 它的由来?
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);
}
3. 日志位置
快捷键: command + shift + g
文件位置为: /tmp/msgSend-***
4. 代码使用
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(true);
Person *person = [Person alloc];
[person sayHello];
instrumentObjcMessageSends(false);
NSLog(@"Hello, World!");
}
return 0;
}
二、消息转发流程
imp查询流程总结
为了查找到 imp, 总结下一共经过的流程,防止思路跟不上。
0. 寻找 sel对应的 imp
1. objc_msgSend cache 快速查找
2. 慢速方法查找 methlist
3. 动态方法决议 resolveInstanceMethod
4. 消息快速转发(让别人做 ,一般可以定义一个专门的对象,进行消息处理) forwardingTargetForSelector
5. 消息慢速转发 , (比快速转发更加灵活) methodSignatureForSelector 和 forwardInvocation 配对出现。
1.动态方法决议后续转发流程
上面通过系统的日志,故意让方法找不到,让系统崩溃,然后打开日志看到整个的方法转发过程,里面有好几个方法。
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject methodSignatureForSelector:
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject doesNotRecognizeSelector:
- LGPerson NSObject doesNotRecognizeSelector:
第一个方法resolveInstanceMethod这个是我们上篇博客介绍的,方法的动态决议。 重点看后面的方法。
2. forwardingTargetForSelector 快速转发流程
-(id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%@ : %s",self,__func__);
return [LGTeacher alloc];
}
3. methodSignatureForSelector慢速转发流程
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%@ : %s",self,__func__);
if (aSelector == @selector(say666)) {
Method method = class_getInstanceMethod([LGTeacher class], aSelector);
const char *type = method_getTypeEncoding(method);
return [NSMethodSignature signatureWithObjCTypes:type];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = anInvocation.selector ;
LGTeacher *teacher = [[LGTeacher alloc] init];
if ( [self respondsToSelector:selector]) {
[anInvocation invoke];
} else if ([teacher respondsToSelector:selector]) {
[anInvocation invokeWithTarget:teacher];
} else {
NSLog(@"----%@------%@--", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
}
4.hoper反编译cf
-
lldb命令: bt //打印堆栈信息。 查看下崩溃的堆栈里面有什么信息。 上面的图片,查到崩溃信息,发现最终定位到 CoreFoundation 中的 forwarding,那他最后到底做了什么呢, 只能通过查看CoreFoundation 反编译下,才能揭晓它到底做了什么? -
准备: 需要下载 hopper 软件。 下载hopper CoreFoundation 动态库。
把 CoreFoundation拖入Hopper 查看下 ,然后搜索
___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, ^ { } });
}
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;
}
说明: 是不是太惊喜了。 CoreFoundation底层是按照这个顺序依次进行调用的。
- 源码中依次为快速转发方法 forwardingTargetForSelector:(SEL)aSelector
- 慢速转发方法 methodSignatureForSelector 与forwardInvocation
- doesNotRecognizeSelector 系统发现你没有做任何补救处理,就会调用这个方法。
三、resolveInstanceMethod 为啥调用了2次。
- 第一次跑进来是imp查找在快速查找与慢速查找后,还没有找到,走进入动态协议流程, 进入 resolveMethod_locked方法 ,调用第一次。
- 第二次跑到这个方法,首先要看下崩溃的时候堆栈信息,看看系统做了什么 ,通过bt看看堆栈信息
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff204d792e libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x0000000100461e79 libsystem_pthread.dylib`pthread_kill + 263
frame #2: 0x00007fff2045b411 libsystem_c.dylib`abort + 120
frame #3: 0x00007fff204c9ef2 libc++abi.dylib`abort_message + 241
frame #4: 0x00007fff204bb5fd libc++abi.dylib`demangling_terminate_handler() + 266
* frame #5: 0x00000001002f7fd2 libobjc.A.dylib`_objc_terminate() at objc-exception.mm:701:13 [opt]
frame #6: 0x00007fff204c9307 libc++abi.dylib`std::__terminate(void (*)()) + 8
frame #7: 0x00007fff204cbbeb libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
frame #8: 0x00007fff204cbbb2 libc++abi.dylib`__cxa_throw + 116
frame #9: 0x00000001002f7acf libobjc.A.dylib`objc_exception_throw(obj=<unavailable>) at objc-exception.mm:591:5 [opt]
frame #10: 0x00007fff206fc38d CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
frame #11: 0x00007fff205e190b CoreFoundation`___forwarding___ + 1448
frame #12: 0x00007fff205e12d8 CoreFoundation`_CF_forwarding_prep_0 + 120
frame #13: 0x0000000100003a9a KCObjcBuild`main(argc=<unavailable>, argv=<unavailable>) at main.m:26:9 [opt]
frame #14: 0x00007fff20521f5d libdyld.dylib`start + 1
这个地方发现了CoreFoundation调用了 ___forwarding___这个方法。 结合上面___forwarding___的反汇编,得出在慢速转发流程中调用了CoreFoundation框架中的methodSignatureForSelector后,会再次进入动态决议,所以这个地方调用了两次 resolveMethod_locked
总结
1.快捷键
lldb命令: bt
command + shift + 0 : 打开苹果官方开发文档。
2. IMP查找流程总结
-
快速查找流程 - 在类的缓存cache中查找指定方法的实现 -
慢速查找流程 - 在类的方法列表中查找,如果还是没找到,则去父类的缓存和方法列表中查找,没有找到,提柜父类的父类直到 nil. -
动态方法决议 - 第一次补救程序崩溃的机会,解决方法为增加resolveInstanceMethod(实例方法调用) resolveClassMethod (类方法调用) -
消息转发 - 第二次补救崩溃机会 ,快速转发, 增加 forwardingTargetForSelector -
消息转发 慢速转发 - 第三次补救崩溃机会 增加方法为 一对, 分别是 methodSignatureForSelector , forwardInvocation。
如果三次机会都没有把握住,则程序直接报错崩溃。
|