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

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

## 前言

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

![简要加载过程](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F57883fd3e202d112a852fc1b13f328680ddc4147.png?generation=1626835564604022\&alt=media)

## 一、 切入点

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

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

![断点](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2fef4e6d38961f35177d00be56549e7e9a8d89c7.png?generation=1626835571588625\&alt=media)

```
(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之前下断点试试吧。

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F86893f4e0a7d6640bfe3a87a095fea7cd9015100.png?generation=1626835558804166\&alt=media)

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

## 二、 dyld-动态链接器

### 2.1 \_dyld\_start

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

![call dyldbootstrap::start](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F390d4852e2194c48e7638651b9cb13c0fb81cf2e.png?generation=1626835572618262\&alt=media)

***dyldbootstrap::start***

![dyldbootstrap::start](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Ff41a4faa44c74e3bed595615ce293df752f83aca.png?generation=1626835574791170\&alt=media)

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

### 2.2 dyld::\_main

![dyld::\_main](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa375a081f55c889e1cd309a52ac783a1b8e466d5.png?generation=1626835567091657\&alt=media)

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

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

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

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

![marks](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe265fce2e9d93bd96898eaf38f7a8a474f8f57fe.png?generation=1626835576914303\&alt=media)

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

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb661d9ef81d80565e5206d21fb4e15572eddd45c.png?generation=1626835561596173\&alt=media)

* 加载共享缓存

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fafaea25fb469689d985c93d1e6c060bf01601482.png?generation=1626835577579587\&alt=media)

* 为主程序初始化 ImageLoader

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fea973b8c7d438bcb4a2144661799db7bfc3e2fe9.png?generation=1626835569754713\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fec5e1ec32548911a2cec4eaa7023ce377afc018e.png?generation=1626835568570052\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F50511a4b5064a8bd631f9f44cdcf7fef504393d3.png?generation=1626835570644545\&alt=media)

* 链接主程序

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8b6ca91c38ff5d9af9064750f964fd51ee7d3aa5.png?generation=1626835559690587\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fd0ba49d755627017addcae78695420f00b561b23.png?generation=1626835563083791\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb485b646b33aa2eac70ef49c063285a1406fcf9d.png?generation=1626835578561945\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F29d4fa4c0a2136c1b63dccf9eebe748cd8552ef9.png?generation=1626835563797391\&alt=media)

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

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F46d84efa1cb64bf5e4c9df5036337153b9f4da82.png?generation=1626835560884753\&alt=media)

* 查找main指针

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fbcc3f40f0f79a55c9feb2ef2989c6f7438aa7224.png?generation=1626835567141661\&alt=media)

### 2.3 sMainExecutable

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

定位 `sMainExecutable` 的初始化

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fea973b8c7d438bcb4a2144661799db7bfc3e2fe9.png?generation=1626835569754713\&alt=media)

### 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` 处断点的堆栈信息可以得到验证：

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F93448bed1508fdfd799090aabe6e6cf96d6a3fbc.png?generation=1626835567946667\&alt=media)

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

## 三、 总结&流程图

![dyld:\_main](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F7d959cbed704a536866afabe824607dc4a2a41d0.png?generation=1626835575728226\&alt=media)

* 程序执行从 `_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`
