iOS应用程序的加载(一)

2020-09-27  本文已影响0人  FireStroy

调用在main()之前

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

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

编译链接

动态链接库包括:

image.png

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

开始在main()之前

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

load方法 C++属性函数

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

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()
上一篇下一篇

猜你喜欢

热点阅读