在 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 相关的信息:

(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加载的大致流程如下:

st=>start: _dyld_start
e=>end: dyld::_main
op=>operation: dyldbootstrap::start
op1=>operation: dyld::initializeMainExecutable()
op2=>operation: ImageLoader...
op3=>operation: ImageLoaderMachO...

st->op->op1->op2->op3->e

详细过程

寄存器初始化

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

/// 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() 方法:

// 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

/// 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. 查找并返回主程序入口地址
/// 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;
}

详细过程

运行参数处理

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

// 环境变量中获取主程序的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( )中设置了太多的参数,大致看下就行了:

/// 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() 加载共享缓存:

	// 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() 进入加载逻辑:

/// 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()
/// 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涉及到共享缓存的具体解析,解析逻辑很长,这里就不再具体分析了,自己可以阅读源码分析。

/// dyld-519.2.1
/// SharedCacheRuntime.cpp
/// line 462

static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    ...
}
/// dyld-519.2.1
/// SharedCacheRuntime.cpp
/// line 424

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

实例化主执行程序的ImageLoader

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

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

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

/// 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:

/// 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 结构体的大小来判断,如果大小不等,那么就说明命令被压缩了。
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的。
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的
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 加载命令获取代码签名。
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 加载命令获取段的加密信息。
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() 源码为例:

/// 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 结构体的定义如下,感兴趣可以仔细阅读下:

/// 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() 方法依次加载:

// 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() 方法来完成动态库的加载:

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

op=>operation: DYLD_ROOT_PATH
op1=>operation: LD_LIBRARY_PATH
op2=>operation: DYLD_FRAMEWORK_PATH or DYLD_LIBRARY_PATH
op3=>operation: raw path
op4=>operation: DYLD_FALLBACK_FRAMEWORK_PATH
op->op1->op2->op3->op4

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

/// 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() 中实现如下:

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

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

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() 中如下:

sMainExecutable->weakBind(gLinkContext);

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

运行初始化方法

/// 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() 方法

/// 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()

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

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

// 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 加载命令获取函数入口地址