美文网首页
iOS应用程序的加载(一)

iOS应用程序的加载(一)

作者: FireStroy | 来源:发表于2020-09-27 11:56 被阅读0次

    调用在main()之前

    一般开发场景中,我们都是把main()函数作为程序的入口,但是这里探究一下man()函数开始之前发生了什么。

    程序运行依赖很多的库和文件,有动态库(.so .framwork)还有静态库(.a .lib)以及很多的.h .m文件 他们如何加载 到程序中的呢

    编译链接
    • 静态库:链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。

    • 动态库: 链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存,还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。

    动态链接库包括:

    • iOS 中用到的所有系统 framework
    • 加载OC runtime方法的libobjc,
    • 系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)
      这些放在内存中的共享库文件会在app启动后交给dyld动态连接器来进行链接管理。
    image.png

    动静态链接库以及对于App本身的可执行文件而言,都称为image。
    dyld则是以image为单位将这些可执行文件加载到app中。

    开始在main()之前

    首先做一份断点信息,iOS中-load()方法会在main()函数开始之前就调用,在这里设置断点可以很好的追踪主函数开始之前的调用信息。同样的,使用__attribute__ ((constructor))修饰的C++属性函数也会在main()之前被加载调用.

    load方法 C++属性函数

    这里通过函数名称大致也能推测出一些比较重要的函数名称

    • _dyld_start
    • dyldbootstrap::start
    • dyld::main
    • dyld::initializeMainExecutable
    • imageLoader::****
    • dyld:: notifySingle
    • load_images

    dyld的加载流程

    很明显_dyld_start是作为整个流程的起始位置。
    打开一份新鲜的objc756源码,开始查找_dyld_start

    __dyld_start:
        mov     x28, sp
        and     sp, x28, #~15       // force 16-byte alignment of stack
        //...
        // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
        bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
        //... 
    

    _dyld_start的源码是用汇编写的,但是只需要看注释就能明白多个版本的下的_dyld_start都会来到dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)这个函数。

    来看看dyldbootstrap::start内部实现:

    uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                    const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue){
        dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
    
        rebaseDyld(dyldsMachHeader);
        const char** envp = &argv[argc+1];
        const char** apple = envp;
        while(*apple != NULL) { ++apple; }
        ++apple;
        __guard_setup(apple);
        uintptr_t appsSlide = appsMachHeader->getSlide();
        return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
    }
    

    出了一些参数的处理 最重要的一步就是_main()这个函数了。

    _main()函数

    _main()函数代码量很大,分了很多功能,主要完成了上下文的建立,主程序初始化成ImageLoader对象,加载共享的系统动态库,加载依赖的动态库,链接动态库,初始化主程序,返回主程序main()函数地址。

    加载共享缓存

    mapSharedCache()负责将系统中的共享动态库加载进内存空间,比如UIKit就是动态共享库,这也是不同的App之间能够实现动态库共享的机制。不同App间访问的共享库最终都映射到了同一块物理内存,从而实现了共享动态库。
    内部会调用loadDyldCache()
    mapSharedCache()的基本逻辑就是:

    1. 先判断共享动态库是否已经映射到内存中了。
    2. 如果已经存在,则直接返回。
    3. 否则打开缓存文件,并将共享动态库映射到内存中。
    load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
        if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
    #if TARGET_OS_SIMULATOR
            if ( sSharedCacheOverrideDir)
                mapSharedCache();
    #else
            mapSharedCache();
    #endif
        }
    
    

    生成ImageLoader对象

    动静态库以及程序自己的.o文件都是以image的形式加载的。
    ImageLoader本身是一个抽象类,用于帮助加载特定格式的可执行文件。例如ImageLoaderMachO继承自ImageLoader.
    instantiateFromLoadedImage()会实例化一个ImageLoader对象,然后调用instantiateMainExecutable()加载文件生成image并进行链接。

    image.png

    APP启动过程中,相关的库和主程序都被加载成ImageLoader对象
    最后返回一个ImageLoaderMachO对象用于访问所有的images对象

    static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path){
        // try mach-o loader
        if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
            ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
            addImage(image);
            return (ImageLoaderMachO*)image;
        }
        throw "main executable not a known format";
    }
    
    

    加载所有插入的库 loadInsertedDylib

    遍历sEnv.DYLD_INSERT_LIBRARIES这个环境变量中的库,调用loadInsertedDylib来加载库。

    // load any inserted libraries
            if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
                for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                    loadInsertedDylib(*lib);
            }
    

    链接主程序

    这一步最终调用的还是ImageLoader::link,内部会调用recursiveLoadLibraries递归加载动态库。
    注意,先链接主程序,然后链接所有加载的库文件。

    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
            sMainExecutable->setNeverUnloadRecursive();
            if ( sMainExecutable->forceFlat() ) {
                gLinkContext.bindFlat = true;
                gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
            }
    // link any inserted libraries
            // do this after linking main executable so that any dylibs pulled in by inserted 
            // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
            if ( sInsertedDylibCount > 0 ) {
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                    image->setNeverUnloadRecursive();
                }
                if ( gLinkContext.allowInterposing ) {
                    // only INSERTED libraries can interpose
                    // register interposing info after all inserted libraries are bound so chaining works
                    for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                        ImageLoader* image = sAllImages[i+1];
                        image->registerInterposing(gLinkContext);
                    }
                }
            }
    
    

    执行初始化方法 initializeMainExecutable

    initializeMainExecutable()里面先对动态库进行runInitializers(),然后才对主程序进行runInitializers()
    runInitializers()内部调用了Imageloader::recursiveInitialization
    Imageloader::recursiveInitialization里面调用了如下内容:

    void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                              InitializerTimingList& timingInfo, UninitedUpwards& uninitUps){
        recursive_lock lock_info(this_thread);
        recursiveSpinLock(lock_info);
        if ( fState < dyld_image_state_dependents_initialized-1 ) {
                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(dyld_image_state_dependents_initialized, this, &timingInfo);
    // initialize this image
    bool hasInitializers = this->doInitialization(context);
    context.notifySingle(dyld_image_state_initialized, this, NULL);
    
    doInitialization()

    doInitialization(context);这里开始做images初始化工作。
    注意,在这个函数里面有一个判断:

    if ( ! dyld::gProcessInfo->libSystemInitialized ) {
      // libSystem initializer must run first
      dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
    }
    

    那就是在所有的images的初始化中,libSystem必须放在第一位。
    既然libSystem这么重要,那么就看看libSystem的初始化方法:

    static void
    libSystem_initializer(int argc,const char* argv[],const char* envp[],const char* apple[],const struct ProgramVars* vars){   
        //内核初始化
        __libkernel_init(&libkernel_funcs, envp, apple, vars);
        //平台初始化
        __libplatform_init(NULL, envp, apple, vars);
        //线程相关初始化
        __pthread_init(&libpthread_funcs, envp, apple, vars);
        _libc_initializer(&libc_funcs, envp, apple, vars);
        __malloc_init(apple);
        //dyld的初始化,上面dyld_start之后并不代表就开始初始化。
        _dyld_initializer();
        //这里是重点
        libdispatch_init();
        _libxpc_initializer();
    }
    

    _objc_init()

    libdispatch_init()这个库的初始化应该最熟悉了,内部会对GCD底层进行一些初始化工作,以及会调用一个对我们当前探索流程很重要的一个初始化函数_os_object_init(),而_os_object_init内部则会直接调用_objc_init()这个函数,来完成_dyld_objc_notify_register(&map_images, load_images, unmap_image);函数注册。

    void
    libdispatch_init(void)
    {
        //...
        _dispatch_hw_config_init();
        _dispatch_time_init();
        _dispatch_vtable_init();
        _os_object_init();
        _voucher_init();
        _dispatch_introspection_init();
    }
    
    从_os_object_init()内部调用来到这里
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    notifySingle()

    doInitialization()完成前后,都会有一个通知信号notifySingle()

    找到它的源码位置

    static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
    {
        if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
            uint64_t t0 = mach_absolute_time();
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
            uint64_t t1 = mach_absolute_time();
            uint64_t t2 = mach_absolute_time();
            uint64_t timeInObjC = t1-t0;
            uint64_t emptyTime = (t2-t1)*100;
            if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
                timingInfo->addTime(image->getShortName(), timeInObjC);
            }
           
    }
    

    只发现了(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());这个函数的调用。
    先看看它的定义:

    typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
    

    全局搜索它的赋值,只有一处地方能找到:

    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;
        //...
    }
    

    那么说明一定是在dyld的某个地方,调用了registerObjCNotifiers,然后给了sNotifyObjCInit赋值。
    同样,全局找到了唯一的调用位置:

    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);
    }
    

    那么看到这里就能明白,这里之所以能调用*sNotifyObjCInit (),就是因为在doInitialization()中完成了libSystem_initializer()的调用,而libSystem初始化的时候内部调用了objc_init()函数,完成了_dyld_objc_notify_register(&map_images, load_images, unmap_image);函数的注册。
    因此,在doInitialization()执行完毕之后,发送一条完成的通知notifySingle(),并执行回调函数。

    dyld总体流程总结:
    _dyld_start开始

    1. 环境变量的配置
    2. 共享缓存
    3. 主程序初始化
    4. 加入动态库
    5. link主程序
    6. link动态库
    7. main()

    相关文章

      网友评论

          本文标题:iOS应用程序的加载(一)

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