[iOS知识简记]-Runtime-消息转发

深入探讨Objective-C中消息传递的内部流程,包括编译器如何将方法调用转换为objc_msgSend调用,以及当方法未找到时,如何进行消息转发。详细解释了动态方法解析、备援接收者和完整消息转发的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值