1 消息流程
1.1 消息传递
编译器把OC方法调用处理成objc_msgSend
调用。该函数汇编实现,要处理可变参数和返回值。
receiver根据isa指针所属类,遍历方法列表找到IMP指针跳转。找不到则根据superClass指针向上查找。
还找不到,就进入消息转发流程。
另外,objc_msgSend的实现里有方法缓存。每个类都有个缓存,缓存了自己以及父类的方法。优先查找缓存。
1.2 消息转发
1.2.1 第一阶段动态方法解析:
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector
例如:
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
1.2.2 第二阶段完整的消息转发:
(1)备援接收者(replacement receiver)
- (id)forwardingTargetForSelector:(SEL)selector
(2)完整的消息转发
- (void)forwardInvocation:(NSInvocation *)invocation
这里,要触发forwardInvocation:
方法,必须实现methodSignatureForSelector:
方法,或通过协议声明间接实现。
更具体的,方法缓存和方法列表里找不到方法时,进入消息转发_objc_msgForward
函数。
<objc/message.h>
_objc_msgForward
_objc_msgForward_stret (for methods with a struct return)
CoreFoundation在init时,调用了这句:
objc_setForwardHandler(___forwarding_prep_0___, ___forwarding_prep_1___);
这也是调用堆栈里看到下面函数的原因,都是CoreFoundation里实现的。
frame #1: 0x0000000105b93c34 CoreFoundation`___forwarding___ + 772
frame #2: 0x0000000105b95da8 CoreFoundation`__forwarding_prep_0___ + 120
__forwarding__
的实现类似如下:
void __forwarding__(BOOL isStret, id receiver, SEL sel, ...) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget) {
return objc_msgSend(forwardingTarget, sel, ...);
}
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature ...];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
// doesNotRecognizeSelector: should throw to avoid the SIGKILL.
[receiver doesNotRecognizeSelector:sel];
kill(getpid(), 9);
}
1.2.3 最后
- (void)doesNotRecognizeSelector:(SEL)aSelector;
参考
- http://www.arigrant.com/blog/2013/12/13/a-selector-left-unhandled
- http://www.arigrant.com/blog/2014/2/12/why-objcmsgsend-must-be-written-in-assembly
最后有个调用栈的细节
0 CoreFoundation 0x017e57e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x015658e5 objc_exception_throw + 44
2 CoreFoundation 0x01882843 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
3 CoreFoundation 0x017d5b0b ___forwarding___ + 1019
4 CoreFoundation 0x017d56ee _CF_forwarding_prep_0 + 14
5 UIKit 0x002987ea -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1097
6 UIKit 0x0028bada -[UIView(Hierarchy) addSubview:] + 56
7 Zoof 0x00002c3f main + 639
8 libdyld.dylib 0x01dca70d start + 1
看调用堆栈时,如上,_CF_forwarding_prep_0
前面是什么OC方法调用触发的,调用堆栈里看不到。原因是:_objc_msgForward
等函数一堆汇编实现,里面函数调用都是b/br
,不是bl/blr
,不会保存下一条指令到LR寄存器,形不成frame栈,所以调用堆栈回溯时无法从当前函数找到上一个函数里的LR,可能找到的是上上个函数里的LR,或更上层。例如下面汇编实现:
STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward
2 相关应用
2.1 JSPatch原理
对js要替换的OC方法,会将对应类的forwardInvocation:
方法的IMP
给HOOK替换成自己的JPForwardInvocation
函数,同时将对应方法的IMP替换为_objc_msgForward
,这样调用对应OC方法就统一进到JPForwardInvocation
函数里了。后续找到jsFunc,打包OC参数,调用JSValue的callWithArguments:
方法即可调用到js方法。
2.2 找不到方法crash的防御
NSObject类,hookforwardingTargetForSelector:
方法,返回一个自定义类对象,并实现了同名的空方法。代码类似如下:
#import <objc/runtime.h>
#import <objc/message.h>
static void* smartFunc(id sel , SEL fun ,...)
{
return NULL;
}
@interface TSmartFunction : NSObject
- (BOOL)addFunction:(SEL)sel;
@end
@implementation TSmartFunction
- (BOOL)addFunction:(SEL)sel {
return class_addMethod([TSmartFunction class], sel, (IMP)smartFunc, "");
}
@end
@interface NSObject(TForwardingTarget)
- (id)TForwardingTargetForSelector:(SEL)aSelector;
@end
@implementation NSObject(TForwardingTarget)
- (id)TForwardingTargetForSelector:(SEL)aSelector {
BOOL bResponse = [self respondsToSelector:aSelector];
NSMethodSignature* signatrue = [self methodSignatureForSelector:aSelector];
if (bResponse || signatrue) {
return [self TForwardingTargetForSelector:aSelector];
} else {
TSmartFunction* func = [[TSmartFunction alloc] init];
[func addFunction:aSelector];
return func;
}
}
@end
@interface NSObject(TSwizzle)
@end
@implementation NSObject(TSwizzle)
+ (BOOL)TSwizzleMethod:(SEL)origSel withMethod:(SEL)altSel error:(NSError**)error {
Method origMethod = class_getInstanceMethod(self, origSel);
if (!origMethod) {
return NO;
}
Method altMethod = class_getInstanceMethod(self, altSel);
if (!altMethod) {
return NO;
}
method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
return YES;
}
@end
@implementation TMsgForward
+ (void)load {
[[NSObject class] TSwizzleMethod:@selector(forwardingTargetForSelector:)
withMethod:@selector(TForwardingTargetForSelector:) error:nil];
}
@end
2.3 Proxy Objects
略。
3 附录代码
CoreFoundation里:
int ___CFInitialize() {
rax = *(int8_t *)___CFInitializing;
rax = rax | *(int8_t *)___CFInitialized;
if (rax != 0x0) goto loc_267d;
loc_1894:
*(int8_t *)___CFInitializing = 0x1;
*0x5decf8 = pthread_self();
*(int8_t *)___CFProphylacticAutofsAccess = 0x1;
rbx = 0x8;
do {
rdi = *(rbx + 0x5dc5f8);
if (rdi != 0x0) {
rax = getenv(rdi);
}
else {
rax = 0x0;
}
*(rbx + ___CFEnv) = rax;
rbx = rbx + 0x10;
} while (rbx != 0x198);
___exceptionInit();
objc_setForwardHandler(___forwarding_prep_0___, ___forwarding_prep_1___);
objc_setEnumerationMutationHandler(___NSFastEnumerationMutationHandler);
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
var_20 = rax;
var_30 = zero_extend_64(xmm7);
var_40 = zero_extend_64(xmm6);
var_50 = zero_extend_64(xmm5);
var_60 = zero_extend_64(xmm4);
var_70 = zero_extend_64(xmm3);
var_80 = zero_extend_64(xmm2);
var_90 = zero_extend_64(xmm1);
var_A0 = zero_extend_64(xmm0);
var_A8 = arg5;
var_B0 = arg4;
var_B8 = arg3;
var_C0 = arg2;
var_C8 = arg1;
rax = ____forwarding___(&var_D0, 0x0, arg2, arg3, arg4);
if (rax != 0x0) {
rax = *rax;
}
else {
rax = objc_msgSend(var_D0, var_C8);
}
return rax;
}
int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4) {
r8 = arg4;
rsi = arg1;
r12 = arg0;
rax = COND_BYTE_SET(NE);
r13 = *_objc_msgSend_stret;
if (rsi == 0x0) {
r13 = *_objc_msgSend;
}
var_40 = *(r12 + rax * 0x8 + 0x8);
rbx = *(r12 + rax * 0x8);
var_38 = rax * 0x8;
if ((rbx & 0x1) == 0x0) goto loc_7dc58;
loc_7dc37:
rax = rbx >> 0x1 & 0x7;
if (rax == 0x7) {
rax = (rbx >> 0x4 & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_7dfd5;
loc_7dc58:
var_58 = rsi;
var_48 = r12;
r12 = object_getClass(rbx);
r15 = class_getName(r12);
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_7dcdf;
loc_7dc8c:
rax = [rbx forwardingTargetForSelector:var_40];
if ((rax == 0x0) || (rax == rbx)) goto loc_7dcdf;
loc_7dca6:
r12 = var_48;
if ((rax & 0x1) == 0x0) goto loc_7dccf;
loc_7dcae:
rcx = rax >> 0x1 & 0x7;
if (rcx == 0x7) {
rcx = (rax >> 0x4 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_7dfd2;
loc_7dccf:
*(r12 + var_38) = rax;
r12 = 0x0;
goto loc_7e008;
loc_7e008:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r12;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_7dfd2:
rbx = rax;
goto loc_7dfd5;
loc_7dfd5:
var_30 = **___stack_chk_guard;
r14 = _getAtomTarget(rbx);
*(r12 + var_38) = r14;
___invoking___(r13, r12, r12, 0x400, 0x0, r9, stack[-104], var_58, var_50, var_48, var_40, var_38, var_30, var_28, stack[-40], stack[-32], stack[-24], stack[-16], stack[-8], stack[0]);
if (*r12 == r14) {
*r12 = rbx;
}
goto loc_7e008;
loc_7dcdf:
var_38 = rbx;
if (strncmp(r15, "_NSZombie_", 0xa) == 0x0) goto loc_7e047;
loc_7dcff:
r14 = var_38;
if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_7e094;
loc_7dd1d:
r12 = [r14 methodSignatureForSelector:var_40];
rbx = var_58;
if (r12 == 0x0) goto loc_7e0f3;
loc_7dd3d:
r15 = [r12 _frameDescriptor];
if (((*(int16_t *)(*r15 + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
rdx = sel_getName(var_40);
rcx = " not";
if ((*(int16_t *)(*r15 + 0x22) & 0xffff & 0x40) != 0x0) {
rcx = "";
}
r8 = " not";
if (rbx != 0x0) {
r8 = "";
}
_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[-104]);
}
rax = object_getClass(r14);
rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
var_50 = r15;
if (rax != 0x0) {
if (*0x5de480 != 0xffffffffffffffff) {
dispatch_once(0x5de480, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
r13 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *0x5de478;
r15 = &stack[-104] - (rsi + 0xf & 0xfffffffffffffff0);
__bzero(r15, rsi);
objc_constructInstance(*0x5de470, r15);
var_40 = r13;
[r15 _initWithMethodSignature:r12 frame:var_48 buffer:&stack[-8] - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
[r14 _forwardStackInvocation:r15];
rbx = 0x1;
}
else {
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) != 0x0) {
r15 = [NSInvocation _invocationWithMethodSignature:r12 frame:var_48];
[r14 forwardInvocation:r15];
}
else {
r15 = 0x0;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", r14, object_getClassName(r14), r8, r9, stack[-104]);
}
var_40 = 0x0;
rbx = 0x0;
}
if (*(int8_t *)&r15->_retainedArgs != 0x0) {
rax = *var_50;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rcx = *ivar_offset(_frame);
rdx = *(int32_t *)(rax + 0x1c);
rsi = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rsi + var_48 + rdx), *(rsi + rdx + *(r15 + rcx)), *(int32_t *)(*rax + 0x10));
}
}
r14 = [r12 methodReturnType];
rax = *(int8_t *)r14;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
r12 = r15->_retdata;
if (rbx != 0x0) {
r12 = [[NSData dataWithBytes:r12 length:var_40] bytes];
[r15 release];
rax = *(int8_t *)r14;
}
if (rax == 0x44) {
asm { fld tword [r12] };
}
}
else {
r12 = ____forwarding___.placeholder;
if (rbx != 0x0) {
r12 = ____forwarding___.placeholder;
[r15 release];
}
}
goto loc_7e008;
loc_7e0f3:
r15 = sel_getName(var_40);
r8 = sel_getUid(r15);
if (r8 != var_40) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_40, r15, r8, r9, stack[-104]);
}
if (class_respondsToSelector(object_getClass(var_38), @selector(doesNotRecognizeSelector:)) == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", var_38, object_getClassName(var_38), r8, r9, stack[-104]);
asm { ud2 };
rax = loc_7e172(rdi, rsi, rdx, rcx, r8);
}
else {
[var_38 doesNotRecognizeSelector:var_40];
asm { ud2 };
rax = loc_7e185();
}
return rax;
loc_7e094:
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_38, r14, object_getClassName(var_38), r9, stack[-104]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_38, r14, r8, r9, stack[-104]);
}
goto loc_7e0f3;
loc_7e047:
if (*(int8_t *)___CFOASafe != 0x0) {
___CFRecordAllocationEvent();
}
_CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p", r15 + 0xa, sel_getName(var_40), var_38, r9, stack[-104]);
asm { ud2 };
rax = loc_7e094(rdi, rsi, rdx, rcx, r8);
return rax;
}