美文网首页
runtime & 消息发送流程(重磅)

runtime & 消息发送流程(重磅)

作者: f8d1cf28626a | 来源:发表于2022-07-17 02:44 被阅读0次

    runtime & 消息发送流程 (重磅)

    • 本文主要关于imp的查找流程,在这之前先走个铺垫

    类的走位链

    对象 的 isa 指向 类(也可称为类对象)
    类 的 isa 指向 元类
    元类 的 isa 指向 根元类,即NSObject
    根元类 的 isa 指向 它自己
    

    首先拓展一下类的结构

    objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图所示

    bits 的结构

    class_ro_t 与 class_rw_t 的关系

    通过以上class_rw_t注解中提到的文章,我们对两者有个大体的概念,两者都定义了方法列表,协议列表,属性列表等, 我们来看一下为什么要这么做,这么做有什么意义.

    struct class_rw_t {             // 重点
    
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;       // 重点
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
        uint32_t index;
    #endif
    
        void setFlags(uint32_t set) 
        {
            OSAtomicOr32Barrier(set, &flags);
        }
    
        void clearFlags(uint32_t clear) 
        {
            OSAtomicXor32Barrier(clear, &flags);
        }
    
        // set and clear must not overlap
        void changeFlags(uint32_t set, uint32_t clear) 
        {
            assert((set & clear) == 0);
    
            uint32_t oldf, newf;
            do {
                oldf = flags;
                newf = (oldf | set) & ~clear;
            } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
        }
    };
    
    • class_rw_t 中包含 class_ro_t 并且为 const 类型(不可被修改)

    class_ro_t 结构

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
        _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                return _swiftMetadataInitializer_NEVER_USE[0];
            } else {
                return nil;
            }
        }
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    
        class_ro_t *duplicate() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
                return ro;
            } else {
                size_t size = sizeof(*this);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                return ro;
            }
        }
    };
    
    

    对比class_rw_t我们发现, class_ro_t中多了 const uint8_t * ivarLayout; const char * name; const ivar_list_t * ivars; const uint8_t * weakIvarLayout;

    • objc_class初始化的过程中有一个realizeClassWithoutSwift方法代码表示除了他们的关系, 源码如下
    static Class realizeClassWithoutSwift(Class cls)
    {
        runtimeLock.assertLocked();
    
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
    
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        assert(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
    
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            rw->ro = ro;
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
        isMeta = ro->flags & RO_META;
    
        rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
    
    
        // Choose an index for this class.
        // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
        cls->chooseClassArrayIndex();
    
        if (PrintConnecting) {
            _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                         cls->nameForLogging(), isMeta ? " (meta)" : "", 
                         (void*)cls, ro, cls->classArrayIndex(),
                         cls->isSwiftStable() ? "(swift)" : "",
                         cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
        }
    
        // Realize superclass and metaclass, if they aren't already.
        // This needs to be done after RW_REALIZED is set above, for root classes.
        // This needs to be done after class index is chosen, for root metaclasses.
        // This assumes that none of those classes have Swift contents,
        //   or that Swift's initializers have already been called.
        //   fixme that assumption will be wrong if we add support
        //   for ObjC subclasses of Swift classes.
        supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    
    #if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
    
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
                 0 == strcmp(ro->name, "OS_object")) 
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&  
                 supercls->instancesRequireRawIsa()) 
        {
            // This is also propagated by addSubclass() 
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate 
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }
        
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsa(rawIsaIsInherited);
        }
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        // Update superclass and metaclass in case of remapping
        cls->superclass = supercls;
        cls->initClassIsa(metacls);
    
        // Reconcile instance variable offsets / layout.
        // This may reallocate class_ro_t, updating our ro variable.
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
        // Set fastInstanceSize if it wasn't set already.
        cls->setInstanceSize(ro->instanceSize);
    
        // Copy some flags from ro to rw
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
        
        // Propagate the associated objects forbidden flag from ro or from
        // the superclass.
        if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
    
        // Connect this class to its superclass's subclass lists
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // Attach categories
        methodizeClass(cls);
    
        return cls;
    }
    

    抽取其中部分

        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            rw->ro = ro;
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
    
    • 1.bits中一开始存储的class_ro_t
    • 2.创建class_rw_t ,并且把class_ro_t赋值给class_rw_t,最后把class_rw_t赋值给bits
    • 3.通过源码分析class_ro_t的内容是为const修饰,so 只读
    • 4.至于在运行时还可以给class添加方法等操作,其原理是通过class_rw_t实现的(讲到这里就是前文为什么要做铺垫的原因,为什么可以动态的添加方法和修改方法的imp,这些都和imp的查找流程息息相关)
    • 5.realizeClass方法中class_rw_t & class_ro_t的创建和赋值动作都是给runtime动态化所做的准备.

    那么。。问题来了 rwe 呢??

    rwe的一切都来自于extAllocIfNeeded()的创建,

    ro属于cleanmemory,在编译即确定的内存空间,只读,可以从内存中移除,需要时再次从文件中加载(多么完美的设计啊)
    rw属于dirtymemory,rw是运行时结构可读可写,可以向类中添加属性、方法等,在运行时会改变的内存;
    rwe相当于类的额外信息(动态拓展的时候生成),在实际使用过程中,只有很少的类会真正的改变他们的内容,所rwe的存在是为了避免资源的消耗
    
    • 1.一旦添加了分类救护创建就必然会有rwe,并且把ro中的methodlist,propertylist,protocollist,classMthods附加到rwe
    • 2.addMethods_finish, _class_addProperty,class_addProtocol,objc_duplicateClass,class_setVersion 这些动作都灰生成rwe(所以分类的开销很大)
    • 经过优化后 class_rw_t 只剩下methods、properties、protocols,demangled,而不是添加一个分类又拷贝一份ro数据

    进入主题

    对象调用方法其实是发送消息的过程,接受者用于定位当前的类,消息主体的sel用于查找对应的方法名,在找到imp

    我们在调用一个方法的时候,下层的实现实际上是objc_msgSend(self,_cmd);

    • 先找cache_t,再找methodlist

      • 步骤一 汇编快速查找流程 CacheLookUp(.macro 是一个汇编语言的宏定义)
        • 定位到当前的bucket查找方法,如果找到则缓存命中CacheHit,如果没有找到则往前便遍历bucket,便利到最前面韩式没有找到则移动指向到最后一个继续往前遍历查找,当遇到第一次查找的bucket时,停止快速查找 _msgUncached
    • 步骤二 C++慢速查找流程 MethodTableLookup _lookUpImpOrForward (慢速查找或转发)

    • 1.先查找当前类的metholist(二分),如果查找到 goto Done,并且添加到Cache_t(log_and_fill_cache())->cache_fill()

    • 2.如果当前类没有找到,会到父类的Cache中(汇编)快速查找CacheLookUp,如果没有找到继续查找父类的methodlist(二分),如果所有的父类中还有找到,那么imp = forward_imp,执行决议LOOK_RESOLVER

    LOOK_RESOLVER 动态方法决议(仅来一次,不会每个月都来一次)

    if (slowpath(behavior & LOOK_RESOLVER)){
    
       behavior ^= LOOK_RESOLVER; // 仅走一次
    
       return resolveMethod_locked(inst,sel,cls, behavior);
    }
    

    resolveMethod_locked 它做了什么??

    static NEVER_INLINE IMP resolveMethod_locked(id inst,SEL sel,Class cls,int behavior){
        rutimeLock.assertLocked();
        ASSERT(cls->isRealized());
        runtimeLock.unlock();
        
        if (!cls -> isMetaClass()){
            resolveInstanceMethod(inst,sel,cls);
        }
        else{
            resolveInstanceMethod(inst,sel,cls);
            if (!lookUpImpOrNil(inst,sel,cls)){
                resolveInstanceMethod(inst,sel,cls);
            }
        }
        
        // 再次进入慢速查找
        return lookUpImpOrForward(inst,sel,cls,behavior | LOOKUP_CACHE);
    }
    
    static void resolveInstanceMethod(id inst,SEL oldSel,Class cls){
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        
        // 这里的意思是可以在当前类重写resolveInstanceMethod方法(动态添加一次方法的机会)
        SEL resolve_sel = @selector(resolveInstanceMethod:);
        
        if (!lookUpImpOrNil(inst,resolve_sel,cls->ISA())){
            // Resolver not implemented.
            return;
        }
        
        BOOL (*msg)(class,SEL,SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls,resolve_sel,oldSel);
        
        // 如果决议一次之后没有找到,继续lookUpImpOrForward(二分遍历当前类和所有父类),最后执行消息转发
        IMP imp = lookUpImpOrNil(inst,oldSel,cls);
        
        if (resolved && PrintResolveing){
            if (imp){
                _objc_inform("RESOLVE: method %c[%s %s]""bynamically resolveed to %p",
                             cls->isMetaClass()?'+':'-',
                             cls->nameForLogging(),
                             sel_getName(oldSel),
                             imp);
            }
            else {
                _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                             ",but no new implementation of %c[%s %s] was found",
                             cls->nameForLogging(),sel_getName(oldSel),
                             cls->isMetaClass()?'+':'-',
                             cls->nameForLogging(),sel_getName(oldSel));
            }
        }
        
    }
    

    resolveMethod_locked 有什么用??
    (可以在当前类重写resolveInstanceMethod方法(动态添加一次方法的机会))

    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(cxxxx)){
            // 添加方法
            IMP imp = class_getMethodImplementation(self,@selector(kxxxx));
            Method met = class_getInstanceMethod(self,@selector(kxxxx));
            const char *type = method_getTypeEncoding(mit);
            BOOL hasWhy = class_addMethod(self,sel,type);
        }
        
        return [super resolveInstanceMethod:sel];
    }
    

    重点 NSObject中的决议(不建议这么处理)

    #import "NSObject+RC.h"
    #import <objc/message.h>
    
    @implementation NSObject (RC)
    // 实例方法 动态决议
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        
        const char *name = object_getClassName(self);
        NSString *hasName = [NSString stringWithUTF8String:name];
        // 理解面向切面
        if (![hasName hasPrefix:@"RC"])return NO; // 只处理自己定义的方法
        
        // 对象方法处理
        const char * cx = "cxxxx";
    
        if (sel_getName(sel) == cx){
            // 添加方法
            IMP imp = class_getMethodImplementation(self,@selector(kxxxx));
            Method met = class_getInstanceMethod(self,@selector(kxxxx));
            const char *type = method_getTypeEncoding(met);
            BOOL hasWhy = class_addMethod(self, sel, imp, type);
            return NO;
        }
    
        // 类方法处理
        const char * sx = "sxxxx";
        
        if (sel_getName(sel) == sx){
            // 添加方法
            Class currenrClass = objc_getMetaClass("RCPerson");
            IMP imp = class_getMethodImplementation(currenrClass,@selector(zxxxx));
            Method met = class_getInstanceMethod(currenrClass,@selector(zxxxx));
            const char *type = method_getTypeEncoding(met);
            BOOL hasWhy = class_addMethod(currenrClass, sel, imp, type);
            return  NO;
        }
        
        return NO;
    }
    

    如果没有使用resolveInstanceMethod动态决议呢??(此时imp等于forawrd_imp)

    • 当imp == forward_imp 就会走消息转发流程

    来到 消息转发流程

    forward_imp 的定义:const forward_imp = (IMP)_objc_msgForward_impcache;

    _objc_msgForward_impcache (没有开源,所以只能全局搜索看汇编)找到STATIC_ENTRY __objc_msg_Forward_impcache

    STATIC_ENTRY __objc_msg_Forward_impcache
    
    b __objc_msgForward
    
    END_ENTRY __objc_msg_Forward_impcache
    
    ENTRY __objc_msgForward
    
    adrp x17, __objc_forward_handler@PAGE
    
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    
    TailCallFunctionPointer x17
    
    ENTRY __objc_msgForward
    

    汇编描述 :主要执行在p17证明是指向x17,即搜索 _objc_forward_handler

    • 默认的消息转发处理objc_defaultForwardHandler

    查找结果:void * _objc_forward_handler = (void*)objc_defaultForwardHandler;

    objc_defaultForwardHandler 是一个Cxx函数 (这是我们经常见到而且无比熟悉的)

    __attribute__((noreturn,cold))void objc_defaultForwardHandler(id self,SEL sel){
    
        _objc_fatal("%c[%s %s]:unrecognized selector sent to instance %p"
                    "(no message forward handler is installed)",
                    class_isMetaClass(object_getClass(self)) ? '+' : '-',
                    object_getClassName(self),
                    sel_getName(sel),
                    self);
    }
    
    • 自定义消息转发处理

    引入 instrumentObjcMessageSnds 打印消息发送流程(添加到你想要的位置)

    
    extern void instrumentObjcMessageSnds(BOOL flag);
    // 这种写法是cxx,mian.m 改成 main.mm
    
    struct RCTest{
        void *met;
        RCTest(){
            instrumentObjcMessageSnds(YES);
        }
        ~RCTest(){
            instrumentObjcMessageSnds(NO);
        }
    };
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            
            RCTest();
            NSLog(@"main 函数\n");
            
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    消息机制建议在这里处理

    • 1.快速转发 forwardingTargetForSelector:
    #import "RPerson.h"
    #import "RCxxxxx.h"
    #import <objc/message.h>
    
    @implementation RPerson
    
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        
        const char *sayBye = "sayBye";
        if (sel_getName(aSelector) == sayBye){
            // 背锅侠
            return [RCxxxxx alloc];
            
            // 也可以在这里动态添加方法xxxxx
        }
        
        return [super forwardingTargetForSelector:aSelector];
    }
    
    @end
    
    • 2.慢速转发 methodSignatureForSelector:(须知慢速转发是在快速转发没有响应的情况下才会执行两走其一)
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        // 1.
        NSMethodSignature *ms = [NSMethodSignature methodSignatureForSelector:@selector(hookHit)];
        return ms;
    }
    
    注意:在methodSignatureForSelector 和 forwardInvocation方法之间还有一次动态方法决议,即苹果再次给的一个机会
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        
        // 继续找背锅侠
        anInvocation.target = (id)[[RCxxxxx alloc] init];
        [anInvocation invoke];
    }
    
    - (void)hookHit{
        
    }
    

    总结1 消息流程 CacheLookUp --> _lookUpImpOrForward(并且cache_fill()) --> LOOK_RESOLVER --> _lookUpImpOrForward --> 消息转发流程(imp = forward_imp)-->快速转发(forwardingTargetForSelector) 或 慢速转发(methodSignatureForSelector & forwardInvocation)

    • 注意:在methodSignatureForSelector 和 forwardInvocation方法之间还有一次动态方法决议LOOK_RESOLVER,即苹果再次给的一个机会

    总结2 实践中主要利用到 消息的处理机制动态方法决议重写消息转发

    技术实践:没有开元的文件,就使反汇编(需要获取本地库文件->断住断点(image list)),然后找到一个黑不溜秋的东西,拖入反汇编的APP。

    • 前往路径快捷键 command + shift + G

    这边文章掌握透彻将披荆斩棘,美女无数我只取一瓢。。。

    相关文章

      网友评论

          本文标题:runtime & 消息发送流程(重磅)

          本文链接:https://www.haomeiwen.com/subject/ruwkirtx.html