应用程序的加载(下)

326 阅读4分钟

从应用程序的加载上,我们了解了main函数之前的dyld整个链接过程

  • _dyld_start
  • dyldbootstrap::start
  • dyld::_main
  • dyld::initializeMainExecutable()
  • ImageLoader::runInitializers
  • ImageLoader::processInitializers
  • ImageLoader::recursiveInitialization
  • ImageLoaderMachO::doInitialization
  • libSystem_initializer
  • libdispatch_init
  • _os_object_init
  • _objc_init 在 _objc_init中我们看到调用了_dyld_objc_notify_register(&map_images, load_images, unmap_image); ❓map_images()是什么时候调用的,load_images()是什么时候调用的。 ❓dyld是怎么到main函数的 ❓cxx 和load()和main函数的加载顺序是什么

_dyld_objc_notify_register

dyld源码

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,

                                _dyld_objc_notify_init      init,

                                _dyld_objc_notify_unmapped  unmapped)

{

dyld::registerObjCNotifiers(mapped, init, unmapped);

}

我们看下 registerObjCNotifiers

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)

{

// record functions to call

sNotifyObjCMapped = mapped;

sNotifyObjCInit = init;

sNotifyObjCUnmapped = unmapped;

// call 'mapped' function with all images mapped so far

try {

notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);

}

catch (const char* msg) {

// ignore request to abort during registration

}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)

for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {

ImageLoader* image = *it;

if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {

dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);

(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

}

}

}

sNotifyObjCMapped = mapped;对map_images的赋值,我们看下sNotifyObjCMapped调用的地方,全局搜索sNotifyObjCMapped是在notifyBatchPartial函数中调用,在registerObjCNotifiers中赋值之后就调起了

load_images

我们看下load_images里面做了什么

load_images(**const** **char** *path __unused, **const** **struct** mach_header *mh)

{

    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {

        didInitialAttachCategories = **true**;

        loadAllCategories();

    }
    // Return without taking locks if there are no +load methods here.

    if (!hasLoadMethods((**const** headerType *)mh)) **return**;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods

    {

        mutex_locker_t lock2(runtimeLock);

        prepare_load_methods((**const** headerType *)mh);

    }
    // Call +load methods (without runtimeLock - re-entrant)

    call_load_methods();

}

看上面的代码知道,load_images是对load方法的加载,prepare_load_methods中准备了类的load方法和分类的load方法

void prepare_load_methods(**const** headerType *mhdr)

{
    size_t count, i;
    runtimeLock.assertLocked();
   classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue// category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, **nil**);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);

    }

}

schedule_class_load是一个递归查找父类load方法,并加入loadable_classes

static void schedule_class_load(Class cls)

{

    if (!cls) return;

    ASSERT(cls->isRealized());  // _read_images should realize
    if (cls->data()->flags & RW_LOADED) return;
    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 

}

我们看下add_class_to_loadable_list 加入loadable_classes操作

void add_class_to_loadable_list(Class cls)

{

    IMP method;
    loadMethodLock.assertLocked();
    method = cls->getLoadMethod();

    if (!method) return// Don't bother if cls has no +load method
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                    cls->nameForLogging());
    }

    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
                              realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));

    }
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;

}

我们看下

IMP
objc_class::getLoadMethod()

{
    runtimeLock.assertLocked();
    const method_list_t *mlist;
    ASSERT(isRealized());
    ASSERT(ISA()->isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA()->isMetaClass());
    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            if (0 == strcmp(name, "load")) {
                return meth.imp(false);
            }
        }
    }
    return nil;

}

分类的load方法也是类似搜集的,就不列出来了。在prepare_load_methods之后就调用了 call_load_methods();

void call_load_methods(**void**)

{
    static bool loading = NO;
    bool more_categories;
    loadMethodLock.assertLocked();
    // Re-entrant calls do nothing; the outermost call will finish the job.

    if (loading) return;
    loading = YES;
    void *pool = objc_autoreleasePoolPush();
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2. Call category +loads ONCE

        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories

    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = **NO**;

}

分别在call_class_loadscall_category_loads调用了对应的load方法。

static void call_class_loads(**void**)

{
    int i;
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = **nil**;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    // Call all +loads for the detached list.

    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) **continue**; 
        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, **@selector**(load));
    }
    // Destroy the detached list.
    if (classes) free(classes);

}

(*load_method)(cls, **@selector**(load));load方法的调用 call_category_loads(*load_method)(cls, @selector(load));对分类方法的load方法进行了调用

static bool call_category_loads(void)

{
    int i, shift;
    bool new_categories_added = NO;
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = **nil**;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;
    // Call all +loads for the detached list.

    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) **continue**;
        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {

                _objc_inform("LOAD: +[%s(%s) load]\n", 

                             cls->nameForLogging(), 

                             _category_getName(cat));

            }

            (*load_method)(cls, @selector(load));

            cats[i].cat = nil;

        }
...
    }

综上,我们知道了load_images就是对load方法的调用,我们看下在dyld是什么时候调用的,全局搜索sNotifyObjCInit是在notifySingle中调起的。搜索notifySingleImageLoader::recursiveInitialization中调用的。

c++,load(), main()的加载顺序

我们看下ImageLoader::recursiveInitializationrecursiveInitialization是一个递归的函数

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)

{
...
// let objc know we are about to initialize this image

uint64_t t1 = mach_absolute_time();

fState = dyld_image_state_dependents_initialized;

oldState = fState;

context.notifySingle(dyld_image_state_dependents_initialized, **this**, &timingInfo);

// initialize this image

bool hasInitializers = this->doInitialization(context);

// let anyone know we finished initializing this image

fState = dyld_image_state_initialized;

oldState = fState;

context.notifySingle(dyld_image_state_initialized, this, NULL);
...
}

context.notifySingle是load_images()的调用时刻,即打印load方法的时刻, 示例代码: 在objc_init前面添加一个c++方法

__attribute__ ((constructor)) void objcFunc(){

    printf("来了 : %s \n", __func__ );

}

在mian函数中

//Person.m
+ (void)load{
    NSLog(@"load");
}
- (void)say1{

    NSLog(@"Person say : %s", __func__);

}
//main
int main(int argc, const char * argv[]) {

    @autoreleasepool {
        Person *p = [Person alloc];
        [p say1];
        NSLog(@"Hello, World!");

    }
    return 0;

}
__attribute__ ((constructor)) void cFunc(void){
    printf("来了 : %s \n", __func__);
}

打印如下

来了 : objcFunc
load
来了 : cFunc
Person say : -[Person say1]
Hello, World!

打印顺序:写在objc库中的静态方法->load方法->c++方法 刚开始,进入到recursiveInitialization函数中的时候,如果有依赖库,先加载底层依赖库,会先走bool hasInitializers = this->doInitialization(context);经过一系列调用调起objc_initobjc_init中有一个static_init()会加载静态方法,所以先打印objcFunc。 在下面context.notifySingle(dyld_image_state_initialized, this, NULL);(doInitialization方法后面的context.notifySingle)中打印出load方法。 加载完系统依赖库,本次运行的工程的c++方法再加载,最后在是main函数。 我们在main函数中的c++方法中加一个断点 截屏2021-07-17 下午1.27.25.png 看到它是doModInitFunctions里面调起的。

从dyld到main

dyld的源码中我们可以看到dyld到main的一个跳转。 截屏2021-07-17 下午1.53.38.png

验证: 在main中静态函数中打一个断点,然后看下汇编: 截屏2021-07-17 下午1.48.37.png 0x100015060 <+96>: jmpq   *%rax 使用register read读取寄存器。

截屏2021-07-17 下午1.49.48.png rax就是main函数