iOS 底层探索篇 —— map_Images之类加载

927 阅读11分钟

前言

dyld加载流程(精简)

由于dyld流程加载是一个比较复杂的过程,这里我们只做简单的分析。

下面是main函数之前调用堆栈的信息:

通过上面的堆栈信息我们可以简单分析出来一个流程

  • _dyld_start函数,是整个加载的入口。
  • 经过dyldbooststrap::start->dyld::start->ImageLoad::->ImageLoadMackO::这些流程把MachO里面的信息读取到。
  • libSystem_initializer,初始化系统库。
  • libdispatch_init,初始化libdispatch库
  • _os_object_init->::_objc_init,初始化objc库。

dyld主要的步骤都在读取MachO段里面的数据,这一步在今后的文章中会做分析,敬请期待。

objc_init流程

这里我们提供objc_init的源码部分

1. _objc_init注释

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

看只是我们可以得到两个信息

  • 注册imagedyld这个阶段;
  • 调用_objc_initlibSystem系统库之后。

2. environ_init

初始化环境变量,我们可以通过export OBJC_HELP=1 ls在终端查看相关的环境变量

我们可以调整相关的环境变量的值。如下

2.1 修改OBJC_PRINT_LOAD_METHOD

修改该环境变量之后观察控制台打印输出的结果

  • 系统的load方法会全部打印出来。
  • 自己定义的load方法也会打印出来,同时我们熊这里入手,在做启动优化的时候可以快速定位调整。

2.1 修改OBJC_PRINT_NONPOINTER_ISA

修改该环境变量之后观察控制台打印输出的结果

3. tls_init()

初始化关于线程key的绑定,比如线程的析构函数。

4. static_init()

初始化静态构造函数,这里的注释部分也在讲在dyld之后。

这里我们做一个实验,比对一下我们自己增加几种构造函数前后count的值。

  1. 未增加自己的构造函数

    没有增加自己的构造函数之前的count值为11。

  2. 增加自己的构造函数

    增加自己的构造函数之前的count值为11。

通过上面两种方式的比对,我们可以知道static_init初始化的是系统的构造函数。

5. lock_init()

初始化objc各种锁

这里主要是4把锁的初始化,代码为空我们分析以下几种可能

  • 没有做相关处理。
  • 有做处理,但是在其他位置。
  • 有做处理,没有做到完全开源。

6. exception_init

报错的初始化

若是出现奔溃信息,则会在_objc_terminate这个函数里面

7. _dyld_objc_notify_register(&map_images, load_images, unmap_image)

通过三个函数指针直接发起回调。这里分析_dyld_objc_notify_mapped这个函数,在dyld里面通过注册函数的内存地址,然后通过函数指针又回调到了map_images这个函数。

记录保存注册函数的内存地址。

通过函数指针的形式回调这个函数即map_images,下面进入到map_images分析。

map_Images

1. map_images函数

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

map_images函数是一个提供外部调用入口,函数的真正实现在map_images_nolock

2. map_images_nolock函数

由于代码比较多,我们去掉一些无关紧要的代码,比如打印日志信息、代码的注释、iOS系统用到的,精简之后如下

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    if (firstTime) {
        preopt_init();
    }

    // Find all images with Objective-C metadata.
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable is size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
            }
            
            hList[hCount++] = hi;
    
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();
    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}

现在开始分析精简之后的代码

  • firstTime一个用static修饰的局部成员变量,记录第一次需要做的事。
  • _getObjc2SelectorRefs函数,从mach_o读取__objc_selrefs段的信息。
  • _getObjc2MessageRefs函数,从mach_o读取__objc_msgrefs段的信息。
  • 第一次需要执行的函数preopt_init()sel_init()arr_init()
  • _read_images()函数,读取镜像文件。

2.1 preopt_init()

注释描述直到objc-using镜像文件可以发现延迟初始化,主要是共享缓存有了之后的处理。

2.2 sel_init()

去掉其他信息之后,我们可以看到是注册了一些系统提供的API

2.3 arr_init()

这一块也是比较重要的信息,让我们知道了AutorereleasePoolPageSideTable的初始化在这里是有执行的。

2.4 _read_images()

前面的三个函数不是我们目前探索的重点,只是简单的分析了一下。下面我们开始深入_read_images(),请看接下来的类加载部分。

类加载

1. _read_images()函数

由于这个函数的代码比较多,我们把其中的代码先用伪代码的信息展示出来,然后再来讲解每一步。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
   1. 定义局部变量,完成doneOnce第一次要处理的事情。
   if (!doneOnce){ ... }
   
   2. Discover classes
   for (EACH_HEADER) { ... }
   
   3. Fix up remapped classes
   if (!noClassesRemapped()) { ... }

   4. Fix up @selector references
   { ... }
   
   5. Fix up old objc_msgSend_fixup call sites
   for (EACH_HEADER) { ... }
   
   6. Discover protocols
   for (EACH_HEADER) { ... }
   
   7. Fix up @protocol references
   for (EACH_HEADER) { ... }
   
   8. Realize non-lazy classes (for +load methods and static instances)
   for (EACH_HEADER) { ... }
   
   9. Realize newly-resolved future classes
   if (resolvedFutureClasses) { ... }
   
   10. Discover categories. 
   for (EACH_HEADER) { ... }
   
   11. +load handled by prepare_load_methods()
   if (DebugNonFragileIvars) { ... }
}

这里我们只对第1步、第2步、第11步做分析,其他的步骤会在对应的文章中讲解。

1.1 doneOnce

去掉打印部分的代码、与iOS操作系统不相干的代码。

if (!doneOnce) {
        doneOnce = YES;

#if SUPPORT_NONPOINTER_ISA

# if SUPPORT_INDEXED_ISA

# endif

# if TARGET_OS_OSX

# endif

#endif
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes do not go in this table.
        // 4/3 is NXMapTable is load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
        
        ts.log("IMAGE TIMES: first time tasks");
    }
  • disableTaggedPointers()函数,条件允许的时候,禁用taggedPointers
  • initializeTaggedPointerObfuscator函数,初始化taggedPointer
  • gdb_objc_realized_classes通过NX技术创建的mapTable,这张表里面包含的所有类的信息,我们称之为总表。
  • allocatedClasses通过NX技术创建的HashTable,这张表只包含已经开辟的类的信息,我们称之为已开辟类的表。

1.2 Discover classes

for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

        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;
            }
        }
    }
  1. _getObjc2ClassListmach_o读取__objc_classlist段的信息。

  2. readClass函数

这里看上去像是对rwro在处理,但是并不是,它是对futureclass的处理。我们自己定义的类并不会进来。

  • 第一步,将cls无条件的插入到gdb_objc_realized_classes这个总表里面。
  • 第二步,若cls可以识别,即是已开辟的类,插入到allocatedClasses这个表里面,并且会递归元类插入。

2. realizeAllClasses()函数

循环处理header_info,函数实现realizedAllClassesInImage().

2.1 realizedAllClassesInImage()函数

remapClass重新映射类,realizeClassMaybeSwiftAndLeaveLocked函数调用。

realizeClassMaybeSwiftMaybeRelock函数真正调用的总入口。

通过判断是否Swift class来区分SwiftOC类的实现入口。

2.2 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 was not 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 is subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls);

    return cls;
}

这里的信息比较多,基本是都是对类的结构体的成员初始化处理。我们抽其中比较重要的描述。

  • rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);rw结构体开辟空间。同时在为其成员赋值。
  • supercls = realizeClassWithoutSwift(remapClass(cls->superclass));递归父类的信息初始化,出口是父类为nil
  • metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));递归元类的信息初始化,出口是元类的元类为自己。
  • if (supercls) {addSubclass(supercls, cls);} else { addRootClass(cls);},处理class_data_bits_t成员双向绑定父类,子类。
  • methodizeClass()处理类的相关信息,看下面的介绍。

methodizeClass()函数

简化一下打印的代码之后

static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they do not have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
    
    if (cats) free(cats);
}

我们可以发现是在这里将ro的信息给了rw,可以说明ro的信息是在编译阶段就已经处理好了,rw则是在运行阶段处理的。

  • method_list_tproperty_list_tprotocol_list_t,他们的数据类型是相同,都是entsize_list_tt这么一个二维数组。
  • 他们都是通过获取ro里面的相关信息之后,然后通过attachLists函数处理来赋值类的rw的信息。
  • attachCategories是把拿到的分类列表进行处理,最终的处理也是将信息通过attachLists()函数处理来赋值类的rw的信息。

attachLists()函数分析

主要有三种情况来处理

  • 多对多;
  • 0对1;
  • 1对多。
  1. 多对多的分析
    uint32_t oldCount = array()->count;
    uint32_t newCount = oldCount + addedCount;
    setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
    array()->count = newCount;
    memmove(array()->lists + addedCount, array()->lists, 
            oldCount * sizeof(array()->lists[0]));
    memcpy(array()->lists, addedLists, 
         addedCount * sizeof(array()->lists[0]));

这种情况分下面几步完成

  1. 扩大已有容器的内存;
  2. 将原本已有的数据移动增加的数量addedCount
  3. 将需要增加的addedLists放在扩容之后的内存的前面。
  1. 0对1的分许
list = addedLists[0];

这就是一步直接的赋值。

  1. 1对多的分析
    List* oldList = list;
    uint32_t oldCount = oldList ? 1 : 0;
    uint32_t newCount = oldCount + addedCount;
    setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
    if (oldList) array()->lists[addedCount] = oldList;
    memcpy(array()->lists, addedLists, 
         addedCount * sizeof(array()->lists[0]));
  1. 扩大已有容器的内存;
  2. 将旧数据放在指定的位置;
  3. 将需要增加的数据放在前面。

总结

  1. _read_images()读取镜像文件。
  2. realizeAllClasses()实现所有的类。包括SwiftOC,同时会初始化其他objc_class结构体相关的信息
  3. methodizeClass()把类的ro信息给到rw,同时也会加载分类的信息到类里面。

学习之路,砥砺前行

不足之处可以在评论区指出