iOS程序启动之map_images探究

前言

在前面一章节中我们探究了ios启动加载,当时探究到了apple会在_objc_init函数调用_dyld_objc_notify_register函数并且传递了&map_images与load_images等参数,在镜像文件初始化之后,就会调用map_images函数.然后接着就会调用load_images,接下来我们今天先探究一个map_imags函数的内容。

探究map_images

void map_images(unsigned count, const char * const paths[],
          const struct mach_header * const mhdrs[])
{
   /*在老的运行时代码中使用的是recursive_mutex_locker_t lock(loadMethodLock);递归锁*/    
   mutex_locker_t lock(runtimeLock);
   return map_images_nolock(count, paths, mhdrs);
}

很明显在map_images函数中,直接返回了一个map_images_nolock函数调用,所以我们直接阅读map_images_nolock函数源码.

1.1 preopt_init与sel_init

在map_images_nolock首先通过调用执行preopt_init()函数禁用dyld共享缓存对SEL的优化,因为dyld共享缓存中的选择器是不可信的。

void preopt_init(void) {
  省略...
 disableSharedCacheOptimizations();
 省略...
}
//sel_init()决定dyld共享缓存中的选择器是不可信的
void disableSharedCacheOptimizations(void)
{
    fixed_up_method_list = OBJC_FIXED_UP_outside_dyld;
}

所以在map_images_nolock当中调用了sel_init()函数,初始化内部使用的选择器表和注册了c++的构造与析构方法

/***********************************************************************
* sel_init
* Initialize selector tables and register selectors used internally.
**********************************************************************/
void sel_init(size_t selrefCount)
{
    namedSelectors.init((unsigned)selrefCount);
    // Register selectors used by libobjc
    mutex_locker_t lock(selLock);
    
    SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
    SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}

1.2 arr_init

在map_images_nolock当中通过调用arr_init函数,完成了自动释放池页包含引用计数表与弱引用表的散列表关联对象管理表的初始化

void arr_init(void) {
    AutoreleasePoolPage::init();//自动释放池页初始化
    SideTablesMap.init();//散列表初始化
    _objc_associations_init();//关联对象管理表初始化
}

先简单的看一下SideTablesMap的结构如下,以后再探究

static objc::ExplicitInit> SideTablesMap;
struct SideTable {
    spinlock_t slock; //自旋锁
    RefcountMap refcnts;//引用计数表
    weak_table_t weak_table;//弱引用表
    省略...
}

1.3 _read_images

前面一系列初始化准备工作完成之后就进入到了read_images流程。
在read_images中

1.3.1 条件控制进行一次的加载,针对旧的swift版本禁用nonpointer isa初始化标记指针(tagged pointer),创建类的映射表

//始化标记指针(tagged pointer)
initializeTaggedPointerObfuscator();
//创建类的映射表
int namedClassesSize =  (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
 gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

1.3.2 修复编译阶段的@selector

  static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            //是否有dyld预优化过的sel
            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]);
                /*在Mac OS X 10.15或者iOS 13.0及以上版本中,sel_registerNameNoLock 本质是调用_dyld_get_objc_selector*/
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
    ts.log("IMAGE TIMES: fix up selector references");

其实就是修复一些名称相同的SEL的地址,

1.3.3 错误混乱的类处理

 // 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)) {
            //镜像已充分优化,无需调用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");

记录被移动但是未被删除的类,留待将来解决。

1.3.4 重新映射未重映射懒加载类

    // 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]);//重新映射
            }
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);//重新映射父类
            }
        }
    }
    ts.log("IMAGE TIMES: remap classes");
    //remapClassRef底层调用remappedClasses方法,只对lazy类处理
    /*
    *为已实现的未来类返回oldClass => newClass映射。
    *被忽略的弱链接类返回oldClass => nil映射。
    */
    static objc::DenseMap *remappedClasses(bool create) {
    static objc::LazyInitDenseMap remapped_class_map;
    runtimeLock.assertLocked();
    // start big enough to hold CF's classes and a few others
    return remapped_class_map.get(create, 32);
}

重新映射未重映射的懒加载类,也就是未实现+load方法的类,

1.3.5 修复一些消息

    // Fix up old objc_msgSend_fixup call sites
    //修复旧的objc_msgSend_fixup调用站点
    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");
  static void fixupMessageRef(message_ref_t *msg) {    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == @selector(alloc)) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == @selector(allocWithZone:)) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == @selector(retain)) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == @selector(release)) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == @selector(autorelease)) {
            msg->imp = (IMP)&objc_autorelease;
        } else {
            msg->imp = &objc_msgSend_fixedup;
        }
    } 
    else if (msg->imp == &objc_msgSendSuper2_fixup) { 
        msg->imp = &objc_msgSendSuper2_fixedup;
    } 
    else if (msg->imp == &objc_msgSend_stret_fixup) { 
        msg->imp = &objc_msgSend_stret_fixedup;
    } 
    else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { 
        msg->imp = &objc_msgSendSuper2_stret_fixedup;
    } 
#if defined(__i386__)  ||  defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fpret_fixup) { 
        msg->imp = &objc_msgSend_fpret_fixedup;
    } 
#endif
#if defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fp2ret_fixup) { 
        msg->imp = &objc_msgSend_fp2ret_fixedup;
    } 
#endif
}

修复一些方法,比如alloc,objc_retain等方法,因为不同的系统方法的实现是不一样,底层的库实现也不一样,所以需要在加载的时候,进行修复。

1.3.6 当我们类里面有协议的时候:readProtocol

    // 发现协议并且读取
    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
        //如果是从共享缓存读取的image,跳过读取协议的过程
        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");

通过readProtocol函数读取协议。具体实现后面再探究

1.3.7 修复协议引用

    // Fix up @protocol references
    //优化过的imags协议引用可能是正确的,但是我们不确定。  
    for (EACH_HEADER) {
        //在启动的时候,我们知道优化过的images的指向,
        //共享缓存定义的协议,我们不需要检查
        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");

1.3.8 分类处理

    // 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
     /发现类别。只有在初始类别之后才这样做
    //附件已完成。对于启动时的类别,
    //发现延迟到第一个load_images调用之后
    //调用_dyld_objc_notify_register完成。
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    // 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()

    ts.log("IMAGE TIMES: discover categories");

1.3.9 非懒加载类的加载处理

    // Realize non-lazy classes (for +load methods and static instances)
    //实现非懒加载类的加载。(用于+load 方法和静态实例)
    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");

1.3.10 没有被处理的类,优化被侵犯的类

    // Realize newly-resolved future classes, in case CF manipulates them
    //实现新解析的未来类,以防CF操纵它们
    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");

总结

在map_images中前期主要就是对方法选择器、类、消息、协议的修复。以及非懒加载类与分类的信息的加载(协议,方法,成员变量)
下一篇文章准备探究类与分类的加载。

你可能感兴趣的:(iOS程序启动之map_images探究)