美文网首页
iOS底层-dyld加载流程分析

iOS底层-dyld加载流程分析

作者: 含笑州 | 来源:发表于2020-09-28 16:31 被阅读0次

一、dyld简介

在iOS系统中,几乎所有的程序都会用到动态库,静态库等,而这些库在加载的时候都需要用到dyld程序进行链接,dyld是苹果的动态链接器,是苹果操作系统的一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。

二、准备

创建一个空的项目,在ViewController这个类中添加一个load方法,打个断点,然后在xcode左侧查看项目的堆栈调用信息,如下:

图1

我们发现在load方法之前,加载了好多函数呀,这些函数都干了什么啦,这些是我们接下来需要探索的东西。

补充
我们也可以通过lldb进行查看如下:

堆栈调用信息图
镜像加载图

dyld初探

下载最新的dyld源码本篇文章基于dyld-750.6进行分析,我们在源码中搜索_dyld_start,然后发现在dyldStartup.s文件中有具体的调用,无论模拟器、真机都会调用dyldbootstrap::start这个方法,我们继续查找,找到如下:

图2

部分源码如下:

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // set up random value for stack canary
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

在进入dyld的__main函数之前,会进行dyld的重定向、dyld内部加载所有的c++初始化器、地址偏移等工作。

我们进行dyld的__main函数中,发现里面干了好多事情,流程如下:


dyld流程分析图.png

1.我们发现__main函数中,主要会对环境以及平台信息进行处理,比如我们xcode设置了一些环境变量之类的,都可以打印出来。
2.getHostInfo函数:获取cpu相关信息
3.checkSharedRegionDisable函数:判断是否可以加载系统共享缓存库,加载共享缓存库
4.instantiateFromLoadedImage函数:实例化主程序,也就是machO这个可执行文件、链接动态库和插入库
5.loadInsertedDylib:加载所有插入库
6.weakBind:符号绑定
7.initializeMainExecutable:初始化依赖库、三方库、load、c++构造函数,这个函数内部源码如下:

void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}

里面关键的函数是runInitializers,顺着这个函数我们可以找到notifySingle,doInitialization
查找如下:runInitializers->processInitializers->recursiveInitialization->notifySingle(加载load方法)
doInitialization:内部会调用全局C++对象的构造函数,即attribute((constructor))这样的函数。
最后通过notifyMonitoringDyldMain:通知main函数dyld引导已经完成了,可以进入main函数了。

补充
我们发现notifySingle这个函数中会调用sNotifyObjCInit,它是在registerObjCNotifiers函数中进行赋值,_dyld_objc_notify_register函数中又调用了registerObjCNotifiers,但是我们却没有找到_dyld_objc_notify_register函数调用的位置,最后在objc的源码中发现了我们想要的结果。

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

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

注释中有解释,Called by libSystem BEFORE library initialization time,library初始化之前被libSystem库调用。

查看load_images源码实现

void
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方法

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_load_methods函数中循环调用类的+load方法
然后会调用分类的+load方法

相关文章

  • iOS底层原理探索--dyld加载流程分析

    iOS底层原理探索--dyld加载流程分析 参考文章:https://juejin.im/post/5e12ce8...

  • iOS底层-dyld加载流程分析

    一、dyld简介 在iOS系统中,几乎所有的程序都会用到动态库,静态库等,而这些库在加载的时候都需要用到dyld程...

  • iOS dyld加载流程

    dyld加载的详细流程可以参考文章 iOS dyld加载流程[https://www.jianshu.com/p...

  • iOS-底层原理14:dyld与objc的关联

    在上一篇文章iOS-底层原理13:dyld加载流程[https://www.jianshu.com/p/030cf...

  • iOS底层-dyld加载分析

    引言: 众所周知,我们的iOS应用是通过Dyld进行加载的,那么Dyld是如何加载我们的应用的,它的流程是怎样的,...

  • dyld加载流程

    本文主要是分析main函数之前,底层做了什么 -- dyld的加载流程 例子 新建一个项目,在ViewContro...

  • iOS底层探索 --- dyld加载流程

    dyld加载流程图 建议大家在阅读文章的时候,结合流程图阅读。这样方便理解这个流程,可以将图片下载到本地,一边阅读...

  • 应用程序加载

    应用程序的加载 本篇主要是分析dyld的加载流程,了解在main函数之前,底层还做了什么? 我们经常听到的二进制重...

  • iOS-OC底层10:dyld加载流程分析

    前沿 我们实现ViewController的+(void)load方法,在main函数中添加c++方法 我们可以看...

  • iOS dyld流程分析

    本文的目的主要是分析dyld的加载流程,了解在main函数之前,底层还做了什么 引子 创建一个project,在V...

网友评论

      本文标题:iOS底层-dyld加载流程分析

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