0%

iOS底层探索 - dyld加载流程

本文我们主要探索应用程序的加载流程,也就是main方法之前,链接器都做了什么。了解这些对我们项目的启动优化有很大帮助。

编译过程和库

编译的过程

我们知道库是一种可执行文件,从源代码到可执行文件工经历了下面几个步骤:

  • 源文件:主要就是我们写的代码,.h、.m、.cpp等文件。
  • 预编译:主要处理哪些源代码文件中以#开始的预编译指令,比如#include#define、删除所有的注释///* */、添加行号和文件名标识、保留所有的#pragma编译期指令、产生.i文件。
  • 编译:将预处理完的文件进行词法分析语义分析及优化后输出汇编代码文件即.s文件。
  • 汇编:将汇编代码转变成机器可以执行的指令即.o文件。
  • 链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件

动态库和静态库

我们项目中经常会使用到动态库静态库,它们的区别是:

  • 静态库:在链接阶段会将汇编生成的目标文件和引用的库一起链接打包到可执行文件中。即静态库在链接阶段就被载入了。
    • 优点:编译完成之后的目标程序没有外部依赖,可以直接运行。
    • 缺点:静态库可以会有多份,会导致目标程序体积增加,对内存、性能、速度消耗较大。
  • 动态库:程序编译并不会链接到目标代码中,在程序可执行文件里面会保留对动态库的引用,在程序运行时才被载入,苹果大部分官方的库都是动态库
    • 优点:
      1. 可以减少App包体积大小:因为不需要拷贝到目标程序中,所以不会影响目标程序的体积.
      2. 共享内存,节约资源:同一份库可以被多个程序使用。
      3. 可以更新动态库,而目标程序不需要重新编译:这是因为动态库运行时才载入,可以随时对库进行替换,而·不需要重新编译代码。
    • 缺点:由于是运行时载入会带来一部分性能损失,使用动态库使得程序依赖于外部环境,如果环境缺少了动态库程序就无法运行。

dyld是什么

dyld动态链接器,目前最新的版本是dyld3,我们首先看一下dyld的版本演变。在dyld之前,NeXT使用的是静态二进制数据。

dyld版本演变

  • dyld1.0(1996-2004)
    • 包含在NeXTStep 3.3
    • 历史早于标准化POSIX dlopen()的调用
    • macOS 10之前编写第三方包装器用来以支持标准Unix软件,但是这些包装器并不能完美的支持相同的语义,在边界情况不能正常工作。
    • 在大多数使用C++动态库的系统之前编写的。
    • mac OS 10.0增加了预绑定。使用预绑定技术为系统中所有的dylib和我们的程序找到固定地址,动态加载器将会加载这些地址的所有内容。
  • dyld2.0(2004-2007)
    • 包含在macOS Tiger
    • 相比1.0版本是完全重写(Complete rewrite)的。
    • 支持了C++初始化语义,扩展了mach-o格式。
    • 有完整的本地(native)dlopendlsym的实现。
    • 2.0版本设计的目标是提高速度,仅进行有限的及安全性检查。
    • 提高了安全性。
    • 减少预编译的工作量(时长)
  • dyld2.x(2007-2017)
    • 增加了更多的基础架构和平台,比如x86x86_64armarm64iOStvOSwatchOS
    • 增强了安全性。增加代码签名ASLR(地址空间配置随机加载),增加了mach-o头文件中的项目边界检查功能它可以避免恶意二进制数据的加入。
    • 增强了性能:用共享缓存代替了预绑定共享缓存是一个包含大部分系统dylib的单文件(Single file),可以节省大量内存,它实际是预链接库。
  • dyld3(2017-至今)
    • 完全改变动态链接器的概念
    • 默认适用于大部分Apple OS系统应用。
    • 完全替代了dyld2.x
    • 提高了性能,尽量提高启动速度和运行速度。
    • 提高安全性:将大多数dyld移出进程,允许部分dyld驻留在进程之中,驻留部分尽可能小,从而减少受攻击的面积。
    • 可测试性和可靠性

dyld 2dyld 3加载过程的区别

  • dyld 2的加载过程

    1. Parse mach-o headers:分析mach-o文件,通过分析mach-o文件弄清楚需要那些库,这些库可能需要其他库,所以会进行递归分析,直到获得所有dylib的完整图。一般普通iOS程序需要3-600个dylib,数据庞大,需要进行大量的处理。
    2. Map mach-o files:映射所有mach-o文件,将它们放入地址空间
    3. Perform symbol lookups:执行符号查找,例如程序使用了printf函数,将会查找printf是否在库系统中,然后找到它的地址,将它复制到你的程序中的函数指针。
    4. Bind and rebase:绑定和基址重置,复制3步的指针,由于使用随机地址,所有指针必须使用基址。
    5. Run initializers:运行初始化器,接下来准备执行main函数。

    流程图如下图,其中红色表示影响性能和安全性的步骤:

dyld2process

  • dyld3加载过程

    dyld3包括三个部分:

    • An out of process MachO parser/compiler:进程外mach-o分析器和编译器。

      1. Resolves all search paths, @rpaths, environment variables:解析所有搜索路径、rpaths、环境变量。
      2. Parses the mach-o binaries:分析mach-o二进制数据
      3. Perform symbol lookups:执行符号查找,例如程序使用了printf函数,将会查找printf是否在库系统中,然后找到它的地址,将它复制到你的程序中的函数指针。
      4. Creates a launch closure with results:创建收尾处理
    • An in-process engine that runs launch:进程内引擎执行启动收尾处理,进驻在内存中。

      1. Validates launch closure:检查启动收尾处理是否正确。
      2. Maps in all dylibs:映射到所有的dylib
      3. Applies fixups:应用修正
      4. Run initializers:运行初始化器,接下来准备执行main函数。
    • A launch closure caching service:启动收尾缓存服务。大部分程序启动会使用缓存但始终不需要调用进程外mach-o分析器和编译器,启动收尾比mach-o更简单,启动收尾文件是内存映射文件,不需要用复杂的方法进行分析从而提高速度。

      流程图(来源WWDC ppt)如下:

      dyld3

注:本小节内容来源于WWDC2017 App Startup Time: Past, Present, and Future感兴趣的童鞋可以查看视频。

dyld加载流程分析

通过上一小节我们其实对dyld的加载有一个初步的了解了,本小节主要通过看源码来探索一下加载流程。本小节需要的源码有:

  • dyld
  • libobjc
  • libSystem
  • libdispatch

源码可直接去苹果Source Browser下载

dyldstart探索

  • 通过main函数

    因为我们程序的入口是main函数,dyld是在main之前执行的,我们很容易想到在main函数打一个断点,然后查看调用堆栈信息来查看dyld的具体调用方法:

    1
    2
    3
    4
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    * frame #0: 0x000000010000339b DyldTest`main(argc=3, argv=0x00007ffeefbff500) at main.m:13:5
    frame #1: 0x00007fff6e7d3cc9 libdyld.dylib`start + 1

    通过这个方式我们看到了start,但是通过打符号断点并没有找到start方法,所以这种方式无效。

  • 通过load方法

    根据我们的经验,我们知道load方法是在main函数之前执行的,我们通过load方法能不能找到dyld的入口呢,心动不如行动,我们试一下,在ViewController类加入load方法,打上断点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    * frame #0: 0x00000001000032d7 DyldTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:19:5
    frame #1: 0x00007fff6d61e560 libobjc.A.dylib`load_images + 1529
    frame #2: 0x000000010001626c dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
    frame #3: 0x0000000100029fe9 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 475
    frame #4: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #5: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #6: 0x00000001000166a8 dyld`dyld::initializeMainExecutable() + 199
    frame #7: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
    frame #8: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #9: 0x0000000100015025 dyld`_dyld_start + 37

    通过这个堆栈我们看到了_dyld_start就是dyld开始的函数,我们依次探讨堆栈里的方法。

_dyld_start

我们首先在dyld源码中搜索_dyld_start,发现是一段汇编代码

dyld_start汇编

通过注释我们可以看到,调用的是dyldbootstrapstart函数。

dyldbootstrap::start

我们在源码中搜索dyldbootstrap找到命名空间,继续查找start函数

1
2
3
4
5
6
7
8
9
10
11
//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
// In dyld we have to do this manually.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
///省略代码
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

这个函数关键是最后一行,调用了dyld::_main

dyld::_main

这个方法很长(900+行),可以从返回值倒退看这个方法都做了什么。方法太长我们省略大部分代码(因为返回值和mainExecutable相关,所以截取的代码基本都和mainExecutable相关):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
///省略代码
// Grab the cdHash of the main executable from the environment
// 创建主程序cdHash的空间
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
unsigned bufferLenUsed;
if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
}
/////配置信息,获取主程序的mach-o header、silder(ASLR的偏移值)
getHostInfo(mainExecutableMH, mainExecutableSlide);
///通过silder+ASLR可以找到信息
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;

///省略代码
//设置上下文,将这里所有的变量放到了gLinkContext中了,保存起来
setContext(mainExecutableMH, argc, argv, envp, apple);
//配置进程是否受限,envp是环境变量
configureProcessRestrictions(mainExecutableMH, envp);
configureProcessRestrictions(mainExecutableMH, envp);
///检测是否强制dyld3
// Check if we should force dyld3. Note we have to do this outside of the regular env parsing due to AMFI
if ( dyld3::internalInstall() ) {
if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {
if ( strcmp(useClosures, "0") == 0 ) {
sClosureMode = ClosureMode::Off;
} else if ( strcmp(useClosures, "1") == 0 ) {
#if !__i386__ // don't support dyld3 for 32-bit macOS
sClosureMode = ClosureMode::On;
sClosureKind = ClosureKind::full;
#endif
} else if ( strcmp(useClosures, "2") == 0 ) {
sClosureMode = ClosureMode::On;
sClosureKind = ClosureKind::minimal;
} else {
dyld::warn("unknown option to DYLD_USE_CLOSURES. Valid options are: 0 and 1\n");
}
}
}
#if TARGET_OS_OSX
///受限制的进程,环境变量可能会变化,需要重新设置
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
///检查环境变量
checkEnvironmentVariables(envp);
///default value for DYLD_FALLBACK_FRAMEWORK_PATH, if not set in environment
///如果没有环境变量 设置默认值
defaultUninitializedFallbackPaths(envp);
}
// load shared cache 加载共享缓存
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide)
#if !TARGET_OS_SIMULATOR
///是否有启动闭包 dyld3有闭包
if ( sClosureMode == ClosureMode::Off ) {
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
///dyld3有闭包
///设置加载启动模式
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
///配置闭包
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
dyld3::closure::LoadedFileInfo mainFileInfo;
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
// check for closure in cache first
// 判断缓存中是否已经有闭包
if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) )
dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
///如果闭包已失效
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
///没有闭包创建一个
if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
// if forcing closures, and no closure in cache, or it is invalid, check for cached closure
if ( !sForceInvalidSharedCacheClosureFormat )
mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure == nullptr ) {
// if no cached closure found, build new one
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
// try using launch closure 使用启动闭包
if ( mainClosure != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
bool closureOutOfDate;
bool recoverable;
///启动闭包
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
/// 如果启动失败
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
///重新启动
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
}
///启动成功 返回main函数
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
result = (uintptr_t)&fake_main;
return result;
}
///不是dyld3的省略代码
}
#endif
///省略代码
// load any inserted libraries插入动态库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
///弱引用绑定主程序
sMainExecutable->weakBind(gLinkContext);
// run all initializers
// 运行所有initializers
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
/// 通知可以进入main函数了
notifyMonitoringDyldMain();
}

大体流程就是:

  • 配置环境变量
  • 检查共享缓存是否开启,以及共享缓存是否映射到共享区域
  • 主程序初始化即instantiateFromLoadedImage
  • 插入动态库
  • link主程序
  • link动态库
  • 弱符号绑定
  • 执行初始化方法
  • 主程序入口

dyld::initializeMainExecutable

主要是循环遍历执行runInitializers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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]);
}

ImageLoader::runInitializers

核心代码是调用processInitializers方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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() };
///调用
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);
}

ImageLoader::processInitializers

对镜像列表调用recursiveInitialization函数进行递归实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}

ImageLoader::recursiveInitialization

主要是加载完镜像后通知出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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);
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
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);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);

// let objc know we are about to initialize this image
// 让objc知道我们要初始化此镜像
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);

if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}

dyld::notifySingle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
if ( handlers != NULL ) {
dyld_image_info info;
info.imageLoadAddress = image->machHeader();
info.imageFilePath = image->getRealPath();
info.imageFileModDate = image->lastModified();
for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
const char* result = (*it)(state, 1, &info);
if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
//fprintf(stderr, " image rejected by handler=%p\n", *it);
// make copy of thrown string so that later catch clauses can free it
const char* str = strdup(result);
throw str;
}
}
}
if ( state == dyld_image_state_mapped ) {
// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
if (!image->inSharedCache()
|| (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
dyld_uuid_info info;
if ( image->getUUID(info.imageUUID) ) {
info.imageLoadAddress = image->machHeader();
addNonSharedCacheImageUUID(info);
}
}
}
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);
}
}
// mach message csdlc about dynamically unloaded images
if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
notifyKernel(*image, false);
const struct mach_header* loadAddress[] = { image->machHeader() };
const char* loadPath[] = { image->getPath() };
notifyMonitoringDyld(true, 1, loadAddress, loadPath);
}
}

其重点是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());,我们全局搜索sNotifyObjCInit并没有实现,但是有赋值操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;

// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}

// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}

registerObjCNotifiers是在_dyld_objc_notify_register调用,而_dyld_objc_notify_register函数是在libobjc源码_objc_init代用的,所以sNotifyObjCInit赋值的就是objc中的load_images,而load_images会调用所有的+load方法。所以综上所述,notifySingle是一个回调函数,所以我们继续看load_images方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}

load_images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}

// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
// 调用了load方法
call_load_methods();
}

方法比较简单,主要调用了call_load_methods方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;

loadMethodLock.assertLocked();

// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;

void *pool = objc_autoreleasePoolPush();

do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}

// 2. Call category +loads ONCE
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);

objc_autoreleasePoolPop(pool);

loading = NO;
}

call_load_methods方法的核心就是循环调用load方法。

###总结

load的源码链为:_dyld_start –> dyldbootstrap::start –> dyld::_main –> dyld::initializeMainExecutable –> ImageLoader::runInitializers –> ImageLoader::processInitializers –> ImageLoader::recursiveInitialization –> dyld::notifySingle(是一个回调处理) –> sNotifyObjCInit –> load_images(libobjc.A.dylib)