+load和main()谁先调用?有经验的iOSer们会绝不犹豫的回答出来是load方法,但为何是load方法呢?今天咱们来探讨一下底层的原理html
新建一个项目,在AppDelegate里添加load方法,打上一个断点就会看到以下图所示的调用堆栈,若是嫌左侧太长了看不全,也能够在控制台输入bt指令查看调用堆栈 bootstrap
从调用堆栈中咱们能够看到,程序由dyld的_dyld_start
函数开始,一步一步的层层调用,最终到了咱们Demo程序的[AppDelegate load]
方法中,看上去这个dyld也是一个程序,就是这个dyld程序在启动咱们的APP缓存
dyld(the dynamic link editor)是苹果的动态连接器,是苹果操做系统一个重要组成部分,在系统内核作好程序准备工做以后,交由dyld负责余下的工做。并且它是开源的,任何人能够经过苹果官网下载它的源码来阅读理解它的运做方式,了解系统加载动态库的细节。WWDC从2016,2017到2019都有session对APP启动的过程以及如何优化作过介绍,直到WWDC2019(iOS 13)才把Dyld3开放给全部APP,在iOS 13系统中,iOS将全面采用新的dyld 3以替代以前版本的dyld 2。 由于dyld 3彻底兼容dyld 2,API接口是同样的,因此在大部分状况下,开发者不须要作额外的适配就能平滑过渡。如今网上大多数关于dyld的文章介绍都是基于dyld2版本的,虽然如今已是dyld3版本了,但dyld3也并不是是对dyld2的彻底重构,dyld2里的主要流程在dyld3里面依然存在,因此我这篇文章也会先介绍一下dyld2中的9个主要流程,最后再简单介绍一下dyld3作了哪些优化安全
dyld下载地址:opensource.apple.com/tarballs/dy…markdown
咱们先跟着刚刚的调用堆栈来跟踪一遍,首先打开咱们刚刚下载的dyld源码,搜索咱们刚刚在调用堆栈里看到的最外层的调用_dyld_start
session
_dyld_start
从截图中能够看到,搜索结果的第一部分和第二部分,都不多是咱们想要找的东西,第一部分.xcconfig像是配置文件,第二部分都是注释;那么确定只有第三部分了,其实一开始看到这个第三部分dyldStartup.s文件心里是崩溃的...(做为非科班iOSer,虽然学过C,Objective-C,Swift也了解过一点点C++,可是这个.s文件真不认识)点开这个.s文件后,看到一堆指令,猜想应该是汇编代码了,这下头更大了,不过好在是汇编,估计你们都挺难懂的,因此苹果的注释给的也很到位,根据搜索结果综合注释来看,找到arm64架构而且不是模拟器的这部分也还算简单,而后看到bl指令上面有一段注释,跟咱们刚刚调用堆栈里面的第8帧是如出一辙的就能猜到个大概了...dyld程序由_dyld_start开始,执行到这个bl指令后开始调用start函数了,那么接下来就是搜索这个start函数在哪了闭包
start
这个start一开始还很差搜,直接搜start会发现有3125个结果在363个文件里...做者表示看到这个结果头很大,因而想了个办法,我要找的是个函数的声明,那么后面一定紧跟着一个(符号,从新搜索一番后发现好了不少,只有173个结果在118个文件了;头虽然没那么大了,可是这173个结果要一个个去找去看,也仍是蛮费时间的...最后灵机一动,尝试在start(前面加一个空格,好家伙,这不就出现了么,结合一下注释,还有函数的参数和调用堆栈第8帧里如出一辙的就肯定了;若是是有C++基础的同窗应该就更加好找了dyldbootstrap是命名空间直接搜就会找到一份文件,再在该文件里搜start(很快就找到了 函数的return的前面一行有一个appsSlide变量,这个变量是ASLR地址空间配置随机加载技术的应用,这是是一种防范内存损坏漏洞被利用的计算机安全技术
这个start函数里面的最后一行代码调用了_main()函数,这个比较舒服,不须要咱们再去找了,直接按住command而后左键点击就能够跳转到对应的实现代码了架构
_main
这个_main函数就是启动咱们APP的关键代码,从它的行数就能够看出来它的份量了,从6455行到7303一共848行(这里就不贴它的所有源码了)好家伙,仍是头一回看到一个C函数写这么长的,原本是想吐槽一下的,但一想到这是苹果的工程师写的底层代码,咱仍是老老实实看源码...这部分的代码就是今天重点中的重点,咱们在下一部分重点讲这个函数,如今仍是接着调用堆栈继续日后面走,后面的几个都挺好找的 app
initializeMainExecutable
这个initializeMainExecutable
函数在_main
里面调用了两次,不过是分不一样架构的,也是能够直接command加左键定位到函数实现的,从这个函数里的注释中能够看到,是先执行的全部插入的库的initialzers方法,再执行咱们主程序的initialzers方法的,这也说明了咱们写的Framework中的load方法会比咱们主程序的load方法先执行;若是不认为run initialzers就是调用load方法,能够跟着调用堆栈流程走完,你就会知道究竟是不是了 ide
runInitializers
这个runInitializers
函数也是在上面initializeMainExecutable
函数里面调用了两次,从两次调用的注释来看,上面调用的是全部插入的动态库的初始化方法,而下面的才是咱们主程序调用初始化方法,因此在[AppDelegate laod]
方法中打的断点卡住的应该是下面的这段代码;这个函数也比较好找,直接搜runInitializers(只有12个结果在5个文件里,载结合它前面的ImageLoader做用域,函数的参数,很快就能找到它的实现
processInitializers
结合调用堆栈,找到runInitializers
里面的processInitializers
函数实现很容易,找到processInitializers
函数的实现也很简单,能够直接command加左键点击就到了
recursiveInitialization
一样是在ImageLoader.cpp文件内,processInitializers
就能直接command加左键定位到实现代码,而recursiveInitialization
却不能够,不知道为何,不过也没什么大问题,recursiveInitialization
在当前文件一搜就找到实现了 这里有个小细节能够说一下,在第一次content.notifySingle()以后有一个
doInitialization
函数,这个函数 里面又会有两个初始化函数 doInitialization()内部首先调用doImageInit来执行镜像的初始化函数,也就是LC_ROUTINES_COMMAND中记录的函数。 再执行doModInitFunctions()方法来解析并执行
_DATA_,__mod_init_func
这个section中保存的函数。使用__attribute__((constructor))
开头的C函数会保存在这里面,如图所示:
notifySingle
notifySingle
的调用一样是在上一个函数recursiveInitialization
的实现里面,可是notifySingle
的实现结合调用堆栈它前面的做用域来看,不在ImageLoader里面,那就直接全局搜索notifySingle(,也比较容易找 接下来,由
notifySingle
到load_images
会发现调用的地方已经不是在dyld了...那么如何实现代码的执行从一个程序跳到另外一个程序呢?有不少种办法,通知,代理,block,函数做为参数传递,咱们仔细观察一下notifySingle
里面有没有以上任何一种,会发现下面这里有一个不太同样的地方sNotifyObjCInit
那接下来就看看这个
sNotifyObjCInit
变量是在哪里被赋值的,搜索一番后发如今这里被赋值了 紧接着搜一搜这个
registerObjCNotifiers
在哪里被调用了 搜索一番后发现这里是dyld提供的对外部的接口...那就说明咱们在dyld里面应该是找不到这个调用的地方了,不过至少咱们找到了这个对外的接口函数
_dyld_objc_notify_register
,这个时候,咱们能够回到最开始新建的项目中去,下一个_dyld_objc_notify_register
的符号断点 会发现是libobjc.A.dylib的_objc_init里面调用了咱们的
_dyld_objc_notify_register
函数,这个libobjc.A.dylib咱们不是头一回看到了,前面的调用堆栈第1帧也是在libobjc.A.dylib的load_images函数,那么这个libobjc.A.dylib究竟是什么库呢?其实这个libobjc.A.dylib就是咱们的Objective-C的运行时库,好消息是这个库苹果依旧开源了出来,能够免费供你们学习,我这里下载的是objc4-818.2
OC运行时库下载:opensource.apple.com/tarballs/ob…
打开下载的objc4源码,找到Products目录,是否是能够看到一个很是眼熟的东西
这样看来咱们真的找对地方了,那就直接全局搜索咱们刚刚获取到的_dyld_objc_notify_register
函数,看看是否是_objc_init
里面调用了它 果真如此,看到这里,就会发现dyld里面的
sNotifyObjCInit
变量是被赋值了一个load_images
的函数,到这里就彻底能解释的通,从调用堆栈的第2帧到第1帧的执行了,这个load_images
能够直接按住command点击定位到实现代码
load_images
接下来就是看怎么从libobjc.A.dylib的load_images函数到咱们的断点[AppDelegate load]
方法的了 很明显,咱们须要查看
call_load_methods
函数 在这个函数里,咱们就能够明显的看到先是调用全部类Class的load方法,而后再调用全部分类category中的load方法,查看
call_class_loads
的实现 到这里,咱们断点卡住的全部调用堆栈就所有跟踪完毕了
须要了解的是,这个时候咱们依然还在dyld的_main
函数里面,连_main
里面的initializeMainExecutable
都没有执行完...而咱们主程序的main()是在何时调用的呢?dyld的_main
函数的返回值result就是咱们主程序的入口,_main
执行完毕以后,会把返回值返回到start
函数,start
函数又会把返回值返回到咱们的最初的入口_dyld_start
里面的那条bl
指令后,x0
就是返回的咱们主程序入口,接下来一个mov x16,x0
看注释也知道是将主程序入口地址保存到x16
了,再搜索一下x16
发现后面基本都是各类状况下的br
或者braaz
到x16
,就是跳转到咱们的主程序了,因此咱们APP的main函数远远晚于load方法的调用
上面调用堆栈里这么多函数里面,最复杂最长的就是这个_main
了,其实这个函数里面才是dyld启动咱们APP的主要流程,可是这个函数实在太长了,咱们将这个函数分红9个主要的部分,我向来讨厌在文章里面贴那种几页几页都翻不完的代码块的,因此下面的介绍都尽可能精简
getHostInfo()获取当前程序架构
getHostInfo(mainExecutableMH, mainExecutableSlide);
复制代码
接着调用setContext()设置上下文信息,包括一些回调函数、参数、标志信息等。设置的回调函数都是dyld模块自身实现的,如loadLibrary()函数实际调用的是libraryLocator(),负责加载动态库。代码片段以下:
static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])
{
gLinkContext.loadLibrary = &libraryLocator;
gLinkContext.terminationRecorder = &terminationRecorder;
......
复制代码
configureProcessRestrictions()用来配置进程是否受限
checkEnvironmentVariables()检查环境变量
细心的读者可能会注意到,整个过程当中有一些DYLD_PRINT_开头的环境变量,好比:
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
复制代码
若是在Xcode中配置了这些环境变量,就会在咱们的控制台中打印相关的信息: 除了上面的两个外,我下面以另一个打印启动时间的环境变量DYLD_PRINT_STATISTICS_DETAILS
为例,这个应该能够做为优化启动时间的参考数据
这里先说明一下,iOS的共享缓存机制:在iOS系统中,每一个程序依赖的动态库都须要经过dyld一个个加载到内存,然而,不少系统库基本上是每一个程序都会用到的(好比UIKit,Foundation...),若是每一个程序启动运行的时候都重复的去加载一次,势必会形成运行缓慢,没必要要的内存消耗,为了优化启动速度和节约内存消耗,共享缓存机制就出现了;这里还要说一句,在iOS中,只有系统库才能称为真正意义上的动态库,咱们普通开发者开发的各类格式的库,都只能是静态库;全部默认的动态连接库被合并成一个大的缓存文件,放在/System/Library/Caches/com.apple.dyld/
目录下,按不一样的架构分别保存,想要分析某个系统库,能够从dyld_shared_cache里将原始的二进制文件提取出来;感兴趣的能够根据个人第一篇参考文章上的步骤去尝试一下
这一步先调用checkSharedRegionDisable()检查共享缓存是否禁用。该函数的iOS实现部分仅有一句注释,从注释咱们能够推断iOS必须开启共享缓存才能正常工做,代码以下: 接下来调用mapSharedCache()加载共享缓存,而mapSharedCache()里面实则是调用了loadDyldCache(),从代码能够看出,共享缓存加载又分为三种状况:
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->errorMessage = nullptr;
#if TARGET_OS_SIMULATOR
// simulator only supports mmap()ing cache privately into process
return mapCachePrivate(options, results);
#else
if ( options.forcePrivate ) {
// mmap cache into this process only
return mapCachePrivate(options, results);
}
else {
// fast path: when cache is already mapped into shared region
bool hasError = false;
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// slow path: this is first process to load cache
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
#endif
}
复制代码
mapCachePrivate()、mapCacheSystemWide()里面就是具体的共享缓存解析逻辑,感兴趣的读者能够详细分析。
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
复制代码
分析一下函数的名称以及参数,从已加载的镜像实例化,由于传入的是咱们的主程序的MachO头,主程序Slide和路径,因此实例化出来的就是咱们的主程序,代码以下:
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
复制代码
ImageLoaderMachO::instantiateMainExecutable()函数里面首先会调用sniffLoadCommands()函数来获取一些数据,包括:
compressed
若是若Mach-O存在LC_DYLD_INFO和LC_DYLD_INFO_ONLY加载命令或LC_DYLD_CHAINED_FIXUPS加载命令,则说明是压缩类型的Mach-O,代码片断以下:switch (cmd->cmd) {
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
if ( cmd->cmdsize != sizeof(dyld_info_command) )
throw "malformed mach-o image: LC_DYLD_INFO size wrong";
dyldInfoCmd = (struct dyld_info_command*)cmd;
*compressed = true;
break;
case LC_DYLD_CHAINED_FIXUPS:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_DYLD_CHAINED_FIXUPS size wrong";
chainedFixupsCmd = (struct linkedit_data_command*)cmd;
*compressed = true;
break;
复制代码
segCount
根据 LC_SEGMENT_COMMAND 加载命令来统计段数量,这里抛出的错误日志也说明了段的数量是不能超过255个,代码片断以下:if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
复制代码
libCount
根据 LC_LOAD_DYLIB、LC_LOAD_WEAK_DYLIB、LC_REEXPORT_DYLIB、LC_LOAD_UPWARD_DYLIB 这几个加载命令来统计库的数量,库的数量不能超过4095个。代码片断以下:case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
*libCount += 1;
......
if ( *libCount > 4095 )
dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
复制代码
codeSigCmd
经过解析LC_CODE_SIGNATURE来获取代码签名加载命令,代码片断以下:case LC_CODE_SIGNATURE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong";
// <rdar://problem/22799652> only support one LC_CODE_SIGNATURE per image
if ( *codeSigCmd != NULL )
throw "malformed mach-o image: multiple LC_CODE_SIGNATURE load commands";
*codeSigCmd = (struct linkedit_data_command*)cmd;
break;
复制代码
encryptCmd
经过LC_ENCRYPTION_INFO和LC_ENCRYPTION_INFO_64来获取段的加密信息,代码片断以下:case LC_ENCRYPTION_INFO:
if ( cmd->cmdsize != sizeof(encryption_info_command) )
throw "malformed mach-o image: LC_ENCRYPTION_INFO size wrong";
// <rdar://problem/22799652> only support one LC_ENCRYPTION_INFO per image
if ( *encryptCmd != NULL )
throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO load commands";
*encryptCmd = (encryption_info_command*)cmd;
break;
case LC_ENCRYPTION_INFO_64:
if ( cmd->cmdsize != sizeof(encryption_info_command_64) )
throw "malformed mach-o image: LC_ENCRYPTION_INFO_64 size wrong";
// <rdar://problem/22799652> only support one LC_ENCRYPTION_INFO_64 per image
if ( *encryptCmd != NULL )
throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO_64 load commands";
*encryptCmd = (encryption_info_command*)cmd;
break;
复制代码
ImageLoader是抽象类,其子类负责把Mach-O文件实例化为image,当sniffLoadCommands()解析完之后,根据compressed的值来决定调用哪一个子类进行实例化,代码以下:
// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
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);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
复制代码
在完成实例化以后,将返回的image加入到sAllImages加入到全局镜像列表,并将image映射到申请的内存中;至此,初始化主程序这一步就完成了
这一步是加载环境变量DYLD_INSERT_LIBRARIES中配置的动态库,先判断环境变量DYLD_INSERT_LIBRARIES中是否存在要加载的动态库,若是存在则调用loadInsertedDylib()依次加载,代码以下:
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
复制代码
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
复制代码
这一步调用link()函数将实例化后的主程序进行动态修正,让二进制变为可正常执行的状态。link()函数内部调用了ImageLoader::link()函数,从源代码能够看到,这一步主要作了如下几个事情:
这一步与连接主程序同样,将前面调用addImage()函数保存在sAllImages中的动态库列表循环取出并调用link()进行连接,须要注意的是,sAllImages中保存的第一项是主程序的镜像,因此要从i+1的位置开始,取到的才是动态库的ImageLoader:
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
}
复制代码
initializeMainExecutable
initializeMainExecutable();
复制代码
这个函数在咱们刚刚的调用堆栈流程跟踪里面讲到过,咱们Objective-C对象的load方法,库中的load方法,还有C++的初始化方法都在这里面被执行了
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
}
复制代码
调用getEntryFromLC_MAIN(),从Load Command读取LC_MAIN入口;若是没有LC_MAIN入口,就读取LC_UNIXTHREAD(),而后返回给start
函数,再返回到_dyld_start
走完剩下的汇编代码,能够看到最后的汇编代码跳转到了咱们程序的入口jump to the program's entry point
在 iOS 13 以前,全部的第三方 App 都是经过 dyld 2 来启动 App 的,主要过程就是上面所讲的9大步骤
上面的全部过程都发生在 App 启动时,包含了大量的计算和I/O,因此苹果开发团队为了加快启动速度,在 WWDC2017 - 413 - App Startup Time: Past, Present, and Future 上正式提出了 dyld3。
dyld 3并非WWDC19推出来的新技术,早在2017年就被引入至iOS 11,当时主要用来优化系统库。如今,在iOS 13中它也将用于启动第三方APP。dyld 3最大的特色就是部分是进程外的且有缓存的,在打开APP时,实际上已经有很多工做都完成了。
在dyld 2的加载流程中,Parse mach-o headers和Find Dependencies存在安全风险(能够经过修改mach-o header及添加非法@rpath进行攻击),而Perform symbol lookups会耗费较多的CPU时间,由于一个库文件不变时,符号将始终位于库中相同的偏移位置,这两部分在dyld 3中将采用提早写入把结果数据缓存成文件的方式构成一个”lauch closure“(能够理解为缓存文件)。
它处理了全部可能影响启动速度的 search path,@rpaths 和环境变量;它解析 mach-o 二进制文件,分析其依赖的动态库,而且完成了全部符号查找的工做;最后它将这些工做的结果建立成了启动闭包,写入缓存,这样,在应用启动的时候,就能够直接从缓存中读取数据,加快加载速度。 这是一个普通的 daemon 进程,可使用一般的测试架构。 out-of-process是一个普通的后台守护程序,由于从各个APP进程抽离出来了,能够提升dyld3的可测试性。
验证”lauch closures“是否正确,把dylib映射到APP进程的地址空间里,而后跳转到main函数。此时,它再也不须要分析mach-o header和执行符号查找,节省了很多时间。
iOS操做系统内置APP的”lauch closure“直接内置在shared cache共享缓存中,咱们甚至不须要打开一个单独的文件;而对于第三方APP,将在APP安装或更新版本时(或者操做系统升级时?)生成lauch closure启动闭包,由于那时候的系统库已经发生更改。这样就能保证”lauch closure“老是在APP打开以前准备好。启动闭包会被写到到一个文件里,下次启动则直接读取和验证这个文件。
dyld 3 把不少耗时的查找、计算和 I/O 的事前都预先处理好了,这使得启动速度有了很大的提高。dyld3在_main
函数里面和dyld2最大的不一样之处在于,在dyld2的第三步和第四步之间,插入了使用Closure启动的逻辑。在最新的dyld源码里面能够看到,在第三步加载共享缓存以后,会判断sClosureMode
模式,并尝试经过Closure的方式启动,若是启动成功了就直接return了,后面的代码就不执行了,固然launchWithClosure()
里面会有dyld3的新处理逻辑,感兴趣的同窗能够自行前往查看源码
if ( sClosureMode == ClosureMode::Off ) {
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
...
// try using launch closure
if ( mainClosure != nullptr ) {
...
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
...
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
result = (uintptr_t)&fake_main;
return result;
}
}
复制代码
整体来讲,dyld 3把不少耗时的操做都提早处理好了,极大提高了启动速度。了解dyld对APP的启动过程有一个更全面的认识,对APP的安全防御,对APP启动速度的优化都须要对dyld有深刻的理解。
这篇文章主要参考了如下几篇文章: