前言
之前的文章分析的是main函数之后的底层流程,那么main函数之前底层的流程是怎么样子的呢?我们这篇文章就得到了很好的分析,请往下看!
准备源码
应用程序加载原理
编译过程

- 源文件:
.m/.h/.cpp等文件。 - 预处理:在预处理的时候,注释被删除、条件编译被处理、头文件展开、宏被替换。
- 编译:进行词法分析语法分析以及中间层
IR文件,最后生成汇编文件.s文件。 - 汇编:将
.s文件转换成机器语言生成.o文件。 - 链接:将所有的
.o文件以及链接的第三方库,生成一个macho类型的可执行文件。
静态库与动态库
什么是库
库就是可执行的二进制文件,可以被系统加载到内存的。库也分为动态库和静态库。它们之间的区别就是链接的区别。
-
动态库:程序编译不会链接到目标代码中,而是程序运行时才被载入。 -
静态库:在链接阶段会将汇编生成的目标和引用库一起链接打包到可执行文件中。
动静态库链接流程

静态库的优缺点
- 优点:静态库被打包到可执行文件(
mach-o文件)中可以独立运行,不需要依赖外部环境。 - 缺点:编译文件
相对较大,影响编译速度。如果要添加新的静态库需要重新编译。
动态库的优缺点
- 优点:
- 使得可执行文件变得
更小,编译速度更快。 -
共享内容,资源共享。 - 可以
动态地进行添加,得到程序更新的目的。
- 使得可执行文件变得
- 缺点:不能够
独立运行,需要依赖外部环境。
dyld加载库到内存的原理图

dyld是动态链接器,是苹果系统的重要组成部分。
dyld初探
既然是dyld加载的库,那么在加载完成后肯定会进入main函数,那么在main函数上打个断点。

通过打印堆栈信息,在
main函数进来之前的确启动了dyld,但是start到main的过程也没有反馈出来,因为+load方法实在main函数之前调用的,呢么我们可以尝试在+load方法中打上断点并查看堆栈的信息。
根据
+load方法的堆栈信息打印,看出了基本的流程如下:_dyld_start --> dyldbootstrap::start --> dyld::_main --> initializeMainExecutable --> runInitializers --> processInitializers --> runInitializers -->recursiveInitialization --> notifySingle --> load_images -->+[ViewController load]
添加符号断点(_dyld_start)参看汇编信息:

汇编中
_dyld_start之后调用的是dyldbootstrap::start方法。在dyld源码中全局搜索dyldbootstrap::start发现没有实现的地方都是汇编:
在全局搜索
dyldbootstrap:
dyldbootstrap是一个命名空间,在dyldInitialization.cpp文件中搜索start方法:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
//告知debugServer,dyld要启动
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
//重新绑定dyld
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
//栈溢出的保护,保持栈平衡
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
_subsystem_init(apple);
//获取ASLR(偏移值)
uintptr_t appsSlide = appsMachHeader->getSlide();
//返回dyld调用main函数的返回值
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
- 因为App一启动系统就会自动给App随机分配ASLR。dyld需要重定位因为它需要到当前进程中获取自己的信息。
- 调用dyld::_main函数,获得返回结果。
dyld::_main
查看dyld::_main源码,发觉源码还是比较多的,有大约1000行。那么经过分拣得到如下的关键代码:
dyld环境配置
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char*
apple[], uintptr_t* startGlue)
{
.......
//系统内核检测
//Check and see if there are any kernel flags
dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));
......
//获取主程序的hash值
uint8_t mainExecutableCDHashBuffer[20];
// 主程序的hash值初始化是0
const uint8_t* mainExecutableCDHash = nullptr;
if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
...
mainExecutableCDHash = mainExecutableCDHashBuffer;//赋值
}
//根据macho头文件配置CPU架构的信息,就是一些文件配置
getHostInfo(mainExecutableMH, mainExecutableSlide);
......
uintptr_t result = 0; // result就是main函数的地址
sMainExecutableMachHeader = mainExecutableMH;//可执行文件的头文件
sMainExecutableSlide = mainExecutableSlide; //加载到进程系统自动提供ASLR虚拟内存偏移
{
__block bool platformFound = false;
//验证主程序是什么架构的是arm64还是x86的是64位的还是32位的
((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform
(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
gProcessInfo->platform = (uint32_t)platform;
platformFound = true;
});
}
...
//设置上下文 就会把信息保存起来,保存到 gLinkContext中
setContext(mainExecutableMH, argc, argv, envp, apple);
...
//文件是否是受限的,AFMI 苹果移动文件保护机制
configureProcessRestrictions(mainExecutableMH, envp);
...
// set again because envp and apple may have changed or moved
//再次更新上下文信息
setContext(mainExecutableMH, argc, argv, envp, apple);
//环境变量的配置,xcode配置环境变量控制台可以打印信息
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
}
其实就是一些读取macho的头文件信息,保存设置,以及环境变量的设置。明显就是准备工作。
加载共享缓存
共享缓存是iOS系统中重要的组成部分,共享缓存是系统级别的动态库。比如iOS开发中常用的UIKit或者CoreFoundation等等就是在共享缓存中加载进来的。自己创建的动态库或者第三方的动态库不会放在共享缓存中。

-
checkSharedRegionDisable方法是检测是否需要不同的架构是否需要共享缓存。 -
mapSharedCache是加载共享缓存。
checkSharedRegionDisable

checkSharedRegionDisable其实就是共享缓存检测结果信息的提示。最后的提示明确了iOS不能脱离共享缓存运行。
loadDyldCache
在mapSharedCache加载共享缓存的源码实现中调用了loadDyldCache方法,明显这就是核心方法。那么查看loadDyldCache方法源码的实现如下:

加载共享缓存有三种情况:
-
强制私有:forcePrivate = YES表示强制私有,只加载到当前App进程中,不放在共享缓存中。 -
缓存已经加载:如果依赖库在共享缓存中加载过,那么直接在共享缓存中直接用就可以了,无需重新加载进来。 -
第一次加载:如果你依赖的库共享缓存中没有,它就会被加载到共享缓存中。
共享缓存总结:
通过源码的分析,对共享缓存的理解已经比较的清晰。系统缓存是系统级别的动态库,那么加载到共享缓存中的库只能是系统自带的库,自定义的动态库或者第三方的动态库是无法加载到共享缓存中的。这样的目的是让更多的进程在共享缓存中使用系统库,提高系统效率。
dyld3或dyld2
dyld3有叫做闭包模式它的加载速度更快,效率更高。IOS11以后主程序都是用dyld3加载,IOS13以后动态库和三方库用dyld3加载。
系统如何选择dyld版本
在main函数实现的6841行有以下的代码操作:
//判断是否使用闭包模式也是dyld3的模式启动 ClosureMode::on 用dyld3 否则使用dyld2
if ( sClosureMode == ClosureMode::Off ) {
//dyld2
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
//dyld3 DYLD_LAUNCH_MODE_USING_CLOSURE 用闭包模式
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
dyld3::closure::LoadedFileInfo mainFileInfo;
//主程序中的info与header
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
...
// 首先到共享缓存中去找是否有dyld3的mainClosure
if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
...
}
...
//如果共享缓存中有,然后去验证closure是否是有效的
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo,
、mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
bool allowClosureRebuilds = false;
if ( sClosureMode == ClosureMode::On ) {
allowClosureRebuilds = true;
}
...
//如果没有在共享缓存中找到有效的closure 此时就会自动创建一个closure
if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
...
if ( mainClosure == nullptr ) {
// 创建一个mainClosure
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp,
bootToken);
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
// try using launch closure
// dyld3 开始启动
if ( mainClosure != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
...
//启动 launchWithClosure
bool launched = launchWithClosure(mainClosure,
sSharedCacheLoadInfo.loadAddress,(dyld3::MachOLoaded*)mainExecutableMH,...);
//启动失败
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
// 如果启动失败 重新去创建mainClosure
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo,
envp, bootToken);
if ( mainClosure != nullptr ) {
...
//dyld3再次启动
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress,
(dyld3::MachOLoaded*)mainExecutableMH,...);
}
}
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
//启动成功直接返回main函数的地址
result = (uintptr_t)&fake_main;
return result;
}
else {
//启动失败,打印异常信息
}
}
}
-
dyld3在启动过程中会尝试多次的启动,这是系统的容错操作。一般情况下不会出现启动失败的情况。 - 如果不启用
dyld3就会选择启动dyld2,请往下看
// could not use closure info, launch old way
// 用dyld2的模式,不用dyld3
sLaunchModeUsed = 0;
// install gdb notifier
stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
// make initial allocations large enough that it is unlikely to need to be re-alloced
sImageRoots.reserve(16);
sAddImageCallbacks.reserve(4);
sRemoveImageCallbacks.reserve(4);
sAddLoadImageCallbacks.reserve(4);
sImageFilesNeedingTermination.reserve(16);
sImageFilesNeedingDOFUnregistration.reserve(8);
#if !TARGET_OS_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
file generation process
WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif
try {
// add dyld itself to UUID list
addDyldImageToUUIDList();
......
}
注意:因为dyld2已经逐渐被dyld3取代,那么我们主要是研究dyld3的加载过程,dyld2就不需要了。
dyld3启动流程总结
- 创建
dyld3的实例mainClosure。 - 再去共享缓存中查找有效的
mainClosure,如果有直接启动。 - 如果
没有,创建mainClosure,赋值给空的mainClosure。 - 启动
mainClosure,启动dyld3。 - 启动成功以后,主程序启动成功,
result就是main函数的地址,返回到dyldbootstrap::start方法,然后进入mian函数。
实例化主程序
通过分析源码,发现dyld3与dyld2其实加载流程是一样的,不一样的是dyld3是闭包模式。image在源码中经常出现,image并不是图片的意思,而是镜像文件,镜像文件就是从磁盘映射到内存的macho文件。可以理解为只要是加载到内存的macho文件就叫镜像文件。

实例化主程序就是把需要的主程序的部分信息加载到内存中,通过instantiateMainExecutable方法返回ImageLoader类型的实例对象,然后对主程序进行签名。
将实例化的image添加到镜像文件的数组中,
值得注意的点是主程序的镜像文件是第一个添加到镜像文件数组中的。
ImageLoaderMachO::instantiateMainExecutable

此方法其实就是读取
mach-o(镜像文件)的Header信息。加载
macho文件中command信息,以及校验。sniffLoadCommands中代码取部分重要的探究。(laodCommand记录着动态库的加载顺序,有那些动态库需要加载等等信息)
由上图面代码分析可知道:
-
sniffLoadCommands中加载segment和commod信息,以及一些校验。 -
segment段的个数最大是256个。 -
command的个数最大是4096个。 - 确保必须依赖了
libSystem库。
使用machOView查看mach-o文件

macho文件主要是3块内容Header、Commods、Data。需要细致的mach-o结构自己自行操作就要可以了哦!
插入动态库

通过
loadInsertedDylib插入动态库,此时有的动态库数量是所有的镜像文件减1。(因为第一个动态库为主程序)
链接主程序

link方法中调用了imageLoader::link方法,ImageLoader负责加载image文件(主程序,动态库)每个image对应一个ImageLoader类的实例。
link的主要流程如下:
- 递归加载所有的
动态库。 - 递归
image重定位。 - 递归绑定
非懒加载。 -
弱绑定。
recursiveLoadLibraries递归动态库,代码实现
void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool
preflightOnly, const RPathChain& loaderRPaths, const char* loadPath){
...
// get list of libraries this image needs
//获取当前的image依赖的动态库
DependentLibraryInfo libraryInfos[fLibraryCount];
this->doGetDependentLibraries(libraryInfos);
// get list of rpaths that this image adds
//获取当前的image依赖的动态库的文件路径
std::vector<const char*> rpathsFromThisImage;
this->getRPaths(context, rpathsFromThisImage);
const RPathChain thisRPaths(&loaderRPaths, &rpathsFromThisImage);
// 加载image依赖的动态库
for(unsigned int i=0; i < fLibraryCount; ++i){
...
dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(),
&thisRPaths, cacheIndex);
// 保存加载的动态库
setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward);
...
`}`
//告诉image依赖的动态库去加载各自需要的动态库
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
dependentImage->recursiveLoadLibraries(context, preflightOnly,
thisRPaths, libraryInfos[i].name);
}
}
}
因为recursiveLoadLibraries方法的实现代码比较多,我们就提取了比较重要的代码进行了分析,得出recursiveLoadLibraries方法只要做以下的步骤:
- 获取当前
image依赖的动态库和动态库的文件路径。 - 加载
image依赖的动态库,并进行了保存。 - 告诉
image依赖的动态库去加载各自需要的动态库。(dyld是系统级别的,里面有image数组)
链接动态库

链接动态库其实跟链接主程序的逻辑基本一样。
注意:循环取
image(镜像文件)的时候要从第1个开始取,因为第0个是主程序。那么我们就在下面验证一下!
好明显
image list中第0个就是主程序。
弱绑定主程序

注意:之前说链接主程序的时候不是进行了弱绑定吗?因为在链接主程序中
linkingMainExecutable = true,所以link里面的弱绑定在主程序时是不调用的,等动态库的都进行了弱绑定,最后对主程序进行弱绑定。
运行初始化方法

-
initializeMainExecutable方法就是初始化image的方法。 -
initializeMainExecutable作为单独一部分进行研究。(请往下看)
返回main函数

最后是获取
main函数地址,返回给外面,这样我们就能回到main函数里面了。
dyld加载流程总结
dyld加载流程:dyld::_main --> 配置环境变量 --> 加载共享缓存 --> 实例化主程序 --> 插入动态库 -->链接主程序 --> 链接动态库 --> 弱绑定主程序 --> 运行初始化方法 --> 返回main函数(其实标题就是流程了)
initializeMainExecutable -- 初始化image

注意:在
initializeMainExecutable始化方法中,先运行动态库的初始化方法,再运行主程序的初始化方法。
runInitializers

进入processInitializers方法

进入recursiveInitialization方法(ImageLoader.cpp)
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
……
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
//先初始化下级lib
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
……
else if ( dependentImage->fDepth >= fDepth ) {
//依赖文件递归初始化
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
……
fState = dyld_image_state_dependents_initialized;
oldState = fState;
//这里调用传递的状态是dyld_image_state_dependents_initialized,image传递的是自己。也就是最后调用了自己的+load。从libobjc.A.dylib开始调用。
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
//初始化镜像文件,调用c++构造函数。libSystem的libSystem_initializer就是在这里调用的。会调用到objc_init中。_dyld_objc_notify_register 中会调用自身的+load方法,然后c++构造函数。
//1.调用libSystem_initializer->objc_init 注册回调。
//2._dyld_objc_notify_register中调用 map_images,load_images,这里是首先初始化一些系统库,调用系统库的load_images。比如libdispatch.dylib,libsystem_featureflags.dylib,libsystem_trace.dylib,libxpc.dylib。
//3.自身的c++构造函数
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
//这里调用不到+load方法。 notifySingle内部fState==dyld_image_state_dependents_initialized 才调用+load。
context.notifySingle(dyld_image_state_initialized, this, NULL);
……
}
……
}
recursiveSpinUnLock();
}
通过上面的代码可以得出:
- 需要初始化的动态库
image是从libImage()中获取,而libImage()的数据是在链接动态库的时recursiveLoadLibraries中的setLibImage保存的image。 - 系统会根据每个库的
依赖深度去初始化,深度值最大的优先去初始化,每次初始化都会有一个image文件。
-image都会调用context.notifySingle方法去调用load_images调用load方法。 - doInitialization是初始化没有依赖的库。
-
context.notifySingle(dyld_image_state_initialized, this, NULL)实际上作用不大,notifySingle方法中并没有判断,有可能是在objc注册回调时里面根据dyld_image_state_initialized状态去调用系统库。
进入notifySingle方法(dyld2.cpp)
//调用到objc里面去
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
……
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
//回调指针 sNotifyObjCInit 是在 registerObjCNotifiers 中赋值的。这里执行会跑到objc的load_images中
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
……
}
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())这是函数调用,而且参数是跟image有关的,现在只要搞清楚在哪里赋值就可以了。全局搜索sNotifyObjCInit,源码如下:

registerObjCNotifiers方法中对sNotifyObjCInit进行赋值,全局搜索registerObjCNotifiers, 源码如下:
全局搜索
_dyld_objc_notify_register,发现dyld源码中并没有调用的这个方法的地方。在项目工程中添加_dyld_objc_notify_register符号断点,我的项目工程跑运行的是Mac用的是dyld2,如果运行的是IOS用的就是dyld3。
通过上面真机的堆栈信息可以发现:
-
doInitialization-->doModInitFunctions-->libSystem_initializer-->libdispatch_init-->_os_object_init-->_objc_init-->_dyld_objc_notify_register-->registerObjCNotifiers。 -
libSystem_initializer方法在libSystem系统库中。 -
libdispatch_init和_os_object_init方法在libdispatch系统库中 -
_objc_init方法在libobjc系统库中,objc源码库。
疑问:_objc_init方来到之后到_dyld_objc_notify_register,那么_objc_init方法里面会不会调用_dyld_objc_notify_register方法呢?
进入_objc_init方法(objc4-812源码中找到)

sNotifyObjCInit = load_images其实就是调用load_images方法,下面探究下load_images方法。
进入load_images方法

进入call_load_methods

进入call_class_load方法

call_class_load方法里面其实就是调用了load方法。
进入call_category_loads方法

call_category_loads方法内部也调用了load方法。
总结:
-
类和分类都会调用load方法,从这里调用顺序可得出这样一个结论:- 类的
load比分类的load方法先调用,类中load方法调用完才开始调用
分类的load方法。 - 类中的
load方法按编译先后顺序,谁先编译谁的load方法先调用。 - 分类中的的
load方法按编译先后顺序,谁先编译谁的load方法先调用。
注意:这是常见的面试题哦!!!!
- 类的
_objc_init流程
_objc_init在objc系统库中,除了是大家最熟悉的源码库还有就是_objc_init起到承上启下的作用。所以从_objc_init反推整个流程。调用_objc_init方法的是_os_object_init方法,在libdispatch源码库中全局搜索_os_object_init
进入_os_object_init方法

_os_object_init方法确实调用_objc_init方法。_os_object_init方法是被libdispatch_init调用,继续验证。
进入libdispatch_init方法

libdispatch_init方法确实调用_os_object_init方法。libdispatch_init被libSystem_initializer调用,libSystem_initializer方法是在libSystem系统库中。
进入libSystem_initializer方法

libSystem_initializer方法确实调用libdispatch_init方法。libSystem_initializer方法是被doModInitFunctions调用, doModInitFunctions方法是在dyld源码库中的。
进入doModInitFunctions中(dyld源码)

libSystem的初始化程序,必须最先运行,其它在它后面执行,doModInitFunctions 调用所有的C++函数。
在main函数下面添加一个全局的C++函数,断点看下堆栈信息:

堆栈信息显示是
doModInitFunctions方法调用了全局的c++方法,是在load方法之后。在doModInitFunctions方法之前调用的是doInitialization方法,然后我们以此类推进行下一步。
进入doInitialization方法

证明
doModInitFunctions方法是被doInitialization调用,那么doInitialization之前已经引申出来了,只是没讲到。
doInitialization方法的调用

recursiveInitialization调用doInitialization方法,又会到了递归的方法里,完美的串联起来。
总结
-
load方法的调用流程:
_dyld_start-->dyldbootstrap::start-->dyld::_main-->intializeMainExecutable-->runInitializers-->processInitializers-->runInitializers-->recursiveInitialization-->notifySingle-->load_images-->+[ViewController load]。 - _objc_init方法的调用流程:
doInitialization-->doModInitFunctions-->libSystem_initializer-->libdispatch_init-->_os_object_init-->_objc_init-->_dyld_objc_notify_register-->registerObjCNotifiers。
这两个调用流程通过doInitialization和 notifySingle完美形成一个完整的闭环。
main函数入口
上面说到C++函数是在main函数之前,在+load函数之后,那么来验证一波!!

根据上面可以得出
+load函数的确实在C++函数之前,那么打开汇编断点可以看到:
发现
+load的方法是在C++之前,这是肯定的了。然后我们让断点一步一步往下走,看看什么时候进入main函数:
dyldbootstrap::start返回了main函数的地址,读取x86_64寄存器发现第一个寄存器rax存放的是main函数的地址,最后一步跳转到main函数。
注意:项目中生成的main.m文件不能随意地修改,因为底层都是写死了main,你修改之后就会找不到然后报错。

dyld加载流程图

总结
dyld我总结了两天的时间,学习的过程中是非常枯燥的,需要把持住!!坚持下来了,就可以把握住dyld的加载流程,先苦后甜肯定是有道理的哦!!加油!!!










