在前面的文章中介绍过,app启动过程中,首先是操作系统内核进行一些处理,比如新建进程,分配内存等。在iOS/Mac OS系统中,操作系统内核是XNU。在XNU完成相关的工作后,会将控制权交给dyld。dyld,即动态链接器,用于加载动态库。dyld是运行在用户态的,从XNU到dyld,完成了一次内核态到用户态的切换。那么,后续dyld做了哪些事情呢?幸运的是,dyld是开源的,我们通过分析dyld的源码,来看一下dyld在app启动过程中做了哪些工作。
dyld入口
在之前的文章中介绍过,dyld入口函数是__dyld_start,我们看一下__dyld_start里面做了那些操作。dyld中的部分源码是汇编语言,__dyld_start源码就是汇编。__dyld_start部分代码如下:
__dyld_start:
// 这里调用了dyldbootstrap::start()函数,此函数会完成动态库加载过程,并返回主程序main函数入口
bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
mov x16,x0 // save entry point address in x16
ldr x1, [sp]
cmp x1, #0
// LC_MAIN case, set up stack for call to main()
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib
ldr x0, [x28, #8] // main param1 = argc
add x1, x28, #16 // main param2 = argv
add x2, x1, x0, lsl #3
add x2, x2, #8 // main param3 = &env[0]
mov x3, x2
__dyld_start内部调用了dyldbootstrap::start()函数,看一下dyldbootstrap::start()内部的实现:
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
if ( slide != 0 ) {
rebaseDyld(dyldsMachHeader, slide);
}
// 调用dyld中的_main()函数,_main()函数返回主程序的main函数入口,也就是我们App的main函数地址
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
查找App main函数地址的操作主要是在_main函数中,_main函数中做了较多的操作,看一下_main()函数是如何实现的。
_main()函数
_main()函数中代码比较多,做的事情也比较多。主要完成了上下文的建立,主程序初始化成ImageLoader对象,加载共享的系统动态库,加载依赖的动态库,链接动态库,初始化主程序,返回主程序main()函数地址。接下来分别看一下每个功能的具体实现。
instantiateFromLoadedImage
instantiateFromLoadedImage()函数主要是将主程序Mach-O文件转变成了一个ImageLoader对象,用于后续的链接过程。ImageLoader是一个抽象类,和其相关的类有ImageLoaderMachO,ImageLoaderMachO是ImageLoader的子类,ImageLoaderMachO又有两个子类,分别是ImageLoaderMachOCompressed和ImageLoaderMachOClassic。这几个类之间的关系如下:
在app启动过程中,主程序和其相关的动态库,最后都被转化成了一个ImageLoader对象。看一下instantiateFromLoadedImage中做的操作。
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// 检测mach-o header的cputype与cpusubtype是否与当前系统兼容
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
}
isCompatibleMachO主要是检测mach-o文件的cputype和cpusubtype是否与当前系统兼容,之后调用了instantiateMainExecutable()函数,看一下instantiateMainExecutable()函数的实现:
// 初始化ImageLoader
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
bool compressed;
unsigned int segCount;
unsigned int libCount;
// sniffLoadCommands主要获取加载命令中compressed的值(压缩还是传统)以及segment的数量、libCount(需要加载的动态库的数量)
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#