我们都知道APP的入口函数是main(),而在main()函数调用之前,APP的加载过程是怎样的呢?接下来我们一起来分析APP的加载流程。
一、利用断点进行追踪
-
首先我们创建一个工程,什么代码都不写,在main()函数处进行断点,会看到情况如下图:
01
- 通过上图我们可以看到,在调用堆栈中,我们只看到了star和main,并开启了主线程,其它的什么都看不到。那要怎么才能看到调用堆栈详细点的信息了?我们都知道,有一个方法比main()函数调用更早,那就是load()函数,此时在控制器中写一个load函数,并断点运行,如下图:
02
- 通过上图,我们看到了比较详细的函数调用顺序,从第13行的_dyld_start到第3行的dyld:notifySingle,频率出现最多的就是这个dyld的家伙,那么dyld是什么?它在做什么?简单来说dyld是一个动态链接器,用来加载所有的库和可执行文件。接下来我们将通过图2的调用关系,去追踪dyld到底在什么?
二、 dyld加载流程分析
1. 首先下载dyld源码。
2. 打开dyld源码工程,根据图2的第12行dyldbootstrap:start为关键字搜索dyldbootstrap中调用的start方法,如下图:
3. 该方法源码如下,接下来我们对该方法的重点部分进行分析:
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], intptr_t slide)
{
// 读取macho文件的头部信息
const struct macho_header* dyldsMachHeader = (const struct macho_header*)(((char*)&_mh_dylinker_header)+slide);
// 滑块,设置偏移量,用于重定位
if ( slide != 0 ) {
rebaseDyld(dyldsMachHeader, slide);
}
uintptr_t appsSlide = 0;
// 针对偏移异常的监测
dyld_exceptions_init(dyldsMachHeader, slide);
// 初始化machO文件
mach_init();
// 设置分段保护,这里的分段下面会介绍,属于machO文件格式
segmentProtectDyld(dyldsMachHeader, slide);
//环境变量指针
const char** envp = &argv[argc+1];
// 环境变量指针结束的设置
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// 在dyld中运行所有c++初始化器
runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
// 如果主可执行文件被链接-pie,那么随机分配它的加载地址
if ( appsMachHeader->flags & MH_PIE )
appsMachHeader = randomizeExecutableLoadAddress(appsMachHeader, envp, &appsSlide);
// 传入头文件信息,偏移量等。调用dyld的自己的main函数(这里并不是APP的main函数)。
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple);
}
-
3.1 函数的参数中我们看到有一个macho_header的参数,这是一个什么东西呢?Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS中的可执行文件格式,并且有自己的文件格式目录,苹果给出的mach文件如下图: