DYLD加载Mach-O完整流程

在 iOS 和 macOS 系统中,几乎所有的程序都会用到动态库,而动态库在加载的时候都需要用 dyld 进行链接。dyld 全称是 dynamic loader,它是完全开源的,不过苹果官方关于 dyld 的文档很少。App 可执行文件在内核加载后,根据 Mach-O 加载命令中 LC_LOAD_DYLINKER 命令获取 dyld 路径(位于/usr/lib/dyld),然后加载 dyld,加载完成后 dyld 进行初始化、缓存加载、依赖库加载等等工作,最后返回App main() 函数地址,main() 函数调用后,就进入了程序的入口。

The dynamic loader for Darwin/OS X is called dyld, and it is responsible for loading all frameworks, dynamic libraries, and bundles (plug-ins) needed by a process.
Apple developer document:Dynamic Loader Release Notes

整体流程

已知objc的初始化入口函数 _objc_init (如果还不了解objc,请自行查询相关资料补充)是由libSystem调用进行初始化的,在 _objc_init 函数入口断点可以看到完整的函数调用栈,这里我们主要关注 dyld 相关的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x000000010030a004 libobjc.A.dylib`::_objc_init() at objc-os.mm:878
frame #1: 0x0000000100934a92 libdispatch.dylib`_os_object_init + 13
frame #2: 0x0000000100934a74 libdispatch.dylib`libdispatch_init + 276
frame #3: 0x00007fff6b3c59c3 libSystem.B.dylib`libSystem_initializer + 121
frame #4: 0x000000010001ca7a dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 420
frame #5: 0x000000010001ccaa dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #6: 0x00000001000181cc dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 330
frame #7: 0x000000010001815f dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 221
frame #8: 0x0000000100017302 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134
frame #9: 0x0000000100017396 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74
frame #10: 0x0000000100008521 dyld`dyld::initializeMainExecutable() + 126
frame #11: 0x000000010000d239 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 7242
frame #12: 0x00000001000073d4 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 453
frame #13: 0x00000001000071d2 dyld`_dyld_start + 54

简化一下,dyld加载的大致流程如下:

详细过程

寄存器初始化

内核在加载完 dyld 后,跳转到 __dyld_start 方法,找到源码发现这是一段汇编函数,在这个方法中进行了寄存器相关状态字的初始化,并跳转到 dyld 的引导方法,这里以 arm64 汇编源码为例:

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
/// dyld-519.2.1
/// dyldStartup.s
/// line 273

#if __arm64__
.data
.align 3
__dso_static:
.quad ___dso_handle

.text
.align 2
.globl __dyld_start
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
mov x0, #0
mov x1, #0
stp x1, x0, [sp, #-16]! // make aligned terminating frame
mov fp, sp // set up fp to point to terminating frame
sub sp, sp, #16 // make room for local variables
ldr x0, [x28] // get app's mh into x0
ldr x1, [x28, #8] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
add x2, x28, #16 // get argv into x2
adrp x4,___dso_handle@page
add x4,x4,___dso_handle@pageoff // get dyld's mh in to x4
adrp x3,__dso_static@page
ldr x3,[x3,__dso_static@pageoff] // get unslid start of dyld
sub x3,x4,x3 // x3 now has slide of dyld
mov x5,sp // x5 has &startGlue

// call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
mov x16,x0 // save entry point address in x16
ldr x1, [sp]
cmp x1, #0
b.ne Lnew

// LC_UNIXTHREAD way, clean up stack and jump to result
add sp, x28, #8 // restore unaligned stack pointer without app mh
br x16 // jump to the program's entry point

// LC_MAIN case, set up stack for call to main()
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib
ldr x0, [x28, #8] // main param1 = argc
add x1, x28, #16 // main param2 = argv
add x2, x1, x0, lsl #3
add x2, x2, #8 // main param3 = &env[0]
mov x3, x2
Lapple: ldr x4, [x3]
add x3, x3, #8
cmp x4, #0
b.ne Lapple // main param4 = apple
br x16

#endif // __arm64__

源码中可以看到一个bl指令,bl到 __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm 处,根据注释可以知道是跳转到 dyldbootstrap::start() 方法:

1
2
// call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm

dyld加载引导

源码中找到 dyldbootstrap::start方法,在 dyldbootstrap::start() 方法中做了很多dyld初始化相关的工作,包括:

  1. dyld address rebase:rebaseDyld(dyldsMachHeader, slide)
  2. mach消息初始化:mach_init()
  3. 堆栈保护(stack canary技术):__guard_setup(apple)
  4. 进入dyld入口函数:dyld::_main()

最后进入的是 dyld的入口 dyld::_main() 函数,并返回 dyld::_main() 函数的地址给__dyld_start

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
/// dyld-519.2.1
/// dyldInitialization.cpp
/// line 206

//
// 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 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
if ( slide != 0 ) {
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);
}

Tips:stack canary是一种预防栈溢出攻击的保护技术,
具体的可以阅读 stack canary技术相关认识

dyld入口

整体流程

进入了 dyld::_main() 方法,这个方法里做的事情较多,源码也较长,这里大致做的事情可以总结为以下几步:

  1. 运行参数处理
  2. 加载共享缓存
  3. 实例化主程序
  4. 加载插入的动态库
  5. 链接主程序
  6. 链接插入的动态库
  7. 弱符号绑定
  8. 运行初始化方法
  9. 查找并返回主程序入口地址
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/// dyld-519.2.1
/// dyld.cpp
/// line 5618

//
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_DYLD, 0, 0);

// Grab the cdHash of the main executable from the environment
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
mainExecutableCDHash = mainExecutableCDHashBuffer;

// Trace dyld's load
notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
#if !TARGET_IPHONE_SIMULATOR
// Trace the main executable's load
notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
// if this is host dyld, check to see if iOS simulator is being run
const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
if ( rootPath != NULL ) {

// look to see if simulator has its own dyld
char simDyldPath[PATH_MAX];
strlcpy(simDyldPath, rootPath, PATH_MAX);
strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
int fd = my_open(simDyldPath, O_RDONLY, 0);
if ( fd != -1 ) {
const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
if ( errMessage != NULL )
halt(errMessage);
return result;
}
}
#endif

CRSetCrashLogMessage("dyld: launch started");

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);

#if __MAC_OS_X_VERSION_MIN_REQUIRED
if ( gLinkContext.processIsRestricted ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
getHostInfo(mainExecutableMH, mainExecutableSlide);

// load shared cache
checkSharedRegionDisable((mach_header*)mainExecutableMH);
#if TARGET_IPHONE_SIMULATOR
// <HACK> until <rdar://30773711> is fixed
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
// </HACK>
#endif
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
mapSharedCache();
}

if ( (sEnableClosures || inWhiteList(sExecPath)) && (sSharedCacheLoadInfo.loadAddress != nullptr) ) {
if ( sSharedCacheLoadInfo.loadAddress->header.formatVersion == dyld3::launch_cache::binary_format::kFormatVersion ) {
const dyld3::launch_cache::BinaryClosureData* mainClosureData;
// check for closure in cache first
dyld3::DyldCacheParser cacheParser(sSharedCacheLoadInfo.loadAddress, false);
mainClosureData = cacheParser.findClosure(sExecPath);
#if __IPHONE_OS_VERSION_MIN_REQUIRED
if ( mainClosureData == nullptr ) {
// see if this is an OS app that was moved
if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) {
dyld3::MachOParser mainParser((mach_header*)mainExecutableMH);
uint32_t textOffset;
uint32_t textSize;
if ( !mainParser.isFairPlayEncrypted(textOffset, textSize) ) {
__block bool hasEmbeddedDylibs = false;
mainParser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t, uint32_t, bool& stop) {
if ( loadPath[0] == '@' ) {
hasEmbeddedDylibs = true;
stop = true;
}
});
if ( !hasEmbeddedDylibs ) {
char altPath[1024];
const char* lastSlash = strrchr(sExecPath, '/');
if ( lastSlash != nullptr ) {
strlcpy(altPath, "/private/var/staged_system_apps", sizeof(altPath));
strlcat(altPath, lastSlash, sizeof(altPath));
strlcat(altPath, ".app", sizeof(altPath));
strlcat(altPath, lastSlash, sizeof(altPath));
if ( gLinkContext.verboseWarnings )
dyld::log("try path: %s\n", altPath);
mainClosureData = cacheParser.findClosure(altPath);
}
}
}
}
}
#endif
if ( gLinkContext.verboseWarnings && (mainClosureData != nullptr) )
dyld::log("dyld: found closure %p in dyld shared cache\n", mainClosureData);
#if !TARGET_IPHONE_SIMULATOR
if ( (mainClosureData == nullptr) || !closureValid(mainClosureData, (mach_header*)mainExecutableMH, mainExecutableCDHash, true, envp) ) {
mainClosureData = nullptr;
if ( sEnableClosures ) {
// if forcing closures, and no closure in cache, or it is invalid, then RPC to closured
mainClosureData = callClosureDaemon(sExecPath, envp);
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: closured return %p for %s\n", mainClosureData, sExecPath);
if ( (mainClosureData != nullptr) && !closureValid(mainClosureData, (mach_header*)mainExecutableMH, mainExecutableCDHash, false, envp) ) {
// some how freshly generated closure is invalid...
mainClosureData = nullptr;
}
}
}
#endif
// try using launch closure
if ( mainClosureData != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
if ( launchWithClosure(mainClosureData, sSharedCacheLoadInfo.loadAddress, (mach_header*)mainExecutableMH, mainExecutableSlide,
argc, argv, envp, apple, &result, startGlue) ) {
if (sSkipMain)
result = (uintptr_t)&fake_main;
return result;
}
else {
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: unable to use closure %p\n", mainClosureData);
}
}
}
else {
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closure because shared cache format version does not match dyld's\n");
}
// could not use closure info, launch old way
}


// 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);
sImageFilesNeedingTermination.reserve(16);
sImageFilesNeedingDOFUnregistration.reserve(8);

#if !TARGET_IPHONE_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
// <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process
WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif


try {
// add dyld itself to UUID list
addDyldImageToUUIDList();

#if SUPPORT_ACCELERATE_TABLES
bool mainExcutableAlreadyRebased = false;
if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
struct stat statBuf;
if ( ::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
}

reloadAllImages:
#endif

CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

#if TARGET_IPHONE_SIMULATOR
// check main executable is not too new for this OS
{
if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) {
throwf("program was built for a platform that is not supported by this runtime");
}
uint32_t mainMinOS = sMainExecutable->minOSVersion();

// dyld is always built for the current OS, so we can get the current OS version
// from the load command in dyld itself.
uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle);
if ( mainMinOS > dyldMinOS ) {
#if TARGET_OS_WATCH
throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d",
mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
#elif TARGET_OS_TV
throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d",
mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
#else
throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d",
mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
#endif
}
}
#endif


#if __MAC_OS_X_VERSION_MIN_REQUIRED
// <rdar://problem/22805519> be less strict about old mach-o binaries
uint32_t mainSDK = sMainExecutable->sdkVersion();
gLinkContext.strictMachORequired = (mainSDK >= DYLD_MACOSX_VERSION_10_12) || gLinkContext.processUsingLibraryValidation;
#else
// simulators, iOS, tvOS, and watchOS are always strict
gLinkContext.strictMachORequired = true;
#endif

#if SUPPORT_ACCELERATE_TABLES
sAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT);
#else
sAllImages.reserve(INITIAL_IMAGE_COUNT);
#endif

// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
checkVersionedPaths();
#endif


// dyld_all_image_infos image list does not contain dyld
// add it as dyldPath field in dyld_all_image_infos
// for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_IPHONE_SIMULATOR
// get path of host dyld from table of syscall vectors in host dyld
void* addressInDyld = gSyscallHelpers;
#else
// get path of dyld itself
void* addressInDyld = (void*)&__dso_handle;
#endif
char dyldPathBuffer[MAXPATHLEN+1];
int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
if ( len > 0 ) {
dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
gProcessInfo->dyldPath = strdup(dyldPathBuffer);
}

// load any inserted 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();
}
}

// <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();
}
#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();
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);
}
gLinkContext.linkingMainExecutable = false;

// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);

// If cache has branch island dylibs, tell debugger about them
if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) {
uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount;
dyld_image_info info[count];
const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset);
// <rdar://problem/20799203> empty branch pools can be in development cache
if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) {
for (int poolIndex=0; poolIndex < count; ++poolIndex) {
uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide;
info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr;
info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands";
info[poolIndex].imageFileModDate = 0;
}
// add to all_images list
addImagesToAllImages(count, info);
// tell gdb about new branch island images
gProcessInfo->notification(dyld_image_adding, count, info);
}
}

CRSetCrashLogMessage("dyld: launch, running initializers");
#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()
dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0);
notifyMonitoringDyldMain();

// find entry point for main executable
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
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->getMain();
*startGlue = 0;
}
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}

CRSetCrashLogMessage(NULL);

if (sSkipMain) {
dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN, 0, 0);
result = (uintptr_t)&fake_main;
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}

return result;
}

详细过程

运行参数处理

这步主要做的就是相关的运行环境参数获取,运行参数配置等等:

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
// 环境变量中获取主程序的hash
mainExecutableCDHash = mainExecutableCDHashBuffer;
// mach-o header
sMainExecutableMachHeader = mainExecutableMH;
// 主程序slide值,rebase会用到
sMainExecutableSlide = mainExecutableSlide;
// 设置众多环境参数
setContext(mainExecutableMH, argc, argv, envp, apple);
// 获取执行程序路径
sExecPath = _simple_getenv(apple, "executable_path");
// 配置条件限制参数
configureProcessRestrictions(mainExecutableMH);
#if __MAC_OS_X_VERSION_MIN_REQUIRED
if ( gLinkContext.processIsRestricted ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}

// cpu相关处理
getHostInfo(mainExecutableMH, mainExecutableSlide);
...
省略众多
...

setContext( )中设置了太多的参数,大致看下就行了:

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
/// dyld-519.2.1
/// dyld.cpp
/// line 4232

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])
{
gLinkContext.loadLibrary = &libraryLocator;
gLinkContext.terminationRecorder = &terminationRecorder;
gLinkContext.flatExportFinder = &flatFindExportedSymbol;
gLinkContext.coalescedExportFinder = &findCoalescedExportedSymbol;
gLinkContext.getCoalescedImages = &getCoalescedImages;
gLinkContext.undefinedHandler = &undefinedHandler;
gLinkContext.getAllMappedRegions = &getMappedRegions;
gLinkContext.bindingHandler = NULL;
gLinkContext.notifySingle = &notifySingle;
gLinkContext.notifyBatch = &notifyBatch;
gLinkContext.removeImage = &removeImage;
gLinkContext.registerDOFs = &registerDOFs;
gLinkContext.clearAllDepths = &clearAllDepths;
gLinkContext.printAllDepths = &printAllDepths;
gLinkContext.imageCount = &imageCount;
gLinkContext.setNewProgramVars = &setNewProgramVars;
gLinkContext.inSharedCache = &inSharedCache;
gLinkContext.setErrorStrings = &setErrorStrings;
#if SUPPORT_OLD_CRT_INITIALIZATION
gLinkContext.setRunInitialzersOldWay= &setRunInitialzersOldWay;
#endif
gLinkContext.findImageContainingAddress = &findImageContainingAddress;
gLinkContext.addDynamicReference = &addDynamicReference;
#if SUPPORT_ACCELERATE_TABLES
gLinkContext.notifySingleFromCache = &notifySingleFromCache;
gLinkContext.getPreInitNotifyHandler= &getPreInitNotifyHandler;
gLinkContext.getBoundBatchHandler = &getBoundBatchHandler;
#endif
gLinkContext.bindingOptions = ImageLoader::kBindingNone;
gLinkContext.argc = argc;
gLinkContext.argv = argv;
gLinkContext.envp = envp;
gLinkContext.apple = apple;
gLinkContext.progname = (argv[0] != NULL) ? basename(argv[0]) : "";
gLinkContext.programVars.mh = mainExecutableMH;
gLinkContext.programVars.NXArgcPtr = &gLinkContext.argc;
gLinkContext.programVars.NXArgvPtr = &gLinkContext.argv;
gLinkContext.programVars.environPtr = &gLinkContext.envp;
gLinkContext.programVars.__prognamePtr=&gLinkContext.progname;
gLinkContext.mainExecutable = NULL;
gLinkContext.imageSuffix = NULL;
gLinkContext.dynamicInterposeArray = NULL;
gLinkContext.dynamicInterposeCount = 0;
gLinkContext.prebindUsage = ImageLoader::kUseAllPrebinding;
#if TARGET_IPHONE_SIMULATOR
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
#else
gLinkContext.sharedRegionMode = ImageLoader::kUseSharedRegion;
#endif
}

加载共享缓存

这一步先检查共享缓存配置,如果配置使用共享缓存,就调用 mapSharedCache() 加载共享缓存:

1
2
3
4
5
6
7
8
9
10
	// load shared cache
checkSharedRegionDisable((mach_header*)mainExecutableMH);
#if TARGET_IPHONE_SIMULATOR
// <HACK> until <rdar://30773711> is fixed
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
// </HACK>
#endif
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
mapSharedCache();
}

mapSharedCache() 中做了一些参数配置,然后调用 loadDyldCache() 进入加载逻辑:

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
/// dyld-519.2.1
/// dyld.cpp
/// line 3629

static void mapSharedCache()
{
dyld3::SharedCacheOptions opts;
opts.cacheDirOverride = sSharedCacheOverrideDir;
opts.forcePrivate = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);
#if __x86_64__ && !TARGET_IPHONE_SIMULATOR
opts.useHaswell = sHaswell;
#else
opts.useHaswell = false;
#endif
opts.verbose = gLinkContext.verboseMapping;
loadDyldCache(opts, &sSharedCacheLoadInfo);

// update global state
if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate;
dyld::gProcessInfo->sharedCacheSlide = sSharedCacheLoadInfo.slide;
dyld::gProcessInfo->sharedCacheBaseAddress = (unsigned long)sSharedCacheLoadInfo.loadAddress;
sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID);
dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0], {0,0}, {{ 0, 0 }}, (const mach_header *)sSharedCacheLoadInfo.loadAddress);
}
}

共享缓存加载要细分三种情况:

  1. 只加载到当前进程,调用 mapCachePrivate()
  2. 共享缓存已经加载到共享空间了,不再做加载处理;
  3. 当前进程是首次加载共享缓存的进程:mapCacheSystemWide()
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
/// dyld-519.2.1
/// SharedCacheRuntime.cpp
/// line 559

bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->cachedDylibsGroup = nullptr;
results->errorMessage = nullptr;

#if TARGET_IPHONE_SIMULATOR
// simulator only supports mmap()ing cache privately into process
return mapCachePrivate(options, results);
#else
if ( options.forcePrivate ) {
// mmap cache into this process only
return mapCachePrivate(options, results);
}
else {
// fast path: when cache is already mapped into shared region
if ( reuseExistingCache(options, results) )
return (results->errorMessage != nullptr);

// slow path: this is first process to load cache
return mapCacheSystemWide(options, results);
}
#endif
}

上述情况1、情况3涉及到共享缓存的具体解析,解析逻辑很长,这里就不再具体分析了,自己可以阅读源码分析。

1
2
3
4
5
6
7
8
/// dyld-519.2.1
/// SharedCacheRuntime.cpp
/// line 462

static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
...
}
1
2
3
4
5
6
7
8
/// dyld-519.2.1
/// SharedCacheRuntime.cpp
/// line 424

static bool mapCacheSystemWide(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
...
}

实例化主执行程序的ImageLoader

dyld::_main() 中调用的位置是:

1
2
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

这里主要调用了 instantiateFromLoadedImage() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// dyld-519.2.1
/// dyld.cpp
/// line 2842

// The kernel maps in main executable before dyld gets control. We need to
// make an ImageLoader* for the already mapped in main executable.
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";
}

instantiateFromLoadedImage() 首先进行了Mach-O兼容性检查,调用 isCompatibleMachO() 来检查mach-header的magic、cputype、cpusubtype等相关属性,判断Mach-O文件的兼容性,如果兼容性满足,那么就进入 ImageLoaderMachO::instantiateMainExecutable() 实例化主程序的 ImageLoader:

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
/// dyld-519.2.1
/// ImageLoaderMachO.cpp
/// line 569

// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
// sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}

这里调用 sniffLoadCommands() 方法来嗅探(获取、解析)相关数据,包括:

  • compressed
    compressed 主要是判断 LC_DYLD_INFO 和 LC_DYLD_INFO_ONLY 加载命令中的大小和 dyld_info_command 结构体的大小来判断,如果大小不等,那么就说明命令被压缩了。
1
2
3
4
5
6
7
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
if ( cmd->cmdsize != sizeof(dyld_info_command) )
throw "malformed mach-o image: LC_DYLD_INFO size wrong";
dyldInfoCmd = (struct dyld_info_command*)cmd;
*compressed = true;
break;
  • segCount
    根据 LC_SEGMENT_COMMAND 加载命令来统计段数量,这里说明了段的数量是不能超过255的。
1
2
3
4
5
6
7
8
9
10
case LC_SEGMENT_COMMAND:
segCmd = (struct macho_segment_command*)cmd;
...
if ( segCmd->vmsize != 0 )
*segCount += 1;
...

// fSegmentsArrayCount is only 8-bits
if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
  • libCount
    这里根据 LC_LOAD_DYLIB、LC_LOAD_WEAK_DYLIB、LC_REEXPORT_DYLIB、LC_LOAD_UPWARD_DYLIB 这几个加载命令来统计库的数量,同样库的数量这里也规定了是不能超过4095的
1
2
3
4
5
6
7
8
9
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
*libCount += 1;

// fSegmentsArrayCount is only 8-bits
if ( *libCount > 4095 )
dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);

Tips:源码中注释成了 // fSegmentsArrayCount is only 8-bits,说明代码copy-on-write 在哪里都一样啊,😆

  • codeSigCmd
    通过 LC_CODE_SIGNATURE 加载命令获取代码签名。
1
2
3
4
5
6
7
8
case LC_CODE_SIGNATURE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong";
// <rdar://problem/22799652> only support one LC_CODE_SIGNATURE per image
if ( *codeSigCmd != NULL )
throw "malformed mach-o image: multiple LC_CODE_SIGNATURE load commands";
*codeSigCmd = (struct linkedit_data_command*)cmd;
break;
  • encryptCmd
    通过 LC_ENCRYPTION_INFO 加载命令获取段的加密信息。
1
2
3
4
5
6
7
8
case LC_ENCRYPTION_INFO:
if ( cmd->cmdsize != sizeof(encryption_info_command) )
throw "malformed mach-o image: LC_ENCRYPTION_INFO size wrong";
// <rdar://problem/22799652> only support one LC_ENCRYPTION_INFO per image
if ( *encryptCmd != NULL )
throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO load commands";
*encryptCmd = (encryption_info_command*)cmd;
break;

解析完以后根据 compressed 的值来判断调用 ImageLoaderMachOCompressed::instantiateMainExecutable()
或者
ImageLoaderMachOClassic::instantiateMainExecutable(),
这里以 ImageLoaderMachOCompressed::instantiateMainExecutable() 源码为例:

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
/// dyld-519.2.1
/// ImageLoaderMachOCompressed.cpp
/// line 74

// create image for main executable
ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path,
unsigned int segCount, unsigned int libCount, const LinkContext& context)
{
ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);

// set slide for PIE programs
image->setSlide(slide);

// for PIE record end of program, to know where to start loading dylibs
if ( slide != 0 )
fgNextPIEDylibAddress = (uintptr_t)image->getEnd();

image->disableCoverageCheck();
image->instantiateFinish(context);
image->setMapped(context);

if ( context.verboseMapping ) {
dyld::log("dyld: Main executable mapped %s\n", path);
for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
const char* name = image->segName(i);
if ( (strcmp(name, "__PAGEZERO") == 0) || (strcmp(name, "__UNIXSTACK") == 0) )
dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segPreferredLoadAddress(i), image->segPreferredLoadAddress(i)+image->segSize(i));
else
dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segActualLoadAddress(i), image->segActualEndAddress(i));
}
}

return image;
}

这里大致总结为4步:

  1. ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart()
    创建 ImageLoaderMachOCompressed 对象;
  2. image->disableCoverageCheck()
    段有效长度检查(这样解释可能不太准确)
  3. image->instantiateFinish(context)
    • 解析其它众多加载命令;
    • this->setDyldInfo() 设置动态库链接信息;
    • this->setSymbolTableInfo() 设置符号表相关信息;
  4. image->setMapped(context)
    注册通知回调、计算执行时间、注册mach message等等

在调用完 ImageLoaderMachO::instantiateMainExecutable() 后调用 addImage() 将image加入到sAllImages全局镜像列表中,并申请内存,将image映射到内存中。


Tips!
dyld_info_command 结构体的定义如下,感兴趣可以仔细阅读下:

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
/// dyld-519.2.1
/// loader.cpp
/// line 1255

struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */

/*
* Dyld rebases an image whenever dyld loads it at an address different
* from its preferred address. The rebase information is a stream
* of byte sized opcodes whose symbolic names start with REBASE_OPCODE_.
* Conceptually the rebase information is a table of tuples:
* <seg-index, seg-offset, type>
* The opcodes are a compressed way to encode the table by only
* encoding when a column changes. In addition simple patterns
* like "every n'th offset for m times" can be encoded in a few
* bytes.
*/
uint32_t rebase_off; /* file offset to rebase info */
uint32_t rebase_size; /* size of rebase info */

/*
* Dyld binds an image during the loading process, if the image
* requires any pointers to be initialized to symbols in other images.
* The bind information is a stream of byte sized
* opcodes whose symbolic names start with BIND_OPCODE_.
* Conceptually the bind information is a table of tuples:
* <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
* The opcodes are a compressed way to encode the table by only
* encoding when a column changes. In addition simple patterns
* like for runs of pointers initialzed to the same value can be
* encoded in a few bytes.
*/
uint32_t bind_off; /* file offset to binding info */
uint32_t bind_size; /* size of binding info */

/*
* Some C++ programs require dyld to unique symbols so that all
* images in the process use the same copy of some code/data.
* This step is done after binding. The content of the weak_bind
* info is an opcode stream like the bind_info. But it is sorted
* alphabetically by symbol name. This enable dyld to walk
* all images with weak binding information in order and look
* for collisions. If there are no collisions, dyld does
* no updating. That means that some fixups are also encoded
* in the bind_info. For instance, all calls to "operator new"
* are first bound to libstdc++.dylib using the information
* in bind_info. Then if some image overrides operator new
* that is detected when the weak_bind information is processed
* and the call to operator new is then rebound.
*/
uint32_t weak_bind_off; /* file offset to weak binding info */
uint32_t weak_bind_size; /* size of weak binding info */

/*
* Some uses of external symbols do not need to be bound immediately.
* Instead they can be lazily bound on first use. The lazy_bind
* are contains a stream of BIND opcodes to bind all lazy symbols.
* Normal use is that dyld ignores the lazy_bind section when
* loading an image. Instead the static linker arranged for the
* lazy pointer to initially point to a helper function which
* pushes the offset into the lazy_bind area for the symbol
* needing to be bound, then jumps to dyld which simply adds
* the offset to lazy_bind_off to get the information on what
* to bind.
*/
uint32_t lazy_bind_off; /* file offset to lazy binding info */
uint32_t lazy_bind_size; /* size of lazy binding infs */

/*
* The symbols exported by a dylib are encoded in a trie. This
* is a compact representation that factors out common prefixes.
* It also reduces LINKEDIT pages in RAM because it encodes all
* information (name, address, flags) in one small, contiguous range.
* The export area is a stream of nodes. The first node sequentially
* is the start node for the trie.
*
* Nodes for a symbol start with a uleb128 that is the length of
* the exported symbol information for the string so far.
* If there is no exported symbol, the node starts with a zero byte.
* If there is exported info, it follows the length.
*
* First is a uleb128 containing flags. Normally, it is followed by
* a uleb128 encoded offset which is location of the content named
* by the symbol from the mach_header for the image. If the flags
* is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is
* a uleb128 encoded library ordinal, then a zero terminated
* UTF8 string. If the string is zero length, then the symbol
* is re-export from the specified dylib with the same name.
* If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following
* the flags is two uleb128s: the stub offset and the resolver offset.
* The stub is used by non-lazy pointers. The resolver is used
* by lazy pointers and must be called to get the actual address to use.
*
* After the optional exported symbol information is a byte of
* how many edges (0-255) that this node has leaving it,
* followed by each edge.
* Each edge is a zero terminated UTF8 of the addition chars
* in the symbol, followed by a uleb128 offset for the node that
* edge points to.
*
*/
uint32_t export_off; /* file offset to lazy binding info */
uint32_t export_size; /* size of lazy binding infs */
};

加载插入的动态库

这一步处理是加载环境变量 DYLD_INSERT_LIBRARIES 中配置的动态库,加载逻辑代码在 dyld::_main() 中实现如下,先判断环境变量 DYLD_INSERT_LIBRARIES 中是否存在要加载的动态库,如果存在,就调用 loadInsertedDylib() 方法依次加载:

1
2
3
4
5
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}

loadInsertedDylib() 方法里,配置好参数后,又是通过调用 load() 方法来完成动态库的加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// dyld-519.2.1
/// dyld.cpp
/// line 4612

static void loadInsertedDylib(const char* path)
{
ImageLoader* image = NULL;
unsigned cacheIndex;
try {
LoadContext context;
...
image = load(path, context, cacheIndex);
}
catch (const char* msg) {
...
}
catch (...) {
...
}
}

load() 方法中会根据不同路径依次遍历搜索加载动态库,这里逻辑太复杂就不再仔细罗列了,大致搜索的路径是:

搜索过程中还会涉及缓存查找。

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
/// dyld-519.2.1
/// dyld.cpp
/// line 3558

//
// Given all the DYLD_ environment variables, the general case for loading libraries
// is that any given path expands into a list of possible locations to load. We
// also must take care to ensure two copies of the "same" library are never loaded.
//
// The algorithm used here is that there is a separate function for each "phase" of the
// path expansion. Each phase function calls the next phase with each possible expansion
// of that phase. The result is the last phase is called with all possible paths.
//
// To catch duplicates the algorithm is run twice. The first time, the last phase checks
// the path against all loaded images. The second time, the last phase calls open() on
// the path. Either time, if an image is found, the phases all unwind without checking
// for other paths.
//
ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
{
CRSetCrashLogMessage2(path);
const char* orgPath = path;
cacheIndex = UINT32_MAX;

//dyld::log("%s(%s)\n", __func__ , path);
char realPath[PATH_MAX];
// when DYLD_IMAGE_SUFFIX is in used, do a realpath(), otherwise a load of "Foo.framework/Foo" will not match
if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL) ) {
if ( realpath(path, realPath) != NULL )
path = realPath;
}

// try all path permutations and check against existing loaded images

ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL);
if ( image != NULL ) {
CRSetCrashLogMessage2(NULL);
return image;
}

// try all path permutations and try open() until first success
std::vector<const char*> exceptions;
image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions);
#if !TARGET_IPHONE_SIMULATOR
// <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache
if ( image == NULL)
image = loadPhase2cache(path, orgPath, context, cacheIndex, &exceptions);
#endif
CRSetCrashLogMessage2(NULL);
if ( image != NULL ) {
// <rdar://problem/6916014> leak in dyld during dlopen when using DYLD_ variables
for (std::vector<const char*>::iterator it = exceptions.begin(); it != exceptions.end(); ++it) {
free((void*)(*it));
}
// if loaded image is not from cache, but original path is in cache
// set gSharedCacheOverridden flag to disable some ObjC optimizations
if ( !gSharedCacheOverridden && !image->inSharedCache() && image->isDylib() && cacheablePath(path) && inSharedCache(path) ) {
gSharedCacheOverridden = true;
}
return image;
}
else if ( exceptions.size() == 0 ) {
if ( context.dontLoad ) {
return NULL;
}
else
throw "image not found";
}
else {
const char* msgStart = "no suitable image found. Did find:";
const char* delim = "\n\t";
size_t allsizes = strlen(msgStart)+8;
for (size_t i=0; i < exceptions.size(); ++i)
allsizes += (strlen(exceptions[i]) + strlen(delim));
char* fullMsg = new char[allsizes];
strcpy(fullMsg, msgStart);
for (size_t i=0; i < exceptions.size(); ++i) {
strcat(fullMsg, delim);
strcat(fullMsg, exceptions[i]);
free((void*)exceptions[i]);
}
throw (const char*)fullMsg;
}
}

链接主程序

链接主程序的代码在 dyld::_main() 中实现如下:

1
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);

link() 函数内部实际调用的是ImageLoader的 link() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
...
context.clearAllDepths();
this->recursiveUpdateDepth(context.imageCount());
...
this->recursiveRebase(context);
context.notifyBatch(dyld_image_state_rebased, false);
...
this->recursiveBind(context, forceLazysBound, neverUnload);
...
context.notifyBatch(dyld_image_state_bound, false);
...
this->recursiveGetDOFSections(context, dofs);
context.registerDOFs(dofs);
...
}
  • recursiveLoadLibraries() 递归加载加载主程序所依赖的库;
  • recursiveUpdateDepth() 递归进行镜像排序;
  • recursiveRebase()递归进行rebase修正地址;
  • recursiveBind() 递归绑定符号表;
  • recursiveGetDOFSections()、context.registerDOFs() 注册DOF sections供DTrace调试用;

Tips
DOF:Dtrace Object Format
Objc中国:DTrace

链接插入的动态库

链接前面加载的动态库,这里和链接主程序一样的,循环调用 link() 函数

弱符号绑定

弱符号绑定是在所有插入的动态库都链接完成后进行的,实现逻辑的的代码在 dyld::_main() 中如下:

1
2
3
4
5
6
sMainExecutable->weakBind(gLinkContext);

void ImageLoader::weakBind(const LinkContext& context)
{
...
}
  1. getCoalescedImages()
    合并所有动态库的弱符号到一个列表里;
  2. initializeCoalIterator()
    排序需要绑定的弱符号;
  3. incrementCoalIterator()
    进行弱符号绑定;

运行初始化方法

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
/// dyld-519.2.1
/// dyld.cpp
/// line 1444

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);
...
}

首先运行的是所有插入动态库的initialzers,然后是主执行程序的,这里会调用ImageLoader的 runInitializers() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// dyld-519.2.1
/// ImageLoader.cpp
/// line 507

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);
}

processInitializers() 中调用 doInitialization()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// dyld-519.2.1
/// ImageLoaderMachO.cpp
/// line 2307

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());

// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);

CRSetCrashLogMessage2(NULL);

return (fHasDashInit || fHasInitializers);
}

查找并返回主程序入口地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
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->getMain();
*startGlue = 0;
}

根据 LC_MAIN 或者 LC_UNIXTHREAD 加载命令获取函数入口地址

0%