# 14.iOS应用启动（一）：dyld与main函数

> 本文基于 `dyld-832.7.3` 与 `objc4-818.2` 源码

## 前言

每个应用程序都会依赖很多的库，每当应用程序启动，都会将MachO中的可执行文件加载到内存中。那么这个过程是怎样的呢？

![简要加载过程](/files/-Mf5x872tXgauT75mqBl)

## 一、 切入点

应用程序的入口是 `main` 函数，那么 `main` 函数之前做了什么呢？

我们在 `main` 函数下个断点：

![断点](/files/-Mf5x875N-THWLQoflPd)

```
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001001f9ef8 ObjcMsgSend`main(argc=1, argv=0x000000016fc0b878) at main.m:17:50
  frame #1: 0x00000001a2d56140 libdyld.dylib`start + 4
```

发现并没有有用的信息。那么在main之前下断点试试吧。

![](/files/-Mf5x878xKMGwoWCOrhM)

通过分析堆栈信息发现切入点 `_dyld_start`

## 二、 dyld-动态链接器

### 2.1 \_dyld\_start

在`dyld`源码中找到了对应实现，为汇编代码，我们找到一处重要的注释：

![call dyldbootstrap::start](/files/-Mf5x879kBc5sace-LVI)

***dyldbootstrap::start***

![dyldbootstrap::start](/files/-Mf5x87AN9ObOTlCIVPs)

前面都是进行一些配置，最后一个看名字就很重要的 `dyld::_main` 我们进去看看

### 2.2 dyld::\_main

![dyld::\_main](/files/-Mf5x87Bpr04pDJFXlvV)

> 一千多行代码，果然很重要。

通过注释我们也可以看出：

* 这里是 `dyld` 的入口
* 最终返回程序的 `main()` 函数

> 因为代码非常长，我对于关键点做了一些注释方便大家对照源码进行查看

![marks](/files/-Mf5x87CwagF-e13yl3x)

#### 源码流程注释对照

* 只要设置了这两个环境变量参数，在App启动时就会打印相关参数、环境变量信息

![](/files/-Mf5x87D_0VJTfZPJc9o)

* 加载共享缓存

![](/files/-Mf5x87EnkW--M4cnhPA)

* 为主程序初始化 ImageLoader

![](/files/-Mf5x87FLQ5MTp_TcvZo)

* 现在共享缓存已经加载完毕了，设置版本化的dylib覆盖

![](/files/-Mf5x87HGoVGraQB8JfP)

* 加载所有插入的库，越狱插件在这里加入
* 记录插入的库的数量，以便统一搜索将先查看插入的库，然后是main，然后是其他。

![](/files/-Mf5x87IguKIq7Lu9T16)

* 链接主程序

![](/files/-Mf5x87JOJ4_Sk33tYrd)

* 链接所有插入的库。链接主可执行文件后执行此操作，以使插入的dylib（例如libSystem）不在程序使用的dylib的前面

![](/files/-Mf5x87KLXxNmTQMTV6b)

* 只有插入的库可以插入。绑定所有插入的库后，注册插入信息，以便链接工作

![](/files/-Mf5x87Ol2wGGDmxvEyJ)

* 绑定并通知主要可执行文件，现在插入的已被注册
* 绑定并通知现在已插入的已插入image已被注册

![](/files/-Mf5x87PLfQ34sgnoVLh)

* 执行所有初始化方法
* 通知任何监视过程此过程即将进入main()

![](/files/-Mf5x87Q_p4eqaOUWRlh)

* 查找main指针

![](/files/-Mf5x87RBkZchiMNgeZk)

### 2.3 sMainExecutable

从上面的流程分析中可以看出，最终`return`的`main`函数指针 来自于 `sMainExecutable`。

定位 `sMainExecutable` 的初始化

![](/files/-Mf5x87Sa5_UNCxks29-)

### 2.4 initializeMainExecutable 初始化主程序

核心源码及流程注释

```cpp
void initializeMainExecutable()
{
#pragma mark - Ryukie 记录一下，进入初始化流程了
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;
#pragma mark - Ryukie 初始化所有插入的库
    // 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]);
        }
    }
#pragma mark - Ryukie 运行主要可执行文件的初始化程序及其依赖的
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);

    ...
}
```

结合前面我们在 `+load` 处断点的堆栈信息可以得到验证：

![](/files/-Mf5x87TcUn12RjWreNs)

> 主线历程我们就分析到这里，细节的流程后面的文章会继续进行分析

## 三、 总结&流程图

![dyld:\_main](/files/-Mf5x87Ui11RYNQAWQOR)

* 程序执行从 `_dyld_start` 开始
* 进入 `dyld_main` 函数
* 配置环境变量以及 `rebase_dyld`
* 加载`共享缓存`
  * `系统的动态库`都在这里了
* `dyld2/dyld(ClosureMode)` 决定以哪种模式继续进行
* 实例化主程序`ImageLoaderMachO`，加入到`allImages`中
* 到此`共享缓存`加载完毕，设置版本化的`dylib`覆盖
  * Now that shared cache is loaded, setup an versioned dylib overrides
* 加载插入的库
  * `越狱插件`在这里加入
* 记录插入的库的数量，以便统一搜索将先查看插入的库，然后是main，然后是其他。
  * record count of inserted libraries so that a flat search will look at inserted libraries, then main, then others.
* 链接主程序
  * `绑定符号（非懒加载、弱符号）等`
* 链接所有插入的库
  * 链接主可执行文件后执行此操作，以使插入的dylib（例如libSystem）不在程序使用的dylib的前面
  * 只有插入的库可以插入。绑定所有插入的库后，注册插入信息，以便链接工作
* 绑定并通知`主程序可执行文件`，现在插入的已被注册
* 绑定并通知`现在已插入的库`已插入的Image已被注册
* 执行所有初始化方法：`ImageLoader`
  * 初始化所有插入的库、运行主要可执行文件的初始化程序及其依赖
  * `ImageLoader`:
    * `runInitializers`:
      * `processInitializers`:
        * `ecursiveInitialization`:
          * `notifySingle`:
            * 此函数执行一个回调，此回调是`_objc_init`初始化时服饰的一个函数`load_images`
              * `load_images`里执行`class_load_metgods`函数
                * `class_load_metgods`里调用`call_class_loads`函数：循环调用各个类的`load`函数
          * `doModInitFunction`
            * 内部会调用全局C++对象的构造函数`__attribute__((constructor))`的C函数
* 通知任何监视过程此过程即将进入`main()`
* 得到`main()`指针并`return`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/14.ios-ying-yong-qi-dong-yi-dyldstart.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
