注册

Objective-C 消息转发深度理解(2)


4.1.3 forwarding_prep_0伪代码分析

Hopper分析完毕后直接搜索forwarding_prep_0查看反汇编伪代码:

int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
//……
rax = ____forwarding___(&stack[0], 0x0);
if (rax != 0x0) {
rax = *rax;
}
else {
//arg0,arg1
rax = objc_msgSend(stack[0], stack[8]);
}
return rax;
}
  • 可以看到内部是对___forwarding___的调用。
  • ____forwarding___返回值不存在的时候调用的是objc_msgSend参数是arg0
    arg1

4.1.4 __forwarding__伪代码分析


点击进去查看___forwarding___的实现:


int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
r9 = arg5;
r8 = arg4;
rcx = arg3;
r13 = arg1;
r15 = arg0;
rax = COND_BYTE_SET(NE);
if (arg1 != 0x0) {
r12 = *_objc_msgSend_stret;
}
else {
r12 = *_objc_msgSend;
}
rbx = *(r15 + rax * 0x8);
rsi = *(r15 + rax * 0x8 + 0x8);
var_140 = rax * 0x8;
if (rbx >= 0x0) goto loc_115af7;

loc_115ac0:
//target pointer处理
rax = *_objc_debug_taggedpointer_obfuscator;
rax = *rax;
rcx = (rax ^ rbx) >> 0x3c & 0x7;
rax = ((rax ^ rbx) >> 0x34 & 0xff) + 0x8;
if (rcx != 0x7) {
rax = rcx;
}
if (rax == 0x0) goto loc_115ea6;

loc_115af7:
var_150 = r12;
var_138 = rsi;
var_148 = r15;
rax = object_getClass(rbx);
r15 = rax;
r12 = class_getName(rax);
//是否能响应 forwardingTargetForSelector,不能响应跳转 loc_115bab 否则继续执行 也就是forwardingTargetForSelector方法返回nil或者自身
if (class_respondsToSelector(r15, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_115bab;

loc_115b38:
//rax返回值
rax = [rbx forwardingTargetForSelector:var_138];
//返回值是否存在,返回值是否等于自己 是则跳转 loc_115bab
if ((rax == 0x0) || (rax == rbx)) goto loc_115bab;

loc_115b55:
if (rax >= 0x0) goto loc_115b91;

loc_115b5a:
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = *rcx;
rdx = (rcx ^ rax) >> 0x3c & 0x7;
rcx = ((rcx ^ rax) >> 0x34 & 0xff) + 0x8;
if (rdx != 0x7) {
rcx = rdx;
}
if (rcx == 0x0) goto loc_115e95;

loc_115b91:
*(var_148 + var_140) = rax;
r15 = 0x0;
goto loc_115ef1;

loc_115ef1:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
//返回 forwardingTargetForSelector 为消息的接收者
return rax;

loc_115e95:
rbx = rax;
r15 = var_148;
r12 = var_150;
goto loc_115ea6;

loc_115ea6:
if (dyld_program_sdk_at_least(0x7e30901ffffffff) != 0x0) goto loc_116040;

loc_115ebd:
r14 = _getAtomTarget(rbx);
*(r15 + var_140) = r14;
___invoking___(r12, r15, r15, 0x400, 0x0, r9, var_150, var_148, var_140, var_138, var_130, stack[-304], stack[-296], stack[-288], stack[-280], stack[-272], stack[-264], stack[-256], stack[-248], stack[-240]);
if (*r15 == r14) {
*r15 = rbx;
}
goto loc_115ef1;

loc_116040:
____forwarding___.cold.1();
rax = objc_opt_class(@class(NSInvocation));
*____forwarding___.invClass = rax;
rax = class_getInstanceSize(rax);
*____forwarding___.invClassSize = rax;
return rax;

loc_115bab:
var_140 = rbx;
//是否僵尸对象
if (strncmp(r12, "_NSZombie_", 0xa) == 0x0) goto loc_115f30;

loc_115bce:
r14 = var_140;
//是否能够响应 methodSignatureForSelector
if (class_respondsToSelector(r15, @selector(methodSignatureForSelector:)) == 0x0) goto loc_115f46;

loc_115bef:
rbx = var_138;
//调用
rax = [r14 methodSignatureForSelector:rbx];
if (rax == 0x0) goto loc_115fc1;

loc_115c0e:
r15 = rax;
rax = [rax _frameDescriptor];
r12 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != r13) {
rax = sel_getName(rbx);
rcx = "";
if ((*(int16_t *)(*r12 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
r8 = "";
if (r13 == 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, var_150);
}
//是否能够响应_forwardStackInvocation
if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_115d61;

loc_115c9a:
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
[NSInvocation requiredStackSizeForSignature:r15];
var_138 = r15;
rdx = *____forwarding___.invClassSize;
r13 = &var_150 - (rdx + 0xf & 0xfffffffffffffff0);
memset(r13, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r13);
var_150 = rax;
r15 = var_138;
[r13 _initWithMethodSignature:var_138 frame:var_148 buffer:&stack[-8] - (0xf + rax & 0xfffffffffffffff0) size:rax];
[var_140 _forwardStackInvocation:r13];
rbx = 0x1;
goto loc_115dce;

loc_115dce:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *r12;
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 = [r15 methodReturnType];
r14 = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (rbx != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_150] bytes];
[r13 release];
rax = *(int8_t *)r14;
}
if (rax == 0x44) {
asm { fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (rbx != 0x0) {
r15 = ____forwarding___.placeholder;
[r13 release];
}
}
goto loc_115ef1;

loc_115d61:
var_138 = r12;
r12 = r14;
//forwardInvocation的判断,如果没有实现直接跳转loc_115f8e
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_115f8e;

loc_115d8d:
rax = [NSInvocation _invocationWithMethodSignature:r15 frame:var_148];
r13 = rax;
[r12 forwardInvocation:rax];
var_150 = 0x0;
rbx = 0x0;
r12 = var_138;
goto loc_115dce;

loc_115f8e:
//错误日志
r14 = @selector(forwardInvocation:);
____forwarding___.cold.4(&var_130, r12);
rcx = r14;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
goto loc_115fba;

loc_115fba:
rbx = var_138;
goto loc_115fc1;

loc_115fc1:
rax = sel_getName(rbx);
r14 = rax;
rax = sel_getUid(rax);
if (rax != rbx) {
rcx = r14;
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_138, rcx, r8, r9, var_150);
}
if (class_respondsToSelector(object_getClass(var_140), @selector(doesNotRecognizeSelector:)) == 0x0) goto loc_116034;

loc_11601b:
[var_140 doesNotRecognizeSelector:rdx];
asm { ud2 };
rax = loc_116034(rdi, rsi, rdx, rcx, r8, r9);
return rax;

loc_116034:
____forwarding___.cold.3(var_140);
goto loc_116040;

loc_115f46:
rbx = class_getSuperclass(r15);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
rax = object_getClassName(var_140);
rcx = r14;
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_140, rcx, r8, r9, var_150);
}
else {
rcx = r14;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
}
goto loc_115fba;

loc_115f30:
r14 = @selector(forwardingTargetForSelector:);
____forwarding___.cold.2(var_140, r12, var_138, rcx, r8);
goto loc_115f46;
}

可以看到汇编伪代码的调用流程与看到的API调用流程差不多。


4.1.5 __forwarding__伪代码还原


还原主要逻辑伪代码如下:


#include <stdio.h>

@interface NSInvocation(additions)

+ (unsigned long long)requiredStackSizeForSignature:(NSMethodSignature *)signature;

-(id)_initWithMethodSignature:(id)arg1 frame:(void*)arg2 buffer:(void*)arg3 size:(unsigned long long)arg4;

+(id)_invocationWithMethodSignature:(id)arg1 frame:(void*)arg2;

@end


@interface NSObject(additions)

- (void)_forwardStackInvocation:(NSInvocation *)invocation;

@end


void forwardingTargetForSelector(Class cls, SEL sel, const char * className, id obj);
void methodSignatureForSelector(Class cls, id obj, SEL sel);
void doesNotRecognizeSelector(id obj, SEL sel);
void _forwardStackInvocation(id obj,NSMethodSignature *signature);
void forwardInvocation(id obj,NSMethodSignature *signature);

int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
SEL sel = NULL;
id obj;
Class cls = object_getClass(obj);
const char * className = class_getName(cls);
forwardingTargetForSelector(cls,sel,className,obj);
return 0;
}

void forwardingTargetForSelector(Class cls, SEL sel, const char * className, id obj) {
//是否能响应 forwardingTargetForSelector,不能响应跳转 loc_115bab 否则继续执行 也就是forwardingTargetForSelector方法返回nil或者自身
if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {
id obj = [cls forwardingTargetForSelector:sel];
if ((obj == nil) || (obj == cls)) {
methodSignatureForSelector(cls,obj,sel);
} else if (obj >= 0x0) {
//返回 forwardingTargetForSelector 备用消息接收者
// return obj;
} else {
//taggedpointer 处理
//返回NSInvocation size数据
}
} else {
//是否僵尸对象
if (strncmp(className, "_NSZombie_", 0xa)) {
methodSignatureForSelector(cls,obj,sel);
} else {
SEL currentSel = @selector(forwardingTargetForSelector:);
doesNotRecognizeSelector(obj,currentSel);
}
}
}


void methodSignatureForSelector(Class cls, id obj, SEL sel) {
if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {
NSMethodSignature *signature = [obj methodSignatureForSelector:sel];
if (signature) {
_forwardStackInvocation(obj,signature);
} else {
doesNotRecognizeSelector(obj,sel);
}
} else {
doesNotRecognizeSelector(obj,sel);
}
}

void _forwardStackInvocation(id obj,NSMethodSignature *signature) {
//是否能够响应_forwardStackInvocation
if (class_respondsToSelector(object_getClass(obj), @selector(_forwardStackInvocation:))) {
//执行dispatch_once相关逻辑
[NSInvocation requiredStackSizeForSignature:signature];
void *bytes;
// objc_constructInstance([NSInvocation class], bytes);
NSInvocation *invocation = [invocation _initWithMethodSignature:signature frame:NULL buffer:NULL size:bytes];
[obj _forwardStackInvocation:invocation];
const char * type = [signature methodReturnType];
//返回signature
} else {
forwardInvocation(obj,signature);
}
}

void forwardInvocation(id obj,NSMethodSignature *signature) {
//forwardInvocation的判断,如果没有实现直接跳转loc_115f8e
if (class_respondsToSelector(object_getClass(obj), @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:signature frame:NULL];
[obj forwardInvocation:invocation];
const char * type = [signature methodReturnType];
//返回signature
} else {
SEL sel = @selector(forwardInvocation:);
doesNotRecognizeSelector(obj,sel);
}
}

void doesNotRecognizeSelector(id obj, SEL sel) {
if (class_respondsToSelector(object_getClass(obj), @selector(doesNotRecognizeSelector:))) {
[obj doesNotRecognizeSelector:sel];
/*
____forwarding___.cold.1();
rax = objc_opt_class(@class(NSInvocation));
*____forwarding___.invClass = rax;
rax = class_getInstanceSize(rax);
*____forwarding___.invClassSize = rax;
return rax;
*/

} else {
/*
____forwarding___.cold.1();
rax = objc_opt_class(@class(NSInvocation));
*____forwarding___.invClass = rax;
rax = class_getInstanceSize(rax);
*____forwarding___.invClassSize = rax;
return rax;
*/

}
}
为了方便分析我这里class-dumpCoreFoundation头文件。手机端使用cycript进入SpringBoard应用,然后classdumpdyld导出CoreFoudation的头文件,最后拷贝到电脑端,具体操作如下:

cycript -p SpringBoard
@import net.limneos.classdumpdyld;
classdumpdyld.dumpBundle([NSBundle > bundleWithIdentifier:@"com.apple.CoreFoudation"]);
//输出导出头文件路径
@"Wrote all headers to /tmp/CoreFoundation"
//拷贝到电脑的相应目录
scp -r -P 12345 root@localhost:/tmp/CoreFoundation/ ./CoreFoundation_Headers/

伪代码流程图如下

69d5a968635c6acac471cda4a6804e31.png

反汇编流程与根据API分析的流程差不多。

  • forwardingTargetForSelector快速转发会对返回值会进行判断,如果是返回的自身或者nil直接进入下一流程(慢速转发)。
  • 如果返回taggedpointer有单独的处理。
  • methodSignatureForSelector慢速转发会先判断有没有实现_forwardStackInvocation(私有方法)。实现_forwardStackInvocation后不会再进入forwardInvocation流程,相当于_forwardStackInvocation是一个私有的前置条件。
  • methodSignatureForSelector如果没有返回签名信息不会继续进行下面的流程。
  • forwardInvocation没有实现就直接走到doesNotRecognizeSelector流程了。

4.2 流程分析


上篇文章分析resolveInstanceMethod在消息转发后还会调用一次resolveInstanceMethod(在日志文件中看到是在doesNotRecognizeSelector之前,methodSignatureForSelector之后)。那么实现对应的方法做下验证:

HPObject resolveInstanceMethod: HPObject-0x100008290-instanceMethod
-[HPObject forwardingTargetForSelector:] - instanceMethod
-[HPObject methodSignatureForSelector:] - instanceMethod
HPObject resolveInstanceMethod: HPObject-0x100008290-instanceMethod
-[HPObject doesNotRecognizeSelector:] - instanceMethod

证实是在methodSignatureForSelector之后,doesNotRecognizeSelector之前有一次进行了方法动态决议。那么为什么要这么处理呢?因为消息转发的过程中可能已经加入了对应的sel-imp,所以再给一次机会进行方法动态决议。这次决议后不会再进行消息转发。

但是在反汇编分析中并没有明确的再次进行动态方法决议的逻辑。


4.2.1 反汇编以及源码探究

那么在第二次调用resolveInstanceMethod前打断点查看下堆栈信息
macOS堆栈如下:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
frame #0: 0x0000000100300f53 libobjc.A.dylib`resolveMethod_locked(inst=0x0000000000000000, sel="instanceMethod", cls=HPObject, behavior=0) at objc-runtime-new.mm:6339:13
frame #1: 0x00000001002ffbd5 libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000000000000, sel="instanceMethod", cls=HPObject, behavior=0) at objc-runtime-new.mm:6601:16
frame #2: 0x00000001002d6df9 libobjc.A.dylib`class_getInstanceMethod(cls=HPObject, sel="instanceMethod") at objc-runtime-new.mm:6210:5
* frame #3: 0x00007fff2e33fc68 CoreFoundation`__methodDescriptionForSelector + 282
frame #4: 0x00007fff2e35b57c CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
frame #5: 0x0000000100003a21 HPObjcTest`-[HPObject methodSignatureForSelector:](self=0x0000000100706a30, _cmd="methodSignatureForSelector:", aSelector="instanceMethod") at HPObject.m:29:12 [opt]
frame #6: 0x00007fff2e327fc0 CoreFoundation`___forwarding___ + 408
frame #7: 0x00007fff2e327d98 CoreFoundation`__forwarding_prep_0___ + 120
frame #8: 0x0000000100003c79 HPObjcTest`main + 153
frame #9: 0x00007fff683fecc9 libdyld.dylib`start + 1
frame #10: 0x00007fff683fecc9 libdyld.dylib`start + 1
可以看到methodSignatureForSelector调用后进入了__methodDescriptionForSelector随后调用了class_getInstanceMethod。查看汇编确实在__methodDescriptionForSelector中调用了class_getInstanceMethod

66e076a3370fd1a9d677b00a4c4701cb.png
那么系统是如何从methodSignatureForSelector调用到__methodDescriptionForSelector的?
当前的methodSignatureForSelector的实现是:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}

如果改为返回nil呢?

HPObject resolveInstanceMethod: HPObject-0x100008288-instanceMethod
-[HPObject forwardingTargetForSelector:] - instanceMethod
-[HPObject methodSignatureForSelector:] - instanceMethod
-[HPObject doesNotRecognizeSelector:] - instanceMethod
这个时候发现没有第二次调用了,那也就是说核心逻辑在[super methodSignatureForSelector:aSelector]的实现中。
查看源码:

// 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中,直接搜索methodSignatureForSelector的反汇编实现:


/* @class NSObject */
-(void *)methodSignatureForSelector:(void *)arg2 {
rdx = arg2;
if ((rdx != 0x0) && (___methodDescriptionForSelector(objc_opt_class(), rdx) != 0x0)) {
rax = [NSMethodSignature signatureWithObjCTypes:rdx];
}
else {
rax = 0x0;
}
return rax;
}
  • sel不为nil的时候会调用___methodDescriptionForSelector。这样就串联起来了。

class_getInstanceMethod的实现如下:


Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
return _class_getMethod(cls, sel);
}

4.2.2 断点调试验证

既然上面已经清楚了resolveInstanceMethod第二次调用是methodSignatureForSelector之后调用的,那么不妨打个符号断点跟踪下methodSignatureForSelector:

2def905128603c5919102f72813a9b33.png


显然只需要关心调用的函数以及跳转逻辑。

跟进去__methodDescriptionForSelector

7e6130c30d14fe32e13f64162aa29c5c.pngedfc8c30d6b41aa6b53c41b214418450.png56002d614a39764492fca98f04bd8c61.png

这样通过断点也从methodSignatureForSelector定位到了resolveInstanceMethod

结论:

  • 实例方法 - methodSignatureForSelector-> ___methodDescriptionForSelector -> class_getInstanceMethod-> lookUpImpOrForward->resolveMethod_locked-> resolveInstanceMethod
  • 类方法 + methodSignatureForSelector -> ___methodDescriptionForSelector(传递的是元类) -> class_getInstanceMethod- lookUpImpOrForward->resolveMethod_locked-> resolveClassMethod

⚠️总结:

  1. 在methodSignatureForSelector内部调用了class_getInstanceMethod进行lookUpImpOrForward随后进入方法动态决议。这也就是class_getInstanceMethod调用第二次的来源入口。
  2. methodSignatureForSelector后第二次调用class_getInstanceMethod是为了再给一次进行消息查找和动态决议流程,因为消息转发流程过程中有可能实现了对应的sel-imp

动态方法决议以及消息转发整个流程如下:

f5fff06f5392a748645647da1817448e.png


五、消息发送查找总结

前面已经通过objc_msgSend分析整个消息缓存、查找、决议、转发整个流程。

  • 通过CacheLookup进行消息快速查找
    • 整个cache查找过程相当于是insert过程的逆过程,找到imp就解码跳转,否则进入慢速查找流程。
  • 通过lookUpImpOrForward进行消息慢速查找
    • 慢速查找涉及到递归查找,查找过程分为二分查找/循环查找。
    • 找到imp直接跳转,否则查找父类缓存。父类缓存依然找不到则在父类方法列表中查找,直到找到nil。查找到父类方法/缓存方法直接插入自己的缓存中。
  • imp找不到的时候进行方法动态决议
    • 当快速和慢速消息查找都没有找到imp的时候就进入了方法动态决议流程,在这个流程中主要是添加imp后再次进行快速慢速消息查找。
  • 之后进入本篇的消息转发流程,消息转发分为快速以及慢速。
    • 在动态方法决议没有返回imp的时候就进入到了消息转发阶段。
    • 快速消息转发提供一个备用消息接收者,返回值不能为nil与自身。这个过程不能修改参数和返回值。
    • 慢速消息转发需要提供消息签名,只要提供有效签名就可以解决消息发送错误问题。同时要实现forwardInvocation配合处理消息。
    • forwardInvocation配合处理消息,使target生效起作用。
    • 在慢速消息转发后系统会再进行一次慢速消息查找流程。这次不会再进行消息转发。
    • 消息转发仍然没有解决问题会进入doesNotRecognizeSelector,这个方法并不能处理错误,实现它仍然会报错。只是能拿到错误信息而已。

⚠️慢速消息转发后系统仍然给了一次机会进行 慢速消息查找!!!(并不仅仅是动态方法决议)。

整个流程如下:


b8cebfc4b3d572cb3d49b324a3b698ca.png




作者:HotPotCat
链接:https://www.jianshu.com/p/f5bf0549b1f5







0 个评论

要回复文章请先登录注册