欢迎阅读iOS探索系列(按序阅读食用效果更加)
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 细数iOS中的那些锁
- iOS探索 全方位解读Block
写在前面
我们平时编写的程序的入口函数都是main.m文件
里面的main函数
,但是这就是App的生命起点了吗?玩过逆向的iOSer都知道可以往+load
方法注入代码来进行安全攻防,而+load
方法先于main函数
执行,那么main
函数之前都发生了哪些有趣的事呢?本文就将带着大家来揭开这片神秘面纱!
本文偏新手向,只会过一遍主要流程!!!
一、静态库与动态库
1.编译过程
在日常开发过程中,开发者会使用成千上万次的Command + B/R
进行开发调试,但可能很少有人关注过这个过程中 Xcode
帮我们做了哪些事情(iOS开发者往往会吐槽Xcode越来越难用了,但不得不承认它越来越强了)
事实上,这个过程分解为4个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking).------ 摘自《程序员的自我修养-- 链接、装载与库》
在以上4个步骤中,IDE主要做了以下几件事:
预编译
:处理代码中的# 开头
的预编译指令,比如删除#define
并展开宏定义,将#include
包含的文件插入到该指令位置等编译
:对预编译处理过的文件进行词法分析、语法分析和语义分析,并进行源代码优化,然后生成汇编代码;汇编
:通过汇编器将汇编代码转换为机器可以执行的指令,并生成目标文件.o文件
链接
:将目标文件链接成可执行文件。这一过程中,链接器将不同的目标文件链接起来,因为不同的目标文件之间可能有相互引用的变量或调用的函数,如我们经常调用Foundation
框架和UIKit
框架中的方法和变量,但是这些框架跟我们的代码并不在一个目标文件中,这就需要链接器将它们与我们自己的代码链接起来
Foundation
和UIKit
这种可以共享代码、实现代码的复用统称为库
——它是可执行代码的二进制文件,可以被操作系统写入内存,它又分为静态库
和动态库
2.静态库
静态库
是指链接时完整的拷贝到可执行文件,多次使用多次拷贝,造成冗余,使包变的更大
如.a
、.lib
都是静态库
3.动态库
动态库
是指链接时不复制,程序运行时由系统加在到内存中,供系统调用,系统只需加载一次,多次使用,共用节省内存。
如.dylib
、.framework
都是动态库
系统的framework是动态的,开发者创建的framework是静态的
那么链接器又是什么呢?它是怎么链接不同的目标文件的呢?
二、dyld
1.dyld简介
dyld(The dynamic link editor)
是苹果的动态链接器,负责程序的链接及加载工作,是苹果操作系统的重要组成部分,存在于MacOS系统的(/usr/lib/dyld)
目录下。在应用被编译打包成可执行文件格式的Mach-O
文件之后 ,交由dyld
负责链接,加载程序
2.dyld_shared_cache
由于不止一个程序需要使用UIKit
系统动态库,所以不可能在每个程序加载时都去加载所有的系统动态库。为了优化程序启动速度和利用动态库缓存,苹果从iOS3.1
之后,将所有系统库(私有与公有)编译成一个大的缓存文件,这就是dyld_shared_cache
,该缓存文件存在iOS系统下的/System/Library/Caches/com.apple.dyld/
目录下
三、dyld加载流程
新建空工程,写下load方法
,并在main方法
和load方法
分别下断点

点击函数调用栈/使用LLVM——bt指令打印,都能看到最初的起点_dyld_start

接下来怎么去研究dyld
呢,我们将通过dyld源码展开分析
1._dyld_start
在源码中全局搜索_dyld_start
,会发现它是由汇编实现的
在arm64
中,_dyld_start
调用了一个看不懂的方法
从注释中得出可能是dyldbootstrap::start
方法(其实在“函数调用栈”那张图中汇编代码已经把这个方法暴露出来了)

2.dyldbootstrap::start
全局搜索dyldbootstrap::start
并没有任何有意义结果,那么只能根据经验来瞎蒙一下了——全局搜索空格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
slide = slideOfMainExecutable(dyldsMachHeader);
bool shouldRebase = slide != 0;
#if __has_feature(ptrauth_calls)
shouldRebase = true;
#endif
if ( shouldRebase ) {
rebaseDyld(dyldsMachHeader, slide);
}
// allow dyld to use mach messaging
mach_init();
// 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(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
在start()
函数中主要做了一下几件事:
- 根据
dyldsMachHeader
计算出slide
, 通过slide
判定是否需要重定位;这里的slide
是根据ASLR技术
计算出的一个随机值,使得程序每一次运行的偏移值都不一样,防止攻击者通过固定地址发起恶意攻击 mach_init()
初始化(允许dyld使用mach消息传递)- 栈溢出保护
- 计算
appsMachHeader
的偏移,调用dyld::_main()
函数
3.dyld::_main()
点击进入dyld::_main()
函数
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
......
uintptr_t result = 0;
//保存传入的可执行文件的头部(是一个struct macho_header结构体),后面根据头部访问信息
sMainExecutableMachHeader = mainExecutableMH;
......
//根据可执行文件头部,参数等设置上下文信息
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
//获取可执行文件路径
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
//将相对路径转换成绝对路径
if ( sExecPath[0] != '/' ) {
// have relative path, use cwd to make absolute
char cwdbuff[MAXPATHLEN];
if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
// maybe use static buffer to avoid calling malloc so early...
char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
strcpy(s, cwdbuff);
strcat(s, "/");
strcat(s, sExecPath);
sExecPath = s;
}
}
// Remember short name of process for later logging
//获取可执行文件的名字
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
//配置进程是否受限
configureProcessRestrictions(mainExecutableMH);
......
{
//检查设置环境变量
checkEnvironmentVariables(envp);
//如果DYLD_FALLBACK为nil,将其设置为默认值
defaultUninitializedFallbackPaths(envp);
}
......
//如果设置了DYLD_PRINT_OPTS环境变量,则打印参数
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
//如果设置了DYLD_PRINT_ENV环境变量,则打印环境变量
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
//根据Mach-O头部获取当前运行架构信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
// load shared cache
//检查共享缓存是否开启,iOS中必须开启
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
#if TARGET_IPHONE_SIMULATOR
// <HACK> until <rdar://30773711> is fixed
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
// </HACK>
#endif
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
//检查共享缓存是否映射到了共享区域
mapSharedCache();
}
......
// instantiate ImageLoader for main executable
//加载可执行文件并生成一个ImageLoader实例对象
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
......
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
//检查库的版本是否有更新,有则覆盖原有的
checkVersionedPaths();
#endif
......
// load any inserted libraries
//加载所有DYLD_INSERT_LIBRARIES指定的库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// record count of inserted libraries so that a flat search will look at
// inserted libraries, then main, then others.
sInsertedDylibCount = sAllImages.size()-1;
// link main executable
//链接主程序
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
//链接所有插入的动态库
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
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();
}
// 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);
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing(gLinkContext);
}
#if SUPPORT_ACCELERATE_TABLES
if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) {
// Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled
ImageLoader::clearInterposingTuples();
// unmap all loaded dylibs (but not main executable)
for (long i=1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image == sMainExecutable )
continue;
if ( image == sAllCacheImagesProxy )
continue;
image->setCanUnload();
ImageLoader::deleteImage(image);
}
// note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table
sAllImages.clear();
sImageRoots.clear();
sImageFilesNeedingTermination.clear();
sImageFilesNeedingDOFUnregistration.clear();
sAddImageCallbacks.clear();
sRemoveImageCallbacks.clear();
sAddLoadImageCallbacks.clear();
sDisableAcceleratorTables = true;
sAllCacheImagesProxy = NULL;
sMappedRangesStart = NULL;
mainExcutableAlreadyRebased = true;
gLinkContext.linkingMainExecutable = false;
resetAllImages();
goto reloadAllImages;
}
#endif
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
//应用符号插入
sImageRoots[i]->applyInterposing(gLinkContext);
}
ImageLoader::applyInterposingToDyldCache(gLinkContext);
gLinkContext.linkingMainExecutable = false;
// Bind and notify for the main executable now that interposing has been registered
uint64_t bindMainExecutableStartTime = mach_absolute_time();
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
uint64_t bindMainExecutableEndTime = mach_absolute_time();
ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
gLinkContext.notifyBatch(dyld_image_state_bound, false);
// Bind and notify for the inserted images now interposing has been registered
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
}
}
// <rdar://problem/12186933> do weak binding only after all inserted images linked
//弱符号绑定
sMainExecutable->weakBind(gLinkContext);
......
#if SUPPORT_OLD_CRT_INITIALIZATION
// Old way is to run initializers via a callback from crt1.o
if ( ! gRunInitializersOldWay )
initializeMainExecutable();
#else
// run all initializers
//执行初始化方法
initializeMainExecutable();
#endif
// notify any montoring proccesses that this process is about to enter main()
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
}
notifyMonitoringDyldMain();
// find entry point for main executable
//寻找目标可执行文件入口并执行
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;
}
#if __has_feature(ptrauth_calls)
// start() calls the result pointer as a function pointer so we need to sign it.
result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}
CRSetCrashLogMessage("dyld2 mode");
if (sSkipMain) {
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
}
result = (uintptr_t)&fake_main;
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}
return result;
}
dyld::_main()
主要流程为:
- 设置上下文信息,检测进程是否受限
- 配置环境变量,获取当前运行架构
- 检查是否开启共享缓存,并加载共享缓存库
- 将dyld本身添加到UUID列表
- 实例化主程序
- 加载插入动态库
- 链接主程序和插入的库,执行符号替换
- 执行初始化方法
- 寻找主程序入口
3.1 设置上下文信息,检测进程是否受限
- 调用
setContext
函数,传入Mach-O头部以及一些参数设置上下文 configureProcessRestrictions
检测进程是否受限,在上下文中做出对应处理
/// _main函数中
setContext(mainExecutableMH, argc, argv, envp, apple);
...
configureProcessRestrictions(mainExecutableMH);
3.2 配置环境变量,获取当前运行架构
- 从环境变量中获取主要可执行文件的
cdHash
checkEnvironmentVariables(envp)
检查设置环境变量defaultUninitializedFallbackPaths(envp)
在DYLD_FALLBACK
为空时设置默认值getHostInfo(mainExecutableMH, mainExecutableSlide)
获取程序架构
/// _main函数中
//如果设置了DYLD_PRINT_OPTS环境变量,则打印参数
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
//如果设置了DYLD_PRINT_ENV环境变量,则打印环境变量
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
只要设置了这两个环境变量参数,在App启动时就会打印相关参数、环境变量信息(自行尝试研究)

3.3 检查是否开启共享缓存,并加载共享缓存库
checkSharedRegionDisable
检查是否开启共享缓存(iOS 下不会被禁用)mapSharedCache
加载共享缓存库,其中调用loadDyldCache
函数有这么几种情况:- 仅加载到当前进程
mapCachePrivate
(模拟器仅支持加载到当前进程) - 共享缓存是第一次被加载,就去做加载操作
mapCacheSystemWide
- 共享缓存不是第一次被加载,那么就不做任何处理
- 仅加载到当前进程

3.4 将dyld本身添加到UUID列表
addDyldImageToUUIDList
将dyld本身添加到UUID列表
接下来是最重要的部分reloadAllImages
3.5 实例化主程序
isCompatibleMachO
检测可执行程序格式,主要判断Mach-O
文件的Magic number、cputype、cpusubtype
等是否兼容instantiateMainExecutable
实例化主程序
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
throw "main executable not a known format";
}

instantiateMainExecutable
中调用ImageLoaderMachO::sniffLoadCommands
,这才是真正实例化主程序的函数
// determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
const linkedit_data_command** codeSigCmd,
const encryption_info_command** encryptCmd)
{
*compressed = false;
*segCount = 0;
*libCount = 0;
*codeSigCmd = NULL;
*encryptCmd = NULL;
/*
...
*/
// fSegmentsArrayCount is only 8-bits
if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
// fSegmentsArrayCount is only 8-bits
if ( *libCount > 4095 )
dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
if ( needsAddedLibSystemDepency(*libCount, mh) )
*libCount = 1;
...
}
这里几个字段都与MachO
有关:
- compressed:根据
LC_DYLD_INFO_ONYL
来决定 - segCount: MachO文件中
segment
数量,最多不超过255个 - libCount: MachO文件中
依赖的动态库
的数量 - codeSigCmd: 签名信息
- encryptCmd: 加密信息,如cryptid等
3.6 加载插入动态库
/// _main函数中
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
遍历DYLD_INSERT_LIBRARIES
环境变量,调用loadInsertedDylib
加载,通过该环境变量我们可以注入自定义的一些动态库代码从而完成安全攻防,loadInsertedDylib
内部会从DYLD_ROOT_PATH
、LD_LIBRARY_PATH
、DYLD_FRAMEWORK_PATH
等路径查找dylib并且检查代码签名,无效则直接抛出异常
3.7 链接主程序和插入的库,执行符号替换
- 通过
ImageLoader::link()
函数链接主程序和插入的库 - 链接完毕后还会进行
recursiveBindWithAccounting()
递归绑定符号表、weakBind()
弱绑定
/// _main函数中
// link main executable
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
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();
}
// 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);
}
}
接下来是重中之重
3.8 执行初始化方法
回顾一下函数调用栈

①initializeMainExecutable
方法调用runInitializers
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
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]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
②runInitializers
调用processInitializers
为初始化做准备
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.images[0] = this;
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);
}
③遍历image,recursiveInitialization
递归初始化镜像
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.
for (uintptr_t i=0; i < images.count; ++i) {
images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
点进去却只有声明,shift+cmd+O
搜索recursiveInitialization
④recursiveInitialization
获取到镜像的初始化
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
...
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
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
...
}
⑤notifySingle
获取到镜像的回调
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)(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);
}
}
...
}
notifySingle中并没有找到函数调用栈中的load_images,其实这是一个回调函数的调用
⑥sNotifyObjCInit
在registerObjCNotifiers
函数中赋值
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
}
⑦registerObjCNotifiers
在_dyld_objc_notify_register
函数中被调用,这个函数只在运行时提供给objc使用
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
_objc_init
调用_dyld_objc_notify_register
,并调用load_image
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
我们可以通过objc源码
下符号断点来_dyld_objc_notify_register
验证

这里又出现了libSystem
...(好吧,是我太天真了,dyld过程真复杂)
⑧context.notifySingle
之后,调用ImageLoaderMachO::doInitialization
,内部调用
doImageInit
ImageLoaderMachO::doModInitFunctions
⑨doImageInit
->libSystemInitialized
->libdispatch_init
->_os_object_init
,内部调用_objc_init

⑩doModInitFunctions
内部调用__mod_init_funcs section
,也就是constructor
方法——C++构造方法
initializeMainExecutable
总结:
runInitializers
->processInitializers
中,遍历recursiveInitialization
- 第一次执行时,进行
libsystem
初始化——doInitialization
->doImageInit
->libSystemInitialized
libsystem
的初始化,会调用起libdispatch_init
,libdispatch
初始化会调用_os_object_init
, 内部调用了_objc_init
_objc_init
中注册并保存了map_images
、load_images
、unmap_image
函数地址- 注册完毕继续回到
recursiveInitialization
递归下一次调用
3.9 寻找主程序入口
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
四、dyld加载过程示意图


写在后面
dyld加载流程代码较多,第一次看大概了解这个过程即可