前言
之前的文章分析的是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_ini
t被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的加载流程,先苦后甜肯定是有道理的哦!!加油!!!