dyld源码
苹果官方资源opensource
objc4-838可编译联调源码
一、了解相关概念
1.静态库、动态库
通常程序都会依赖系统一些库, 库是什么呢? 其实库就是一些可执行的二进制文件, 能被操作系统加载到内存里面中。库分为两种:静态库 / 动态库


1.Xcode编译command + B产生可执行文件的过程:

- a.
预编译
处理源代码文件中以#开头的预编译的命令(#import/#include等等);把包含的文件插入到指定的位置;替换代码中的宏定义;删除代码里的注释等等。产生.i文件。 - b.
编译
词法分析;语法分析;语义分析;生成相应的汇编代码文件。 产生.s文件(汇编文件) - c.
汇编
将汇编代码文件翻译成机器指令,机器指令会生成.o文件。 - d.
链接
把之前所有操作的文件链接到程序里面来, 对.o文件中引用其他库的地方进行引用, 生成最后的可执行文件。动态库与静态库区别其实就是链接的区别。
2.点击App启动的过程:
- a.系统调用
exec()分配内存、开辟进程 - b.app对应的
可执行文件加载到内存 - c.
dyld动态链接器加载到内存:load dyld - d.
dyld动态链接器进行动态链接:rebase->bing->Objc->initalizers - e.调起
main()函数
启动时间的划分可以把main()函数作为关键点分割成两块

t1阶段,main()之前的处理所需时间,称为pre-main

t2阶段,main()及main()之后处理所需时间
t2阶段耗时的主要是业务代码
3.Mach-O是什么?
Mach-O: Mach Object文件格式的缩写,它是一种用于可执行文件,目标文件.o,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速度。
三种Mach-O格式的文件:可执行文件、dylib动态库、Bundle资源文件包
Mach-O由四部分组成:Mach-O头部、Load Command、section、Other Data,可以通过MachOView可查看可执行文件信息。
二、什么是dyld
本文主要介绍dyld源码执行流程。
在app启动加载过程中类和分类加载都不可避免的触及dyld,所以了解dyld源码可以让我们更好的理解app的工作原理。
什么是dyld?dyld(the dynamic link editor 动态链接器):是苹果操作系统的一个重要的组成部分。在iOS/Mac OSX系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以Mach-O镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由动态链接器dyld来完成的,也就是符号绑定。
在app被编译打包成可执行文件格式的Mach-O文件后,在程序启动时交由dyld负责链接加载程序。
dyld是开源的,可以通过苹果官网下载它的dyld源码,本文的对应的版本是941.5。dyld4是iOS15发布的。
dyld3: 相比dyld2新增预构建/闭包, 目的是将一些启动数据创建为闭包存到本地,下次启动将不再重新解析数据,而是直接读取闭包内容dyld4: 采用pre-build + just-in-time预构建/闭包+实时解析的双解析模式, 将根据缓存有效与否选择合适的模式进行解析, 同时也处理了闭包失效时候需要重建闭包的性能问题。
三、dyld工作流程

新建一个工程,在ViewController.m里重写+(void)load方法,在load方法上打个断点调试,运行app后在断点处输出其函数调用的堆栈:


新版本dyld: start开始接下来走 dyld4中prepare方法, 源码入口需要在start中开始探索。
(旧版本dyld: _dyld_start开始, 接下来走dyldbootstrap, 源码入口需要在dyld_start开始。)

1.start函数是dyld的开始

- mach消息初始化:
mach_init() - 栈溢出保护:
__guard_setup(kernArgs->findApple()); - 构建进程配置:
config = ProcessConfig(kernArgs, sSyscallDelegate);
APIs继承自RuntimeState

2.prepare:加载程序的所有依赖项

gProcessInfo的声明:struct dyld_all_image_infos* gProcessInfo = &dyld_all_image_infos;


dyld3 出于对启动速度的优化的目的, 增加了预构建(闭包)。App第一次启动或者App发生变化时会将部分启动数据创建为闭包存到本地,那么App下次启动将不再重新解析数据,而是直接读取闭包内容。当然前提是应用程序和系统应很少发生变化,但如果这两者经常变化等, 就会导闭包丢失或失效。
dyld4 采用了 pre-build + just-in-time 的双解析模式,预构建 pre-build 对应的就是 dyld3 中的闭包,just-in-time 可以理解为实时解析。当然just-in-time 也是可以利用 pre-build 的缓存的,所以性能可控。有了just-in-time, 目前应用首次启动、系统版本更新、普通启动,dyld4 则可以根据缓存是否有效去选择合适的模式进行解析。
- 装载uuid、动态库、可执行文件


记录插入信息, 遍历所有dylibs, 一些记录检查操作继续往下走
- 插入缓存

Loader::applyInterposingToDyldCache的实现:
接下来的工作是一些其他通知和写入操作。
- 运行初始化方法
3.runAllInitializersForMain:运行初始化方法
- 运行
libSystem的初始化器

- 告诉
objc在libSystem的子dylibs上运行所有的+load方法
(可执行文件、动态库等等上面的load)
notifyObjCInit 这个工作在第4部分介绍

runInitializersBottomUpPlusUpwardLinks 这个工作在第5部分介绍
4.notifyObjCInit
在libSystem的初始化时,在其子dylibs上需要通知objc运行所有的+load方法

notifyObjCInit的定义是在RuntimeState类的声明里:

_dyld_objc_notify_init 在setObjCNotifiers被赋值的:

那setObjCNotifiers是在_dyld_objc_notify_register被调用的
而_dyld_objc_notify_register是在objc4源码里的 objc_init()里被调用的

最后objc_init()在库初始化时间之前由libSystem调用。
ps: load_images的内容在下章节,知道它是用来进行加载和调用+load方法就够了。
5.link 动态库和主程序: runInitializersBottomUpPlusUpwardLinks
runInitializersBottomUp
6.调起主程序入口
在 prepare 函数调用最后返回的就是主程序入口 MainFunc
在start函数里调用了主程序入口

最后给个总结图
dyld4应用程序加载流程图

dyld3应用程序加载流程图:



