iOS APP启动-Main函数之前的那些事儿

上一篇文章中我们介绍了应用启动在objc_init方法执行前的调用堆栈,根据这个堆栈我们可以看出在main函数之前实际上系统内核以及dyld还做了很多的操作,那么这篇文章我们来详细的看一下在这个过程中到底做了哪些事情。

我们在来看下这这张图:

从上图中我们看一看到应用启动的入口实际是_dyld_start函数,我们从XNU源码dyldStartup.s中找到了这个方法:

__dyld_start

__dyld_start:
	//..........省略掉汇编代码
	// call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
	bl	__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
  //..........省略掉汇编代码

__dyld_start是一个汇编方法(看不懂😢),不过我们也可以看出这个方法里实际上是调用了dyldbootstrap::start方法,恰好也验证了我们截图中的调用堆栈。

dyldbootstrap::start

dyldbootstrap::start(...), 首先bootstrapping dyld, 然后调用dyld::_main核心方法

// appsMachHeader 即mach-o文件的header字段
// argc 即 argument count 即程序运行的参数个数
// argv[] 即 argument value 是一个字符串数组 用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数
// slide 偏移量
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
				intptr_t slide, const struct macho_header* dyldsMachHeader,
				uintptr_t* startGlue)
{
	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
	// 如果slide dyld, 我们必须 fixeup dyly中的内容
	if ( slide != 0 ) {
		// 重新设定dyld
		rebaseDyld(dyldsMachHeader, slide);
	}

	// allow dyld to use mach messaging
	// 允许dyld使用mach消息传递
	mach_init();

	// kernel sets up env pointer to be just past end of agv array
	// 内核设置的env pointers, 也就是环境参数
	// envp = environment pointer
	// 取出argv的第argc条数据 但是实际上argv 只有argc个参数
	// 因此 envp 默认是紧挨着argv存储的
	const char** envp = &argv[argc+1];
	
	// kernel sets up apple pointer to be just past end of envp array
	// kernel将apple指针设置为刚好超出envp数组的末尾
	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
	// 在dyld中运行所有C初始化程序
	runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

	// now that we are done bootstrapping dyld, call dyld's main
	// 调用dyld的main
	uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
	return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

这里附上start方法的重要参数macho_header的结构体:

struct mach_header_64 {
    uint32_t    magic;      /* 区分系统架构版本 */
    cpu_type_t  cputype;    /*CPU类型 */
    cpu_subtype_t   cpusubtype; /* CPU具体类型 */
    uint32_t    filetype;   /* 文件类型 */
    uint32_t    ncmds;      /* loadcommands 条数,即依赖库数量*/
    uint32_t    sizeofcmds; /* 依赖库大小 */
    uint32_t    flags;      /* 标志位 */
    uint32_t    reserved;   /* 保留字段,暂没有用到*/
};

start方法的主要作用就是:先读取Mach-O文件的头部信息,设置虚拟地址偏移,这里的偏移主要用于重定向。接下来就是初始化Mach-O文件,用于后续加载库文件和DATA数据,再运行C++的初始化器,最后进入dyly的主函数。

dyld::_main

// dyld的main函数 dyld的入口方法kernel加载dyld并设置设置一些寄存器并调用此函数,之后跳转到__dyld_start
// mainExecutableSlide 主程序的slider,用于做重定向 会在main方法中被赋值
// mainExecutableMH 主程序MachO的header
// argc 表示main函数参数个数
// argv 表示main函数的参数值 argv[argc] 可以获取到参数值
// envp[] 表示以设置好的环境变量
// apple 是从envp开始获取到第一个值为NULL的指针地址
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)
{
	uintptr_t result = 0;
	// 1 设置运行环境,处理环境变量 mainExecutableMH为macho_header类型
	// 表示的是当前主程序的Mach-O头部信息, 有了头部信息, 加载器就可以从头开始, 遍历整个Mach-O文件的信息
	sMainExecutableMachHeader = mainExecutableMH;

	CRSetCrashLogMessage("dyld: launch started");

	// 设置上下文  包括一些回调函数, 参数与标志设置信息
	setContext(mainExecutableMH, argc, argv, envp, apple);

	// Pickup the pointer to the exec path.
	// 获取指向exec路径的指针 执行exec相关指令 apple是一个数组 所以apple表示数组首元素的地址
	// _simple_getenv 方法可以理解为从apple中获取"executable_path"对应的值
	sExecPath = _simple_getenv(apple, "executable_path");

	// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
	if (!sExecPath) sExecPath = apple[0];

	// 将可执行文件的路径由相对路径转化成绝对路径
	bool ignoreEnvironmentVariables = false;
	// 判断是否是相对路径的条件
	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
	//  获取可执行文件去除前面的路径, 获取它的name
	// strrchr:在参数 sExecPath 所指向的字符串中搜索最后一次出现字符 '/'的位置
	sExecShortName = ::strrchr(sExecPath, '/');
	// 如果获取到了文件名的位置
	if ( sExecShortName != NULL )
		// 文件名真正的起始位置
		++sExecShortName;
	else
		// 文件名起始位置就是绝对路径
		sExecShortName = sExecPath;

	// 配置进程是否受到限制
    sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
	// 如果进程受限
    if ( sProcessIsRestricted ) {
#if SUPPORT_LC_DYLD_ENVIRONMENT
		// 检查加载命令环境变量
		// 遍历Mach-O中所有的LC_DYLD_ENVIRONMENT加载命令, 然后调用processDyldEnvironmentVariable()对不同的环境变量做相应的处理
		checkLoadCommandEnvironmentVariables();
#endif
		// 删除进程的LD_LIBRARY_PATH与所有以DYLD_开头的环境变量, 这样以后创建的子进程就不包含这些环境变量了
		pruneEnvironmentVariables(envp, &apple);
		// set again because envp and apple may have changed or moved
		// 重新设置链接上下文。这一步执行的主要目的是由于环境变量发生变化了, 需要更新进程的envp与apple参数
		setContext(mainExecutableMH, argc, argv, envp, apple);
	}
	else {
		if ( !ignoreEnvironmentVariables )
			// 检查环境变量
			checkEnvironmentVariables(envp);
		defaultUninitializedFallbackPaths(envp);
	}

	// 打印信息 不需要关注
	if ( sEnv.DYLD_PRINT_OPTS )
		printOptions(argv);
	if ( sEnv.DYLD_PRINT_ENV ) 
		printEnvironmentVariables(envp);

	// 获取当前设备的CPU架构信息
	getHostInfo(mainExecutableMH, mainExecutableSlide);

	// install gdb notifier
	// 注册gdb的监听者, 用于调试
	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
	sAllImages.reserve(INITIAL_IMAGE_COUNT);
	sImageRoots.reserve(16);
	sAddImageCallbacks.reserve(4);
	sRemoveImageCallbacks.reserve(4);
	sImageFilesNeedingTermination.reserve(16);
	sImageFilesNeedingDOFUnregistration.reserve(8);
	
	
#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
	
	//2 初始化主程序
	try {
		// add dyld itself to UUID list
		// 将dyld添加到UUIDlist中
		addDyldImageToUUIDList();

		CRSetCrashLogMessage(sLoadingCrashMessage);
		// instantiate ImageLoader for main executable
		//  加载sExecPath路径下的可执行文件, 实例化一个ImageLoader对象
		sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
		// 设置上下文, 将MainExecutable 这个 ImageLoader设置给链接上下文, 配置链接上下文其他变量
		gLinkContext.mainExecutable = sMainExecutable;
		gLinkContext.processIsRestricted = sProcessIsRestricted;
		gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
		gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

		// load shared cache
		// 3 加载共享缓存
		checkSharedRegionDisable();
	#if DYLD_SHARED_CACHE_SUPPORT
		if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )
			// 映射共享缓存
			mapSharedCache();
	#endif

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

		// load any inserted libraries
		// 4 加载插入的动态库
		// 变量 `DYLD_INSERT_LIBRARIES` 环境变量, 调用`loadInsertedDylib`方法加载所有要插入的库,
		// 这些库都被加入到`sAllImages`数组中
		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.
		// 记录插入的库的数量,以便进行统一搜索插入的库,然后是main,然后是其他
		sInsertedDylibCount = sAllImages.size()-1;

		// link main executable
		// 5 链接主程序
		// 开始链接主程序, 此时主程序已经被加载到gLinkContext.mainExecutable中,
		// 调用 link 链接主程序。内核调用的是ImageLoader::link 函数。
		gLinkContext.linkingMainExecutable = true;
		// link方法
		link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
		// 设置永不递归卸载
		sMainExecutable->setNeverUnloadRecursive();
		// mach-o header中的MH_FORCE_FLAT
		if ( sMainExecutable->forceFlat() ) {
			gLinkContext.bindFlat = true;
			gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
		}

		// link any inserted libraries
		// 6 链接插入的动态库
		// 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
		// 对 sAllimages (除了主程序的Image外)中的库调用link进行链接,
		// 然后调用 registerInterposing 注册符号插入, 例如是libSystem就是此时加入的
		if ( sInsertedDylibCount > 0 ) {
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1];
				// link
				link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
				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];
				// 注册符号插入,Interposition, 是通过编写与函数库同名的函数来取代函数库的行为.
				image->registerInterposing();
			}
		}

		// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
		for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
			ImageLoader* image = sAllImages[i];
			if ( image->inSharedCache() )
				continue;
			image->registerInterposing();
		}

		// 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
		// 7 执行弱符号绑定
		sMainExecutable->weakBind(gLinkContext);
		
		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
		// 8 执行初始化方法
		// 执行初始化方法, 其中`+load` 和constructor方法就是在这里执行,
		// `initializeMainExecutable`方法先是内部调用动态库的初始化方法, 然后调用主程序的初始化方法
		initializeMainExecutable(); 
	#endif
		// find entry point for main executable
		// 9 查找APP入口点并返回
		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);
	
	return result;
}

下面我们对main函数进行拆分讲解

1 设置运行环境,处理环境变量

这一步我们主要关注sExecPath,processRestricted,getHostInfo这几个方法。

sExecPath
sExecPath = _simple_getenv(apple, "executable_path");

我们在介绍参数的时候介绍到 apple 实际上存储这应用的环境变量的数组,executable_path就表示执行路径,而_simple_getenv方法就是从apple中获取executable_path对应的值。不过这里获取到的可能是一个相对路径,而dyld判断是否为相对路径的条件:

if ( sExecPath[0] != '/' ) {
    // 相对路径
    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;
		}
}

这样我们就可以获取到执行文件的绝对路径。在获取到绝对路径后,我们可以根据绝对路径获取到执行文件的文件名:

sExecShortName = ::strrchr(sExecPath, '/');

strrchr方法的功能为:在参数 sExecPath 所指向的字符串中搜索最后一次出现字符 '/'的位置

processRestricted

进程是否受限,这里我们主要关注下Mach-O相关的一个判断:

// 进程受限
static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation)
{			
	// <rdar://problem/13158444&13245742> Respect __RESTRICT,__restrict section for root processes
	// 段名受限。当Mach-O包含一个__RESTRICT/__restrict段时,进程会被设置成受限
	if ( hasRestrictedSegment(mainExecutableMH) ) {
		// existence of __RESTRICT/__restrict section make process restricted
		sRestrictedReason = restrictedBySegment;
		return true;
	}
    return false;
}

hasRestrictedSegment方法的实现如下:

//dyld::log("seg name: %s\n", seg->segname);
				if (strcmp(seg->segname, "__RESTRICT") == 0) {
					const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
					const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
					for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
						if (strcmp(sect->sectname, "__restrict") == 0) 
							return true;
					}
				}

实际上是从Mach-O文件中依次读取所有的segment,判断segment->segname是否包含__RESTRICT字符串来判断是否受限。

getHostInfo

getHostInfo是用来获取当前设备的CPU架构信息。

我们来简单看下这个方法的实现:

static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if CPU_SUBTYPES_SUPPORTED
#if __ARM_ARCH_7K__
	sHostCPU		= CPU_TYPE_ARM;
	sHostCPUsubtype = CPU_SUBTYPE_ARM_V7K;
#elif __ARM_ARCH_7A__
	sHostCPU		= CPU_TYPE_ARM;
	sHostCPUsubtype = CPU_SUBTYPE_ARM_V7;
#elif __ARM_ARCH_6K__
	sHostCPU		= CPU_TYPE_ARM;
	sHostCPUsubtype = CPU_SUBTYPE_ARM_V6;
#elif __ARM_ARCH_7F__
	sHostCPU		= CPU_TYPE_ARM;
	sHostCPUsubtype = CPU_SUBTYPE_ARM_V7F;
#elif __ARM_ARCH_7S__
	sHostCPU		= CPU_TYPE_ARM;
	sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S;
#else
	struct host_basic_info info;
	mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
	mach_port_t hostPort = mach_host_self();
	kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
	if ( result != KERN_SUCCESS )
		throw "host_info() failed";
	sHostCPU		= info.cpu_type;
	sHostCPUsubtype = info.cpu_subtype;
	mach_port_deallocate(mach_task_self(), hostPort);
  #if __x86_64__
	#if TARGET_IPHONE_SIMULATOR
	  sHaswell = false;
	#else
	  sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H);
	  // <rdar://problem/18528074> x86_64h: Fall back to the x86_64 slice if an app requires GC.
	  if ( sHaswell ) {
		if ( isGCProgram(mainExecutableMH, mainExecutableSlide) ) {
			// When running a GC program on a haswell machine, don't use and 'h slices
			sHostCPUsubtype = CPU_SUBTYPE_X86_64_ALL;
			sHaswell = false;
			gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
		}
	  }
	#endif
  #endif
#endif
#endif
}

设置环境变量完成且获取了CPU信息后,dyld就开始准备初始化主程序了,下面我们看下main函数的下一步初始化主程序。

2 初始化主程序

初始化主程序主要做了两件事:

  • 将dyld添加到UUIDlist中
  • 加载可执行文件 实例化ImageLoader对象

下面我们来详细看下这两步分别都做了什么

addDyldImageToUUIDList

addDyldImageToUUIDList方法是加载DYLD到UUID list中,我们来看下这个方法的实现:

// <rdar://problem/10583252> Add dyld to uuidArray to enable symbolication of stackshots
// 将dyld添加到uuidArray以启用符号堆叠
static void addDyldImageToUUIDList()
{
	const struct macho_header* mh = (macho_header*)&__dso_handle;
	const uint32_t cmd_count = mh->ncmds;
	const struct load_command* const cmds = (struct load_command*)((char*)mh + sizeof(macho_header));
	const struct load_command* cmd = cmds;
	for (uint32_t i = 0; i < cmd_count; ++i) {
		switch (cmd->cmd) {
			case LC_UUID: {
				uuid_command* uc = (uuid_command*)cmd;
				// 新建一个dyld_uuid_info
				dyld_uuid_info info;
				// 给新建的info imageLoadAddress 字段赋值
				info.imageLoadAddress = (mach_header*)mh;
				// 复制uc->uuid的16个字节给info.imageUUID
				memcpy(info.imageUUID, uc->uuid, 16);
				// 利用组装好的info给dyld的gProcessInfo的uuidArray和uuidArrayCount赋值
				addNonSharedCacheImageUUID(info);
				return;
			}
		}
		cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
	}
}

从代码中我们可以看出,这个方法是遍历了Mach-O中的load_command并将cmd->cmd值为LC_UUID的添加到dyld::gProcessInfo->uuidArray中并更新个数。

我们可以通过addNonSharedCacheImageUUID的实现进一步确认:

// 将info中的uuidArray添加到dyld::gProcessInfo中
void addNonSharedCacheImageUUID(const dyld_uuid_info& info)
{
	// set uuidArray to NULL to denote it is in-use
	// 将uuidArray设置为NULL 表示这个字段正在使用中
	dyld::gProcessInfo->uuidArray = NULL;
	
	// append all new images
	// 追加外部传入的info到sImageUUIDs中
	sImageUUIDs.push_back(info);
	// 重新设置追加后uuidArrayCount
	dyld::gProcessInfo->uuidArrayCount = sImageUUIDs.size();
	
	// set uuidArray back to base address of vector (other process can now read)
	// 更新追加后的uuidArray
	dyld::gProcessInfo->uuidArray = &sImageUUIDs[0];
}
instantiateFromLoadedImage

从方法名中我们就可以看到这个方法是实例化一个ImageLoader,下面我们来详细了解下这个方法:

// mh 即 Mach-O文件的header
// slide 表示偏移量
// path 表示可执行文件地址
static ImageLoader* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
	// try mach-o loader
	// 检查mach-o的subtype是否是当前cpu可以支持
	if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
		// 根据传入的参数实例化一个ImageLoaderMachO类型的ImageLoader
		ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
		// 将主程序添加到全局主列表sAllImages中, 
		// 最后调用addMappedRange()申请内存, 更新主程序映像映射的内存区
		addImage(image);
		return image;
	}
	
	throw "main executable not a known format";
}

实例化一个ImageLoaderMachO后,我们将第一步获取到的一些变量设置给我们刚创建的ImageLoader:

gLinkContext.mainExecutable = sMainExecutable;
		gLinkContext.processIsRestricted = sProcessIsRestricted;
		gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
		gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

3 加载共享缓存

何为共享缓存,比如我们都知道iOS开发中会依赖系统的UIKit以及Foundation库,那么iOS系统中安装很多应用每个应用都要有自己独立加载UIKit吗?当然不是,所有的App会共用一份UIKit库,而这份UIKit库就存放在共享缓存中。

这一步我们重点关注:checkSharedRegionDisable,mapSharedCache,checkVersionedPaths这几个方法:

checkSharedRegionDisable
static void checkSharedRegionDisable()
{
	// iPhoneOS cannot run without shared region
}

这个方法中包含了一些Mac OS的判断不过在方法的最后,系统的注释: iOS如果没有共享库将无法运行。所以这个方法我们也不需要多做解读

mapSharedCache
static void mapSharedCache() {
	// 快速检查缓存是否已经被加载到共享缓存中了  如果没有返回-1
	if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) {
		if ( (header->mappingOffset >= 0x48) && (header->slideInfoSize != 0) ) {
			// solve for slide by comparing loaded address to address of first region
			// 通过比较加载的地址和第一个区域的地址来解决偏移问题
			const uint8_t* loadedAddress = (uint8_t*)sSharedCache;
			const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)(loadedAddress+header->mappingOffset);
			const uint8_t* preferedLoadAddress = (uint8_t*)(long)(mappings[0].address);
			//加载的地址 - 第一个区域的地址
			// 更新偏移量
			sSharedCacheSlide = loadedAddress - preferedLoadAddress;
			dyld::gProcessInfo->sharedCacheSlide = sSharedCacheSlide;
		}
		// if cache has a uuid, copy it
		// 更新UUID
		if ( header->mappingOffset >= 0x68 ) {
			memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);
		}
	} else {
		if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) {
			// 安全模式下
			::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME);
			// 设置sharedRegionMode = kDontUseSharedRegion
			gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
			return;
		} else {
			// map in shared cache to shared region
			int fd = openSharedCacheFile();
			if ( fd != -1 ) {
				uint8_t firstPages[8192];
				if ( ::read(fd, firstPages, 8192) == 8192 ) {
					dyld_cache_header* header = (dyld_cache_header*)firstPages;
					for (const dyld_cache_mapping_info* p = fileMappingsStart; p < fileMappingsEnd; ++p, ++i) {
						mappings[i].sfm_address		= p->address;
						mappings[i].sfm_size		= p->size;
						mappings[i].sfm_file_offset	= p->fileOffset;
						mappings[i].sfm_max_prot	= p->maxProt;
						mappings[i].sfm_init_prot	= p->initProt;
						// rdar://problem/5694507 old update_dyld_shared_cache tool could make a cache file
						// that is not page aligned, but otherwise ok.
						if ( p->fileOffset+p->size > (uint64_t)(stat_buf.st_size+4095 & (-4096)) ) {
							dyld::log("dyld: shared cached file is corrupt: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir);
							goodCache = false;
						}
						if ( (mappings[i].sfm_init_prot & (VM_PROT_READ|VM_PROT_WRITE)) == (VM_PROT_READ|VM_PROT_WRITE) ) {
							readWriteMappingIndex = i;
						}
						if ( mappings[i].sfm_init_prot == VM_PROT_READ ) {
							readOnlyMappingIndex = i;
						}
						if ( gLinkContext.verboseMapping ) {
							dyld::log("dyld: calling _shared_region_map_and_slide_np() with regions:\n");
							for (int i=0; i < mappingCount; ++i) {
								dyld::log("   address=0x%08llX, size=0x%08llX, fileOffset=0x%08llX\n", mappings[i].sfm_address, mappings[i].sfm_size, mappings[i].sfm_file_offset);
							}
						}
						if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, codeSignatureMappingIndex, cacheSlide, slideInfo, slideInfoSize) == 0) {
							// successfully mapped cache into shared region
							sSharedCache = (dyld_cache_header*)mappings[0].sfm_address;
							sSharedCacheSlide = cacheSlide;
							dyld::gProcessInfo->sharedCacheSlide = cacheSlide;
							//dyld::log("sSharedCache=%p sSharedCacheSlide=0x%08lX\n", sSharedCache, sSharedCacheSlide);
							// if cache has a uuid, copy it
							if ( header->mappingOffset >= 0x68 ) {
								memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);
							}
						}
					}
				}
			}

		}

	}
}

这一步先通过mapSharedCache()方法来映射共享缓存, 该函数先通过_shared_region_check_np()来检查缓存是否已经映射到了共享区域了, 如果已经映射了, 就更新缓存的slide与UUID, 然后返回;
如果有没有映射 判断系统是否处于安全启动模式(safe-boot mode)下,如果是就删除缓存文件并返回, 如果非安全启动模式, 接下来调用openSharedCacheFile()打开缓存文件, 该函数在sSharedCacheDir路径下, 打开与系统当前cpu架构匹配的缓存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h, 接着读取缓存文件的前8192字节, 解析缓存头dyld_cache_header的信息, 将解析好的缓存信息存入mappings变量, 最后调用_shared_region_map_and_slide_np()完成真正的映射工作。

checkVersionedPaths
static void checkVersionedPaths()
{
	// search DYLD_VERSIONED_LIBRARY_PATH directories for dylibs and check if they are newer
	// 读取DYLD_VERSIONED_LIBRARY_PATH环境变量 
	if ( sEnv.DYLD_VERSIONED_LIBRARY_PATH != NULL ) {
		for(const char* const* lp = sEnv.DYLD_VERSIONED_LIBRARY_PATH; *lp != NULL; ++lp) {
			// 判断是否需要覆盖当前目录下的库
			checkDylibOverridesInDir(*lp);
		}
	}
	// 读取DYLD_VERSIONED_FRAMEWORK_PATH环境变量
	// search DYLD_VERSIONED_FRAMEWORK_PATH directories for dylibs and check if they are newer
	if ( sEnv.DYLD_VERSIONED_FRAMEWORK_PATH != NULL ) {
		for(const char* const* fp = sEnv.DYLD_VERSIONED_FRAMEWORK_PATH; *fp != NULL; ++fp) {
			// 判断是否需要覆盖当前目录下的库
			checkFrameworkOverridesInDir(*fp);
		}
	}
}

4、加载插入的动态库

		// 遍历 `DYLD_INSERT_LIBRARIES` 环境变量, 调用`loadInsertedDylib`方法加载所有要插入的库,
		// 这些库都被加入到`sAllImages`数组中
		if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
			for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
			 // 这里传入的是每个lib的path
				loadInsertedDylib(*lib);
		}

上面的这段代码主要是:遍历sEnv.DYLD_INSERT_LIBRARIES所有要拆入的库(地址连续所以使用++获取地址),然后调用了loadInsertedDylib方法进行加载插入的库。

下面我们来详细看下这个方法:

static void loadInsertedDylib(const char* path)
{
	// 创建一个imageloader
	ImageLoader* image = NULL;
	try {
		LoadContext context;
		context.useSearchPaths		= false;
		context.useFallbackPaths	= false;
		context.useLdLibraryPath	= false;
		context.implicitRPath		= false;
		context.matchByInstallName	= false;
		context.dontLoad			= false;
		context.mustBeBundle		= false;
		context.mustBeDylib			= true;
		context.canBePIE			= false;
		context.origin				= NULL;	// can't use @loader_path with DYLD_INSERT_LIBRARIES
		context.rpath				= NULL;
		// 根据外部传入的path和新建的context构造一个ImageLoader
		image = load(path, context);
	}
}

load方法会先调用loadPhase0方法方式从文件加载,而loadPhase0又会调用loadPhase1loadPhase2去加载,实际上调用层次没加一层都是在对应load方法的path参数后拼接了一层,是不断的完善path路径的过程:

加载拆入的库后,还需要更新sInsertedDylibCount:

		sInsertedDylibCount = sAllImages.size()-1;

这里的-1操作实际上是排除主程序之外

5、链接主程序

// 开始链接主程序, 此时主程序已经被加载到gLinkContext.mainExecutable中,
		// 调用 link 链接主程序。内核调用的是ImageLoader::link 函数。
		gLinkContext.linkingMainExecutable = true;
		// link方法
		link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
		// 设置永不递归卸载
		sMainExecutable->setNeverUnloadRecursive();
		// mach-o header中的MH_FORCE_FLAT
		if ( sMainExecutable->forceFlat() ) {
			gLinkContext.bindFlat = true;
			gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
		}

这一步就是将加载进来的二进制变为可用状态的过程:rebase => binding

rebase就是针对 “mach-o在加载到内存中不是固定的首地址” 这一现象做数据修正的过程。
binding就是将这个二进制调用的外部符号进行绑定的过程。
lazyBinding就是在加载动态库的时候不会立即binding, 当时当第一次调用这个方法的时候再实施binding。

例如我们objc代码中需要使用到NSObject, 即符号_OBJC_CLASS_$_NSObject,但是这个符号又不在我们的二进制中,在系统库 Foundation.framework中,因此就需要binding这个操作将对应关系绑定到一起。

Link

这一步我们主要是看link方法(简化版):

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)
{
   // 递归加载库
	this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);
	context.notifyBatch(dyld_image_state_dependents_mapped);
  // 递归rebase
 	this->recursiveRebase(context);
	context.notifyBatch(dyld_image_state_rebased);
  // 递归bind
 	this->recursiveBind(context, forceLazysBound, neverUnload);
   
	if ( !context.linkingMainExecutable )
	   // weakBind 
		this->weakBind(context);

	context.notifyBatch(dyld_image_state_bound);

	std::vector<DOFInfo> dofs;
	// 递归获取DOFSection
	this->recursiveGetDOFSections(context, dofs);
	context.registerDOFs(dofs);
}

经过link操作后主程序达到了一个可用的状态。

6、链接插入的动态库

在链接主程序后链接插入的动态库,因此所有插入的动态库都会在系统使用的动态库后面。

与链接主程序相同,拆入的动态库也是通过调用link方法进行链接:

link
// sInsertedDylibCount 插入动态库的个数
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));
				// 
				image->setNeverUnloadRecursive();
			}

sInsertedDylibCount表示前期通过调用addImage方法插入到sAllImages的动态库的个数,遍历每一个拆入的动态库注意:这里sAllImages的下标是从1开始的,因为第0个位置存放的是主程序。

registerInterposing
void ImageLoaderMachO::registerInterposing()
{
	// mach-o files advertise interposing by having a __DATA __interpose section
	// 这个方法是要操作 Mach-O文件的__DATA__区
	const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
	const struct load_command* cmd = cmds;
	for (uint32_t i = 0; i < cmd_count; ++i) {
		switch (cmd->cmd) {
			// 找到load_commands中的LC_SEGMENT_COMMAND
			case LC_SEGMENT_COMMAND:
				{

					for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
						// 查找__DATA段的__interpose节区
						if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) {

							for (size_t i=0; i < count; ++i) {

								// 找到需要应用插入操作(也可以叫作符号地址替换)的数据
								if ( this->containsAddress((void*)tuple.replacement) ) {

									// 将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中, 供以后具体符号替换时查询
									ImageLoader::fgInterposingTuples.push_back(tuple);
								}
							}
						}
					}
				}
				break;
		}
		cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
	}
}

registerInterposing()查找__DATA段的__interpose节区, 找到需要应用插入操作(也可以叫作符号地址替换)的数据, 然后做一些检查后, 将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中, 供以后具体符号替换时查询(applyInterposing中会用到)。

applyInterposing

applyInterposing() -> recursiveApplyInterposing() -> doInterpose() -> eachBind() -> interposeAt()

下面看下interposeAt方法:

uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, 
												uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver)
{
	if ( type == BIND_TYPE_POINTER ) {
		uintptr_t* fixupLocation = (uintptr_t*)addr;
		uintptr_t curValue = *fixupLocation;
		uintptr_t newValue = interposedAddress(context, curValue, this);
		if ( newValue != curValue)
			*fixupLocation = newValue;
	}
	return 0;
}

这个方法的实现很简单就是对比了新值和旧值 如果不同就将对应地址的值改为新值。

7 执行弱符号绑定


void ImageLoader::weakBind(const LinkContext& context)
{
	ImageLoader* imagesNeedingCoalescing[fgImagesRequiringCoalescing];
	// 将sAllImages中所有含有弱符号的映像合并成一个列表
	int count = context.getCoalescedImages(imagesNeedingCoalescing);
	// don't need to do any coalescing if only one image has overrides, or all have already been done
	// 如果进行weakbind的镜像个数>0
	if ( (countOfImagesWithWeakDefinitionsNotInSharedCache > 0) && (countNotYetWeakBound > 0) ) {
		// make symbol iterators for each
		ImageLoader::CoalIterator iterators[count];
		ImageLoader::CoalIterator* sortedIts[count];
		for(int i=0; i < count; ++i) {
			// 对镜像进行排序
			imagesNeedingCoalescing[i]->initializeCoalIterator(iterators[i], i);
			sortedIts[i] = &iterators[i];
		}

		int doneCount = 0;
		while ( doneCount != count ) {
			// 收集需要进行绑定的弱符号
			// 该函数读取映像动态链接信息的weak_bind_off与weak_bind_size来确定弱符号的数据偏移与大小,然后挨个计算它们的地址信息
			if ( sortedIts[0]->image->incrementCoalIterator(*sortedIts[0]) )
				++doneCount;
			// process all matching symbols just before incrementing the lowest one that matches
			if ( sortedIts[0]->symbolMatches && !sortedIts[0]->done ) {

				ImageLoader* targetImage = NULL;
				for(int i=0; i < count; ++i) {
					if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) {
						if ( iterators[i].weakSymbol ) {
							if ( targetAddr == 0 ) {
								// 按照映像的加载顺序在导出表中查找符号的地址
								targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context);
								if ( targetAddr != 0 )
									targetImage = iterators[i].image;
							}
						}
						else {
							targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context);
							if ( targetAddr != 0 ) {
								targetImage = iterators[i].image;
								// strong implementation found, stop searching
								break;
							}
						}
					}
				}

				// tell each to bind to this symbol (unless already bound)
				if ( targetAddr != 0 ) {
					for(int i=0; i < count; ++i) {
						if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) {
							// 绑定操作
							// 内部执行绑定的是bindLocation()
							iterators[i].image->updateUsesCoalIterator(iterators[i], targetAddr, targetImage, context);
					}
				}
				
			}
		}
}

8、执行初始化方法

执行初始化方法, 其中+load 和constructor方法就是在这里执行。

// initializeMainExecutable 执行初始化方法,其中 +load 和 constructor 方法就是在这里执行。
// initializeMainExecutable 内部先调用了动态库的初始化方法,后调用主程序的初始化方法。
// 初始化主程序
void initializeMainExecutable()
{
	// record that we've reached this step
	gLinkContext.startedInitializingMainExecutable = true;

	// run initialzers for any inserted dylibs
	// 给被插入的所有的 dylibs 进行初始化 -- 调用 initialzers
	ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];

	initializerTimes[0].count = 0;

	const size_t rootCount = sImageRoots.size();
	if ( rootCount > 1 ) {
		// 这里是下标1开始 排除掉了主程序的初始化
		for(size_t i=1; i < rootCount; ++i) {
			// 执行镜像的初始化方法
			// 从 sImageRoots 中的第一个变量是 MainExcutable image, 
			// 因此这里初始化的时候需要跳过第一个数据, 对其他后面插入的dylib进行调用
			// ImageLoader::runInitializers进行初始化
			sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
		}
	}
	
	// run initializers for main executable and everything it brings up
	// 调用主程序的初始化方法
	// 单独对 main executable调用ImageLoader::runInitializers进行初始化
	sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
	
	// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
	if ( gLibSystemHelpers != NULL ) 
		(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

	// dump info if requested
	if ( sEnv.DYLD_PRINT_STATISTICS )
		ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}

这个方法主要是执行了ImageLoader的runInitializers方法,下面我看下这个方法的实现:

runInitializers
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
  // 初始化当前 imageLoader 中的 image镜像的实际调用方法 ImageLoader::processInitializers
	processInitializers(context, thisThread, timingInfo, up);
	context.notifyBatch(dyld_image_state_initialized);
}
processInitializers
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
									 InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    // 处理当前image依赖 dylib动态库, 调用 recursiveInitialization 方法!!!
	for (uintptr_t i=0; i < images.count; ++i) {
		images.images[i]->recursiveInitialization(context, thisThread, timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}
recursiveInitialization
// 递归调用 image 进行初始化, 先调用image依赖的image进行初始化. 直到自己
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
// 当前ImageLoader依赖的Image还没有初始化完, 进入if中, 如果执行完成, 直接返回
	if ( fState < dyld_image_state_dependents_initialized-1 ) {
		uint8_t oldState = fState;
		// break cycles
		// break cycles -> 这是设置当前imageLoader的state接近依赖初始化.
		fState = dyld_image_state_dependents_initialized-1;
		try {
			// initialize lower level libraries first
			// 首先初始化image底层的依赖库
			for(unsigned int i=0; i < libraryCount(); ++i) {
				ImageLoader* dependentImage = libImage(i);
				if ( dependentImage != NULL ) {
					// don't try to initialize stuff "above" me yet
					if ( libIsUpward(i) ) {
						uninitUps.images[uninitUps.count] = dependentImage;
						uninitUps.count++;
					}
					else if ( dependentImage->fDepth >= fDepth ) {
					   // 递归调用
						dependentImage->recursiveInitialization(context, this_thread, timingInfo, uninitUps);
					}
                }
			}
			
			// 到这里image底层的依赖库都递归调用, 初始化完成.
			
			// let objc know we are about to initialize this image
			uint64_t t1 = mach_absolute_time();
			fState = dyld_image_state_dependents_initialized;
			oldState = fState;
			// 通知 runtime, 当前状态发生变化 -- image的依赖已经完全加载. 
			// 注意这里可能在runtime中注册了状态监听, 注册了callback函数, 当状态发送变化时,
			// 会触发回调函数.
			context.notifySingle(dyld_image_state_dependents_initialized, this);
			
			// initialize this image
			// 初始化当前image, `ImageLoaderMachO::doInitialization`方法内部会调用image  
			// 的"Initializer", 这是一个函数指针, 实际是image的初始化方法. 例如 
			// `libSystem.dylib`, 它的初始化方法就比较特殊, 我们可以参考libSystem的init.c源
			// 码, 内部的`libsystem_initializer`函数就是初始化真正调用的函数
			
			 // _init_objc方法!!!!
			bool hasInitializers = this->doInitialization(context);

			// let anyone know we finished initializing this image
			fState = dyld_image_state_initialized;
			oldState = fState;
			//通知runtime, 档期那状态发送变化 -- image自己已经完成初始化!!!!
			context.notifySingle(dyld_image_state_initialized, this);
			
			if ( hasInitializers ) {
				uint64_t t2 = mach_absolute_time();
				timingInfo.images[timingInfo.count].image = this;
				timingInfo.images[timingInfo.count].initTime = (t2-t1);
				timingInfo.count++;
			}

		}
		catch (const char* msg) {
			// this image is not initialized
			fState = oldState;
			recursiveSpinUnLock();
			throw;
		}
	}
}
doInitialization
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
	CRSetCrashLogMessage2(this->getPath());

	// mach-o has -init and static initializers
	// 调用Mach-O的 init 和  static initializers方法
	doImageInit(context);
	doModInitFunctions(context);
	
	CRSetCrashLogMessage2(NULL);
	
	return (fHasDashInit || fHasInitializers);
}
doImageInit

获取mach-o的init方法的地址并调用

void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
	if ( fHasDashInit ) {
		// mach-o文件中指令的个数
		const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
		const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
		const struct load_command* cmd = cmds;
		// 遍历指令
		for (uint32_t i = 0; i < cmd_count; ++i) {
			switch (cmd->cmd) {
				case LC_ROUTINES_COMMAND:
					// 获取macho_routines_command的init_address
					Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
					// <rdar://problem/8543820&9228031> verify initializers are in image
					if ( ! this->containsAddress((void*)func) ) {
						dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
					}
					if ( context.verboseInit )
						dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
					// 执行-init方法
					func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
					break;
			}
			// 计算下一个指令((char*)cmd)+cmd->cmdsize
			cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
		}
	}
}
doModInitFunctions

获取mach-o的static initializer的地址并调用

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
	if ( fHasInitializers ) {
		// mach-o文件中指令的个数
		const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
		const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
		const struct load_command* cmd = cmds;
		// 遍历所有的指令
		for (uint32_t i = 0; i < cmd_count; ++i) {
			 // 如果指令是Mach-o中的LC_SEGMENT_COMMAND
			if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
				const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
				const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
				const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
				// 从sectionsStart到sectionsEnd遍历所有的macho_section
				for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
					const uint8_t type = sect->flags & SECTION_TYPE;
					//
					if ( type == S_MOD_INIT_FUNC_POINTERS ) {
						Initializer* inits = (Initializer*)(sect->addr + fSlide);
						const size_t count = sect->size / sizeof(uintptr_t);
						for (size_t i=0; i < count; ++i) {
							// 获取到Initializer方法
							Initializer func = inits[i];
							// <rdar://problem/8543820&9228031> verify initializers are in image
							if ( ! this->containsAddress((void*)func) ) {
								dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
							}
							if ( context.verboseInit )
								dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
							// 执行initializer方法
							func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
						}
					}
				}
			}
			// 根据指令的地址+指令大小获取到下一个指令
			cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
		}
	}
}

9、查找APP入口点并返回

这一步也是最后一步主要功能为:
查找到main函数的地址,并返回。

// 9 查找APP入口点并返回
		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;
		}
getThreadPC
// 查找主程序的LC_MAIN加载命令获取程序的入口点,
void* ImageLoaderMachO::getThreadPC() const
{
	const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
	const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
	const struct load_command* cmd = cmds;
	for (uint32_t i = 0; i < cmd_count; ++i) {
		if ( cmd->cmd == LC_MAIN ) {
			entry_point_command* mainCmd = (entry_point_command*)cmd;
			void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
			// <rdar://problem/8543820&9228031> verify entry point is in image
			if ( this->containsAddress(entry) )
				return entry;
			else
				throw "LC_MAIN entryoff is out of range";
		}
		cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
	}
	return NULL;
}

该方法遍历了Load Commands 找到LC_MAIN命令的入口点地址返回,这个地址就是main函数的地址

getMain

如果getThreadPC没有找到LC_MAIN的入口地址

// 在LC_UNIXTHREAD加载命令中去找, 找到后就跳到入口点指定的地址
void* ImageLoaderMachO::getMain() const
{
	const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
	const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
	const struct load_command* cmd = cmds;
	for (uint32_t i = 0; i < cmd_count; ++i) {
		switch (cmd->cmd) {
			case LC_UNIXTHREAD:
			{
			#if __i386__
				const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16);
				void* entry = (void*)(registers->eip + fSlide);
			#elif __x86_64__
				const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16);
				void* entry = (void*)(registers->rip + fSlide);
			#elif __arm__
				const arm_thread_state_t* registers = (arm_thread_state_t*)(((char*)cmd) + 16);
				void* entry = (void*)(registers->__pc + fSlide);
			#elif __arm64__
				const arm_thread_state64_t* registers = (arm_thread_state64_t*)(((char*)cmd) + 16);
				void* entry = (void*)(registers->__pc + fSlide);
			#else
				#warning need processor specific code
			#endif
				// <rdar://problem/8543820&9228031> verify entry point is in image
				if ( this->containsAddress(entry) ) {
					return entry;
				}
			}
			break;
		}
		cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
	}
	throw "no valid entry point";
}

objc_init

从上面的步骤描述中我们知道实际上objc_init是在ImageLoaderMachO::doModInitFunctions时就被调用了,我们先来看代码

doModInitFunctions

// doModInitFunctions方法部分代码
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
	for (size_t i=0; i < count; ++i) {
		// 获取到Initializer方法
		Initializer func = inits[i];
		// <rdar://problem/8543820&9228031> verify initializers are in image
		if ( ! this->containsAddress((void*)func) ) {
			dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
		}
		if ( context.verboseInit )
			dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
		// 执行initializer方法
		func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
	}
}

doModInitFunctions方法实际上调用了Initializer方法,对对一个动态库进行初始化是通过_libdispatch_init方法进行的 我们来看下这个方法:

_libdispatch_init

void
libdispatch_init(void)
{

	_dispatch_hw_config_init();
	_dispatch_time_init();
	_dispatch_vtable_init();
	_os_object_init();
	_voucher_init();
	_dispatch_introspection_init();
}

上面的方法我们看到,其中调用了_os_object_init方法,然后我们在看下这个方法的实现:

_os_object_init

void
_os_object_init(void)
{
   // 省略....
	_objc_init();
	// 省略....
}

从上面的代码中我们看到,在_os_object_init方法中调用了我们熟知的_objc_init方法,至此运行时就开始。

_objc_init

void _objc_init(void)
{
  // 省略代码...
	/*
	 仅供objc运行时使用,注册在映射、取消映射和初始化objc映像调用的处理程序。dyld将使用包含objc-image-info回调给`mapped`.
	 这些dylibs将自动引用计数,因此objc将不再需要调用dlopen()防止未加载。
	 在调用_dyld_objc_notify_register()期间,dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。
	 在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数
	 
	 在调用任何images +load方法时候
	 */
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

这个方法中,主要是注册了map_imagesload_images方法.

map_images

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

}

load_images

void
load_images(const char *path __unused, const struct mach_header *mh)
{

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        //加载 class+load 和category+load方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    //执行 class+load 和category+load方法
    call_load_methods();
}

总结

总结上述的整个过程便是在主工程的main函数开始执行之前系统做的所有操作,其中有一些步骤和环境我也不是太清楚,因此需要进一步的完善和梳理。

参考文章

libdispatch源码
dyld与ObjC
iOS App启动时发生了什么?
dyld加载应用启动原理详解

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页