美文网首页
14.iOS底层学习之read_images分析

14.iOS底层学习之read_images分析

作者: 牛牛大王奥利给 | 来源:发表于2021-10-13 16:43 被阅读0次

    上一篇文章具体的查看了load_images内部的实现,以及初步了解到map_images参数内部的实现,由于map_images内容过多,在这篇文章中单独拆分出来分析下map_images的内部核心函数read_images,在研究read_images之前我们先来看一下_objc_init,因为_dyld_objc_notify_register是由_objc_init调起的。

    _objc_init

    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        // fixme defer initialization until an objc-using image is found?
    //环境变量的初始化
        environ_init();
    //线程创建
        tls_init();
    //*静态初始化
    //*运行C++静态构造函数。
    //*libc在dyld调用静态构造函数之前调用_objc_init(),
    //*所以我们必须自己做。
        static_init();
    //两张表的初始化
        runtime_init();
    //*异常初始化
    //*初始化libobjc的异常处理系统。
    //*由map_images()调用。
        exception_init();
    #if __OBJC2__
    //cache的初始化
        cache_t::init();
    #endif
        _imp_implementationWithBlock_init();
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    

    read_images

    我们还是通过源码来看,我们通过研究发现_dyld_objc_notify_register是连接objc和dyld的桥梁,在objc中的_dyld_objc_notify_register有参数map_images,实现如下:

    map_images
    map_images实现
    发现map_images内的核心实现是map_images_nolock
    void 
    map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                      const struct mach_header * const mhdrs[])
    {
    ......
        if (hCount > 0) {
            _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
        }
    ......
    }
    

    省略了部分代码,关于这段代码,我们主要去关注下镜像文件相关的,这个方法的前面做了一些init相关的操作,和images有关的方法是_read_images

    _read_images

    这段代码挺长的,包含非常多的信息,挺重要的,我就不省略了都粘贴上来了。

    /***********************************************************************
    * _read_images
    * Perform initial processing of the headers in the linked 
    * list beginning with headerList. 
    *
    * Called by: map_images_nolock
    *
    * Locking: runtimeLock acquired by map_images
    **********************************************************************/
    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    {
        header_info *hi;
        uint32_t hIndex;
        size_t count;
        size_t I;
        Class *resolvedFutureClasses = nil;
        size_t resolvedFutureClassCount = 0;
        static bool doneOnce;
        bool launchTime = NO;
        TimeLogger ts(PrintImageTimes);
    
        runtimeLock.assertLocked();
    
    #define EACH_HEADER \
        hIndex = 0;         \
        hIndex < hCount && (hi = hList[hIndex]); \
        hIndex++
    
        if (!doneOnce) {
            doneOnce = YES;
            launchTime = YES;
    
    #if SUPPORT_NONPOINTER_ISA
            // Disable non-pointer isa under some conditions.
    
    # if SUPPORT_INDEXED_ISA
            // Disable nonpointer isa if any image contains old Swift code
            for (EACH_HEADER) {
                if (hi->info()->containsSwift()  &&
                    hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
                {
                    DisableNonpointerIsa = true;
                    if (PrintRawIsa) {
                        _objc_inform("RAW ISA: disabling non-pointer isa because "
                                     "the app or a framework contains Swift code "
                                     "older than Swift 3.0");
                    }
                    break;
                }
            }
    # endif
    
    # if TARGET_OS_OSX
            // Disable non-pointer isa if the app is too old
            // (linked before OS X 10.11)
    //        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
    //            DisableNonpointerIsa = true;
    //            if (PrintRawIsa) {
    //                _objc_inform("RAW ISA: disabling non-pointer isa because "
    //                             "the app is too old.");
    //            }
    //        }
    
            // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
            // New apps that load old extensions may need this.
            for (EACH_HEADER) {
                if (hi->mhdr()->filetype != MH_EXECUTE) continue;
                unsigned long size;
                if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
                    DisableNonpointerIsa = true;
                    if (PrintRawIsa) {
                        _objc_inform("RAW ISA: disabling non-pointer isa because "
                                     "the app has a __DATA,__objc_rawisa section");
                    }
                }
                break;  // assume only one MH_EXECUTE image
            }
    # endif
    
    #endif
    
            if (DisableTaggedPointers) {
                disableTaggedPointers();
            }
            
            initializeTaggedPointerObfuscator();
    
            if (PrintConnecting) {
                _objc_inform("CLASS: found %d classes during launch", totalClasses);
            }
    
            // namedClasses
            // Preoptimized classes don't go in this table.
            // 4/3 is NXMapTable's load factor
            int namedClassesSize = 
                (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
            gdb_objc_realized_classes =
                NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    
            ts.log("IMAGE TIMES: first time tasks");
        }
    
        // Fix up @selector references
        static size_t UnfixedSelectors;
        {
            mutex_locker_t lock(selLock);
            for (EACH_HEADER) {
                if (hi->hasPreoptimizedSelectors()) continue;
    
                bool isBundle = hi->isBundle();
                SEL *sels = _getObjc2SelectorRefs(hi, &count);
                UnfixedSelectors += count;
                for (i = 0; i < count; i++) {
                    const char *name = sel_cname(sels[i]);
                    SEL sel = sel_registerNameNoLock(name, isBundle);
                    if (sels[i] != sel) {
                        sels[i] = sel;
                    }
                }
            }
        }
    
        ts.log("IMAGE TIMES: fix up selector references");
    
        // Discover classes. Fix up unresolved future classes. Mark bundle classes.
        bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    
        for (EACH_HEADER) {
            if (! mustReadClasses(hi, hasDyldRoots)) {
                // Image is sufficiently optimized that we need not call readClass()
                continue;
            }
    
            classref_t const *classlist = _getObjc2ClassList(hi, &count);
    
            bool headerIsBundle = hi->isBundle();
            bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
    
            for (i = 0; i < count; i++) {
                Class cls = (Class)classlist[I];
                Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    
                if (newCls != cls  &&  newCls) {
                    // Class was moved but not deleted. Currently this occurs 
                    // only when the new class resolved a future class.
                    // Non-lazily realize the class below.
                    resolvedFutureClasses = (Class *)
                        realloc(resolvedFutureClasses, 
                                (resolvedFutureClassCount+1) * sizeof(Class));
                    resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
                }
            }
        }
    
        ts.log("IMAGE TIMES: discover classes");
    
        // Fix up remapped classes
        // Class list and nonlazy class list remain unremapped.
        // Class refs and super refs are remapped for message dispatching.
        
        if (!noClassesRemapped()) {
            for (EACH_HEADER) {
                Class *classrefs = _getObjc2ClassRefs(hi, &count);
                for (i = 0; i < count; i++) {
                    remapClassRef(&classrefs[I]);
                }
                // fixme why doesn't test future1 catch the absence of this?
                classrefs = _getObjc2SuperRefs(hi, &count);
                for (i = 0; i < count; i++) {
                    remapClassRef(&classrefs[I]);
                }
            }
        }
    
        ts.log("IMAGE TIMES: remap classes");
    
    #if SUPPORT_FIXUP
        // Fix up old objc_msgSend_fixup call sites
        for (EACH_HEADER) {
            message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
            if (count == 0) continue;
    
            if (PrintVtables) {
                _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                             "call sites in %s", count, hi->fname());
            }
            for (i = 0; i < count; i++) {
                fixupMessageRef(refs+i);
            }
        }
    
        ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
    #endif
    
    
        // Discover protocols. Fix up protocol refs.
        for (EACH_HEADER) {
            extern objc_class OBJC_CLASS_$_Protocol;
            Class cls = (Class)&OBJC_CLASS_$_Protocol;
            ASSERT(cls);
            NXMapTable *protocol_map = protocols();
            bool isPreoptimized = hi->hasPreoptimizedProtocols();
    
            // Skip reading protocols if this is an image from the shared cache
            // and we support roots
            // Note, after launch we do need to walk the protocol as the protocol
            // in the shared cache is marked with isCanonical() and that may not
            // be true if some non-shared cache binary was chosen as the canonical
            // definition
            if (launchTime && isPreoptimized) {
                if (PrintProtocols) {
                    _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                                 hi->fname());
                }
                continue;
            }
    
            bool isBundle = hi->isBundle();
    
            protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
            for (i = 0; i < count; i++) {
                readProtocol(protolist[i], cls, protocol_map, 
                             isPreoptimized, isBundle);
            }
        }
    
        ts.log("IMAGE TIMES: discover protocols");
    
        // Fix up @protocol references
        // Preoptimized images may have the right 
        // answer already but we don't know for sure.
        for (EACH_HEADER) {
            // At launch time, we know preoptimized image refs are pointing at the
            // shared cache definition of a protocol.  We can skip the check on
            // launch, but have to visit @protocol refs for shared cache images
            // loaded later.
            if (launchTime && hi->isPreoptimized())
                continue;
            protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapProtocolRef(&protolist[I]);
            }
        }
    
        ts.log("IMAGE TIMES: fix up @protocol references");
    
        // Discover categories. Only do this after the initial category
        // attachment has been done. For categories present at startup,
        // discovery is deferred until the first load_images call after
        // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
        if (didInitialAttachCategories) {
            for (EACH_HEADER) {
                load_categories_nolock(hi);
            }
        }
    
        ts.log("IMAGE TIMES: discover categories");
    
        // Category discovery MUST BE Late to avoid potential races
        // when other threads call the new category code before
        // this thread finishes its fixups.
    
        // +load handled by prepare_load_methods()
    
        // Realize non-lazy classes (for +load methods and static instances)
        for (EACH_HEADER) {
            classref_t const *classlist = hi->nlclslist(&count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;
    
                addClassTableEntry(cls);
    
                if (cls->isSwiftStable()) {
                    if (cls->swiftMetadataInitializer()) {
                        _objc_fatal("Swift class %s with a metadata initializer "
                                    "is not allowed to be non-lazy",
                                    cls->nameForLogging());
                    }
                    // fixme also disallow relocatable classes
                    // We can't disallow all Swift classes because of
                    // classes like Swift.__EmptyArrayStorage
                }
                realizeClassWithoutSwift(cls, nil);
            }
        }
    
        ts.log("IMAGE TIMES: realize non-lazy classes");
    
        // Realize newly-resolved future classes, in case CF manipulates them
        if (resolvedFutureClasses) {
            for (i = 0; i < resolvedFutureClassCount; i++) {
                Class cls = resolvedFutureClasses[I];
                if (cls->isSwiftStable()) {
                    _objc_fatal("Swift class is not allowed to be future");
                }
                realizeClassWithoutSwift(cls, nil);
                cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
            }
            free(resolvedFutureClasses);
        }
    
        ts.log("IMAGE TIMES: realize future classes");
    
        if (DebugNonFragileIvars) {
            realizeAllClasses();
        }
    
    
        // Print preoptimization statistics
        if (PrintPreopt) {
            static unsigned int PreoptTotalMethodLists;
            static unsigned int PreoptOptimizedMethodLists;
            static unsigned int PreoptTotalClasses;
            static unsigned int PreoptOptimizedClasses;
    
            for (EACH_HEADER) {
                if (hi->hasPreoptimizedSelectors()) {
                    _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                                 "in %s", hi->fname());
                }
                else if (hi->info()->optimizedByDyld()) {
                    _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                                 "in %s", hi->fname());
                }
    
                classref_t const *classlist = _getObjc2ClassList(hi, &count);
                for (i = 0; i < count; i++) {
                    Class cls = remapClass(classlist[i]);
                    if (!cls) continue;
    
                    PreoptTotalClasses++;
                    if (hi->hasPreoptimizedClasses()) {
                        PreoptOptimizedClasses++;
                    }
                    
                    const method_list_t *mlist;
                    if ((mlist = cls->bits.safe_ro()->baseMethods())) {
                        PreoptTotalMethodLists++;
                        if (mlist->isFixedUp()) {
                            PreoptOptimizedMethodLists++;
                        }
                    }
                    if ((mlist = cls->ISA()->bits.safe_ro()->baseMethods())) {
                        PreoptTotalMethodLists++;
                        if (mlist->isFixedUp()) {
                            PreoptOptimizedMethodLists++;
                        }
                    }
                }
            }
    
            _objc_inform("PREOPTIMIZATION: %zu selector references not "
                         "pre-optimized", UnfixedSelectors);
            _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                         PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                         PreoptTotalMethodLists
                         ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                         : 0.0);
            _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                         PreoptOptimizedClasses, PreoptTotalClasses, 
                         PreoptTotalClasses 
                         ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                         : 0.0);
            _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                         "pre-optimized", UnfixedProtocolReferences);
        }
    
    #undef EACH_HEADER
    }
    

    通过read_images的这段源码,代码很多三百多行,我们可以了解到这个方法中包含了对class、protocol、selector、category等一系列的处理。我们折叠代码之后可以看到系统的一些log提示,把整个代码功能分成了几个模块。

    image.png
    这几个部分的log依次翻译一下:
    1、ts.log("IMAGE TIMES: fix up selector references"); 修复选择器引用
    2、ts.log("IMAGE TIMES: discover classes"); 查找相关类;
    3、ts.log("IMAGE TIMES: remap classes"); 重映射类;
    4、ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); 修复objc_msgSend_fixup;
    5、ts.log("IMAGE TIMES: discover protocols"); 查找相关协议
    6、ts.log("IMAGE TIMES: fix up @protocol references"); 修复协议指向
    7、 ts.log("IMAGE TIMES: discover categories"); 查找相关分类
    8、ts.log("IMAGE TIMES: realize non-lazy classes"); 实现非懒加载类
    9、ts.log("IMAGE TIMES: realize future classes"); 实现类
    所以,这个部分的主要功能就是一些相关类信息的修复,包括协议,分类等之前漏加载的在这个地方修复,重新映射等。我们通过2、7可以看到关于分类的具体操作,主要由方法readClassload_categories_nolock方法。
    readClass
    image.png
    我们结合自己实现的类LGPerson,进行条件断点调试,然后调试readClass的具体流程如下:
    1、第一个条件判断missingWeakSuperclass(cls) 没有走,貌似是找不到父类的会返回nil;
    2、接下来是if (mangledName != nullptr) 进入,mangledName这个不为空的时候会走;
    mangledName != nullptr.png
    3、然后是if (Class newCls = popFutureNamedClass(mangledName)) 没走
    static Class popFutureNamedClass(const char *name)
    {
        runtimeLock.assertLocked();
    
        Class cls = nil;
    
        if (future_named_class_map) {
            cls = (Class)NXMapKeyFreeingRemove(future_named_class_map, name);
            if (cls && NXCountMapTable(future_named_class_map) == 0) {
                NXFreeMapTable(future_named_class_map);
                future_named_class_map = nil;
            }
        }
    
        return cls;
    }
    

    popFutureNamedClass这个主要是判断这个类是不是在未实现的表future_named_class_map中,进入到这个方法后future_named_class_map这个表为空,所以直接返回class,不做操作。

    4、if (headerIsPreoptimized && !replacing)没走,headerIsPreoptimized为false,那么去执行else分支的内容;
    5、else 中的 if (mangledName) ,执行该分支 ,执行方法 addNamedClass(cls, mangledName, replacing),而addNamedClass具体实现如下

    * addNamedClass
    * Adds name => cls to the named non-meta class map.
    * Warns about duplicate class names and keeps the old mapping.
    * Locking: runtimeLock must be held by the caller
    **********************************************************************/
    static void addNamedClass(Class cls, const char *name, Class replacing = nil)
    {
        runtimeLock.assertLocked();
        Class old;
        if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
            inform_duplicate(name, old, cls);
    
            // getMaybeUnrealizedNonMetaClass uses name lookups.
            // Classes not found by name lookup must be in the
            // secondary meta->nonmeta table.
            addNonMetaClass(cls);
        } else {
            NXMapInsert(gdb_objc_realized_classes, name, cls);
        }
        ASSERT(!(cls->data()->flags & RO_META));
    
        // wrong: constructed classes are already realized when they get here
        // ASSERT(!cls->isRealized());
    }
    

    就是把类名添加到表non-meta class map中,重复的类名会保留旧的映射。然后 if ((old = getClassExceptSomeSwift(name)) && old != replacing) 这个判断不会走,因为目前的LGTeacher没有重复,会执行else部分的NXMapInsert;

    截屏2021-10-13 下午4.31.35.png
    typedef struct _NXMapTable {
        /* private data structure; may change */
        const struct _NXMapTablePrototype   * _Nonnull prototype;
        unsigned    count;
        unsigned    nbBucketsMinusOne;
        void    * _Nullable buckets;
    } NXMapTable OBJC_MAP_AVAILABILITY;
    

    这个地方是将类名插入到NXMapTable表中,这个表中有个成员变量叫buckets。
    先通过table去读取buckets;
    然后根据固定映射关系算出下一次要插入表中的index;
    进行偏移量的增加准备这一次的类名插入,插入之前由四分之三扩容原则先去判断一下;
    if (table->count * 4 > numBuckets * 3):如果table总长度的4 比numBuckets的3倍还大,说明这一次插入的位置不合适,会执行_NXMapRehash:

    static void _NXMapRehash(NXMapTable *table) {
        MapPair *pairs = (MapPair *)table->buckets;
        MapPair *pair = pairs;
        unsigned    numBuckets = table->nbBucketsMinusOne + 1;
        unsigned    index = numBuckets;
        unsigned    oldCount = table->count;
        
        table->nbBucketsMinusOne = 2 * numBuckets - 1;
        table->count = 0; 
        table->buckets = allocBuckets(malloc_zone_from_ptr(table), table->nbBucketsMinusOne + 1);
        while (index--) {
        if (pair->key != NX_MAPNOTAKEY) {
            (void)NXMapInsert(table, pair->key, pair->value);
        }
        pair++;
        }
        if (oldCount != table->count)
        _objc_inform("*** maptable: count differs after rehashing; probably indicates a broken invariant: there are x and y such as isEqual(x, y) is TRUE but hash(x) != hash (y)\n");
        freeBuckets(pairs);
    }
    

    这是一个再哈希的算法,直到找到合适的位置为止,把类的映射插入到NXMapTable表中。这样类在NXMapTable表中的映射就完成了!整个过程非常复杂并且精密!👍🏻👍🏻👍🏻

    load_categories_nolock
      // Discover categories. Only do this after the initial category
        // attachment has been done. For categories present at startup,
        // discovery is deferred until the first load_images call after
        // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
        if (didInitialAttachCategories) {
            for (EACH_HEADER) {
                load_categories_nolock(hi);
            }
        }
    

    注释翻译:分类的获取。仅在完成初始化分类附件后执行此操作。对于启动时出现的分类,获取将推迟到调用_dyld_objc_notify_register完成后的第一次load_images调用。
    大概的意思是在load_images时调用。

    相关文章

      网友评论

          本文标题:14.iOS底层学习之read_images分析

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