14.iOS应用启动(一):dyld与main函数
Last updated
Last updated
本文基于
dyld-832.7.3
与objc4-818.2
源码
每个应用程序都会依赖很多的库,每当应用程序启动,都会将MachO中的可执行文件加载到内存中。那么这个过程是怎样的呢?
应用程序的入口是 main
函数,那么 main
函数之前做了什么呢?
我们在 main
函数下个断点:
发现并没有有用的信息。那么在main之前下断点试试吧。
通过分析堆栈信息发现切入点 _dyld_start
在dyld
源码中找到了对应实现,为汇编代码,我们找到一处重要的注释:
dyldbootstrap::start
前面都是进行一些配置,最后一个看名字就很重要的 dyld::_main
我们进去看看
一千多行代码,果然很重要。
通过注释我们也可以看出:
这里是 dyld
的入口
最终返回程序的 main()
函数
因为代码非常长,我对于关键点做了一些注释方便大家对照源码进行查看
只要设置了这两个环境变量参数,在App启动时就会打印相关参数、环境变量信息
加载共享缓存
为主程序初始化 ImageLoader
现在共享缓存已经加载完毕了,设置版本化的dylib覆盖
加载所有插入的库,越狱插件在这里加入
记录插入的库的数量,以便统一搜索将先查看插入的库,然后是main,然后是其他。
链接主程序
链接所有插入的库。链接主可执行文件后执行此操作,以使插入的dylib(例如libSystem)不在程序使用的dylib的前面
只有插入的库可以插入。绑定所有插入的库后,注册插入信息,以便链接工作
绑定并通知主要可执行文件,现在插入的已被注册
绑定并通知现在已插入的已插入image已被注册
执行所有初始化方法
通知任何监视过程此过程即将进入main()
查找main指针
从上面的流程分析中可以看出,最终return
的main
函数指针 来自于 sMainExecutable
。
定位 sMainExecutable
的初始化
核心源码及流程注释
结合前面我们在 +load
处断点的堆栈信息可以得到验证:
主线历程我们就分析到这里,细节的流程后面的文章会继续进行分析
程序执行从 _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