前言
前面探究了基本上都是main
函数之后底层的流程,那么今天就探究下main
函数之后底层究竟默默的做了哪些操作
准备工作
编译和库
IOS
程序员一般是写上层代码,最熟悉的就是.h
和.m
文件,在编译的时候.h
和.m
文件具体经过哪些流程最终生成了可执行文件
- 源文件:
.h
、.m
、.cpp
等文件 - 预处理:在预处理的时候,注释被删除、条件编译被处理、头文件展开、宏被替换
- 编译:进行词法分析语法分析以及中间层
IR
文件,最后生成汇编文件.s
文件 - 汇编:将
.s
文件转换成机器语言生成.o
文件 - 链接:将所有的
.o
文件以及链接的第三方库,生成一个macho
类型的可执行文件
编译流程图
动态库静态库
- 静态库:在链接阶段会将汇编生成的目标和引用库一起链接打包到可执行文件中
- 动态库:程序编译不会链接到目标代码中,而是程序运行时才被载入
动态库静态库区别
静态库优缺点
优点
- 静态库被打包到可执行文件中,编译成功后可执行文件可以独立运行,不需要依赖外部环境 缺点
- 编译的文件会变大,如果静态库更新必须重新编译
动态库优缺点
优点
- 减少打包之后
App
的大小 - 共享内容,资源共享
- 通过更新动态库,达到更新程序的目的 缺点
- 可执行文件不可以单独运行,必须依赖外部环境
静动态库图解
dyld加载流程
因为是探索main
函数之前的流程,那么值能通过汇编跟流程,直接在main
函数断点
堆栈信息显示libdyld.dylib
库的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
的源码库,_dyld_start
详细汇编信息如下
汇编中_dyld_start
之后调用的是dyldbootstrap::start
方法。在dyld
源码中全局搜索dyldbootstrap::start
发现没有实现的地方都是汇编,在全局搜索dyldbootstrap
dyldbootstrap
是一个命名空间,在dyldInitialization.cpp
文件中搜索start
方法
- 重定位
dyld
,因为App
一启动系统就会自动给App
随机分配ASLR
。dyld
需要重定位因为它需要到当前进程中获取自己的信息 - 调用
dyld::_main
方法,获取返回结果
dyld::_main
dyld::_main
方法中的代码有800
多行,就把整体流程梳理下,掌握整体的加载启动流程感觉就可以了,细节部分感兴趣的可以自己探索
配置环境变量
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
是必须要有共享缓存的,共享缓存中存的都是系统级别的动态库。比如UIKit
,CoreFoundation
等。自己创建的动态库或者第三方的动态库不会放在共享缓存中
checkSharedRegionDisable
方法是检测是否需要不同的架构是否需要共享缓存mapSharedCache
加载共享缓存
checkSharedRegionDisable
方法很明显的提示IOS
是必须要有共享缓存的。下面探究下具体是怎么加载共享缓存的mapSharedCache
方法中有调用了loadDyldCache
加载共享缓存的方法
共享缓存的加载共有三种情况
强制私有
:forcePrivate
=YES
,表示强制私有。只加载到当前App
进程中,不放在共享缓存中共享缓存已加载
:如果你依赖的库在共享缓存中已经加载过了,此时就可以直接用无需其他操作第一次加载
;如果你依赖的库共享缓存中没有,它就会被加载到共享缓存中
我想现在大家对共享缓存的加载有一个清晰的认知,IOS
是必须有共享缓存且共享缓存中只存放系统库,创建共享缓存的目的是为了多进程共同使用系统库
dyld3
或dyld2
dyld3
有叫做闭包模式
它的加载速度更快,效率更高。IOS11
以后主程序都是用dyld3
加载,IOS13
以后动态库和三方库用dyld3
加载
//判断是否使用闭包模式也是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;
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();
...
}
今天主要看下探究下dyld3
的模式,因为这个以后会逐渐取代dyld2
,后面有时间再去探究下dyld2
过程
总结dyld3
的启动流程
- 从共享缓存中获取
dyld3
的实例mainClosure
- 验证
mainClosure
是否有效 - 再去共享缓存中查找有效的
mainClosure
,如果有直接启动 - 如果没有,创建
mainClosure
- 启动
mainClosure
,启动dyld3
- 启动成功以后,主程序启动成功,
result
就是main
函数的地址,返回到dyldbootstrap::start
方法,然后进入mian
函数
实例化主程序
dyld3
和dyld2
走的流程都是一样的只不过dyld3
用的闭包模式。image
在源码中经常出现,image
并不是图片的意思,而是镜像文件
,镜像文件
就是从磁盘映射
到内存的macho
文件。可以理解为只要是加载到内存的macho
文件就叫镜像文件
实例化主程序就是把需要的主程序的部分信息加载到内存中,通过instantiateMainExecutable
方法返回ImageLoader
类型的实例对象,然后对主程序进行签名
将实例化的image
添加到镜像文件数组中,在这注意一点主程序的image
是第一个添加到数组中的。现在探究下 ImageLoaderMachO::instantiateMainExecutable
方法
加载macho
文件中command
信息,以及校验。sniffLoadCommands
中代码取部分重要的探究
sniffLoadCommands
中加载segment
和commod
信息,以及一些校验
segment
段的个数最大是256
个command
的个数最大是4096
个- 确保必须依赖了
libSystem
库
大家可能对segment
和command
以及macho
头文件还是有点模糊,通过MachOView
工具认识下可执行文件
图中很清晰明了macho
头文件就是架构信息以及文件类型等。macho
文件主要是3块内容Header
、Commods
、Data
插入动态库
通过loadInsertedDylib
插入动态库,此时有的动态库数量是所有的镜像文件减一
链接主程序
link
中调用了ImageLoader::link
方法,ImageLoader
负责加载image
文件(主程序,动态库)每个image
对应一个ImageLoader
类的实例
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool
preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath{
...
//递归加载所有的动态库
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
...
__block uint64_t t2, t3, t4, t5;
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
t2 = mach_absolute_time();
//递归重定位
this->recursiveRebaseWithAccounting(context);
context.notifyBatch(dyld_image_state_rebased, false);
t3 = mach_absolute_time();
if ( !context.linkingMainExecutable )
//递归绑定非懒加载
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
t4 = mach_absolute_time();
if ( !context.linkingMainExecutable )
//弱绑定
this->weakBind(context);
t5 = mach_absolute_time();
}
...
//链接过程中可以统计链接动态库的时间,在环境变量设置可以打印出信息
}
- 递归加载所有的动态库
- 递归
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);
}
}
}
代码很多只截取整体流程的代码
- 获取当前
image
依赖的动态库和动态库的文件路径 - 加载
image
依赖的动态库,并保存起来 - 告诉image依赖的动态库去加载各自需要的动态库
链接动态库
链接动态库和链接主程序的逻辑基本一样,注意下循环取image
文件的时候是从1
开始,因为第0
个位置是主程序,下面来验证下
image list
中第一个数据就是主程序
弱绑定主程序
可能大家会有疑问在链接主程序的时候里面不是有弱符号绑定嘛。因为在链接主程序之linkingMainExecutable
= true
,所以link
里面的弱绑定在主程序时是不调用的,等动态库的都进行了弱绑定,最后对主程序进行弱绑定
运行初始化方法
initializeMainExecutable
在这不展开因为内容很多,放在后面单独探究
返回main
函数
获取到main
函数以后,就会进入大家熟悉main
函数中
总结
dyld
加载流程: dyld::_main
--> 配置环境变量 --> 加载共享缓存 --> 实例化主程序 --> 插入动态库 --> 链接主程序 --> 链接动态库 --> 弱绑定主程序 --> 运行初始化方法 --> 返回main
函数
initializeMainExecutable
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
// 运行所有的dylibs中的initialzers方法
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
//先运行动态库的初始化方法
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
// 运行主程序的初始化方法
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
...
}
先运行动态库的初始化方法,再运行主程序的初始化方法
runInitializers
全局搜索runInitializers
方法,源码如下
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
// 递归当前image的镜像列表实例化
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
进入processInitializers
源码如下
// upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t
thisThread,InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
//递归所有镜像列表中的所有`image`,如果有没有初始化就去初始化
for (uintptr_t i=0; i < images.count; ++i) {
images.imagesAndPaths[i].first->recursiveInitialization(context,
thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
// 为了保证所有的向上依赖关系都初始化,再次把没有初始化的image去初始化
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
全局搜索recursiveInitialization
,源码如下
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t
this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
...
// initialize lower level libraries first
//优先初始化依赖最深的库
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage,
libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread,
libPath(i), timingInfo, uninitUps);
}
}
}
// 将要初始化的image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
// 初始化image
bool hasInitializers = this->doInitialization(context);
// image初始化完成
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
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
和doInitialization
,全局搜索notifySingle
源码如下
(*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
源码库最熟悉
下面就来探究下*sNotifyObjCInit
具体执行了什么内容,首先找到赋值的地方_objc_init
调用的_dyld_objc_notify_register
所以赋值应该在_objc_init
方法
sNotifyObjCInit
= load_images
其实就是调用load_images
方法,下面探究下load_images
方法
call_load_methods
从方法名字来看就是调用load
方法,接着往下探究call_load_methods
方法
循环遍历的load
方法,探究下call_class_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
方法确实调用_objc_init
方法。_os_object_init
方法是被libdispatch_init
调用,继续验证
libdispatch_init
方法确实调用_os_object_init
方法。libdispatch_init
被libSystem_initializer
调用,libSystem_initializer
方法是在libSystem
系统库中
libSystem_initializer
方法确实调用libdispatch_init
方法。libSystem_initializer
方法是被doModInitFunctions
调用, doModInitFunctions
方法是在dyld
源码库中的
libSystem
的初始化程序,必须最先运行,其它的都给我往后稍一稍,doModInitFunctions
调用所有的C++
函数
在main
函数下面添加一个全局的C++
函数,断点看下堆栈信息
堆栈信息显示是doModInitFunctions
方法调用了全局的c++
方法,是在load
方法之后
doModInitFunctions
方法是被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++
函数是在load
方法之后执行,但是是在main
函数之前执行,在C++
函数中加上断点一步步调试,一定会进入main
函数中的
断点汇编显示在C++
函数中,此时一步步调试进入下个流程找到main
函数调用的地方
dyldbootstrap::start
返回了main
函数的地址,读取x86_64
寄存器发现第一个寄存器rax
存放的是main
函数的地址,最后一步跳转到main
函数
注意
:不要奇奇怪怪的修改main函数的名称
,如果修改编译直接报错,main
函数是固定函数
dyld
加载流程图
总结
dyld
探究是比较枯燥、乏味、复杂的,在探索学习的过程遇到了很多问题。贵在坚持,基本上把握了dyld整体的加载流程