iOS_UI

dyld加载应用启动原理详解

2021-05-11  本文已影响0人  忻凯同学

我们都知道APP的入口函数是main(),而在main()函数调用之前,APP的加载过程是怎样的呢?接下来我们一起来分析APP的加载流程。

一. 准备工作

由于load()main()调用更早,因此我们创建一个工程,在控制器中写一个load()函数,并断点运行,如下图:

运行起来之后,可以清晰的看到比较详细的函数调用顺序,从_dyld_start()dyld:notifySingle(),频率出现最多的就是这个dyld,那么dyld是什么?它在做什么?

简单来说dyld是一个动态链接器,用来加载所有的库和可执行文件。接下来我们将通过对dyld源码分析,去追踪dyld到底做了什么?

_dyld_start

二. dyld加载流程分析

1. 首先下载dyld源码

2. 打开dyld源码工程,根据上图dyldbootstrap::start为关键字搜索dyldbootstrap中调用的start(),如下图:

dyldbootstrap::start

3. 进入dyld的start函数

其中rebaseDyld()分析如下:

4. 进入dyld的main函数

注:因为dyld::main()函数代码比较多,以下会分段介绍,也会介绍相对来说比较重要的函数。

4.1 内核检测

4.2 获取main执行文件的cdHash缓存区

4.3 获取CPU信息

    // 获取CPU信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);

4.4 设置MachHeader和内存偏移量

    // 设置MachHeader和内存偏移量Slide
    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;

4.5 设置上下文

    // 设置上下文,保存信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

4.6 配置进程限制

4.7 检测环境变量

4.8 打印环境配置信息

    // 打印环境配置信息
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

此处可以自己定义环境变量配置,回到刚才创建的新工程中,在Edit Scheme -> Run -> Arguments -> Environment Variables 添加两个参数 DYLD_PRINT_OPTSDYLD_PRINT_ENV,并设置测试value值,如下:

运行程序,可看到如下打印信息:

4.9 加载共享缓存(如果没有共享缓存,iOS将无法运行)

主要函数mapSharedCache()如下:

4.10 dyld配置

(1) dyld3(闭包模式)

iOS11版本之后,引入dyld3闭包模式(ClosureMode):加载速度更快,效率更高。

开始执行闭包模式

判断是否开启了闭包模式

启动闭包模式加载

其中launchWithClosure加载闭包,会把后面说到的dyld2的大部分流程都封装到launchWithClosure()这个函数里面了,这里不再细说launchWithClosure,因为在接下来的dyld2(非闭包)中会详细解释整个dyld加载的流程,也就是launchWithClosure实现过程。

(2) dyld2(非闭包模式)

开始执行闭包模式

把dyld加入到UUID列表

    // 把dyld加入到UUID列表
    addDyldImageToUUIDList();

配置缓存代理

4.11 创建主程序的Image

开始创建主程序的Image,通过instantiateFromLoadedImage(),调用instantiateMainExecutable(),实例化具体的Image类,最后生成的对象,设置到gLinkContext中。

4.12 设置动态库的版本

    // 加载完共享缓存,设置动态库的版本
    checkVersionedPaths();

4.13 加载插入的动态库

    // 加载插入的动态库
    if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
            oadInsertedDylib(*lib);
    }

4.14 链接主程序和动态库

4.15 绑定主程序和动态库

4.16 初始化主程序

根据Demo上的堆栈信息,如下:

终于看到熟悉的函数了,那么dyld加载流程也快结束了。
根据堆栈信息,获取函数调用层级关系。

    // 初始化主程序
    initializeMainExecutable(); 
加载主程序 runInitializers processInitializers recursiveInitialization objc-os.mm

其中第二个参数就是load_images(),在load_images()中也找到了call_load_methods()

此时初始化程序还未执行完成,回到之前的 ImageLoader::recursiveInitialization()方法中。

4.17 进入主程序

    // 通知此进程将要进入程序main()
    notifyMonitoringDyldMain();

到此,start() -> main(),全部执行完毕。

三. 总结

上一篇 下一篇

猜你喜欢

热点阅读