APP加载流程探索之dyld

310 阅读6分钟

我们今天对app的加载流程进行探索一下,看看app在启动的过程中,具体都做了哪些事情。

首先我们来运行起一个项目,运行成功后,我们在Xcode上选择Product -> Show Build Folder in Finder,会跳到一个Build文件夹下,然后按照下图中的步骤找到一个跟应用程序同名的文件,显示包内容:

图片.png

打开包内容后,找到一个黑色的exec文件,它是一个可执行文件,这个文件是mach-o格式的。

图片.png

什么是mach-o格式?

mach-o是一种文件格式,比如可执行文件、目标文件(.o文件)、静态库、动态库等,这些都是mach-o格式的。

像我们平时写的.h和.m文件,在我们运行项目时,都是要经过以下几个过程,最终变成可执行文件:
1、预编译:这阶段一般是处理代码中以" # "开头的预编译命令,包括替换宏定义、根据"#import"命令把相应的文件插入到指定位置、删除注释等工作。
2、编译:这个阶段主要进行词法分析、语法分析、语义分析等,生成汇编代码文件。
3、汇编:将编译阶段生成的汇编代码文件,翻译成机器指令,生成.o文件。
4、链接:分为静态链接和动态链接。

对于步骤4,再详细说明一下:
我们写的.h和.m文件会生成一个.o文件,但是我们可能在A类中调用了B类中的变量或者函数,但是每个模块的.o文件都是单独生成的,它们不知道其它模块的.o文件里的内容,这时就需要链接器登场了,链接器把所有的.o文件进行解析和符号收集等,这就把所有的.o文件连在了一起,形成一个可执行文件,这个过程就是静态链接

说到这儿,再说一下静态库,静态库就是一堆.o文件的集合,静态链接阶段所链接的所有.o文件就包括静态库里面的.o文件,所以静态链接结束后,静态库就不存在了。

动态库是一个已经链接完全的镜像image,其中包含可执行文件dylibbundle这几种形式。

下面,我们在项目的main方法中加一个断点,通过image list命令来看下动态库列表:

图片.png

这些.dylib文件都是动态库。

dyld

我们的app在启动的时候,其实是从exec()方法开始的,在这个方法里,app对应的可执行文件被加载到内存,dyld也被加载到内存,之后dyld就开始了动态链接。

dyld是"the dynamic link editor"的缩写,它主要做哪些工作呢?
1、递归加载可执行文件中所有的动态库
2、rebase和binding
3、调起main函数

dyld的工作是在main函数执行之前,那我们通过断点查看一下。 在ViewController中添加一个+(void)load方法,运行,在load方法断住,通过bt命令查看栈信息:

图片.png

看到,是从_dyld_start方法开始的, 之后执行dyldbootstrap::start方法,这是在dyldbootstrap命名空间下的start方法,在dyld的源码中搜一下,start方法的最后是去执行_main方法:

图片.png

_main方法里最重要的一个方法是initializeMainExecutable(),它的注释是run all initializers,进行所有的初始化的操作。

// run all initializers
initializeMainExecutable();

查看initializeMainExecutable方法具体实现,先是初始化所有的动态库:

图片.png

然后初始化可执行文件和我们写的那些类:

图片.png

runInitializers方法里,其中红框内的这一行是关键:

图片.png

图片.png

processInitializers方法主要做两件事:
1、为初始化动态库做准备
2、初始化动态库

先看红框中的注释,意思是说,如果该动态库还依赖其它的动态库,那么被依赖的库也需要初始化一下,所以这是个递归的方法。

上面代码中的recursiveInitialization方法,就是具体执行初始化的。

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
	recursive_lock lock_info(this_thread);
	recursiveSpinLock(lock_info);

	if ( fState < dyld_image_state_dependents_initialized-1 ) {
		uint8_t oldState = fState;
		// break cycles
		fState = dyld_image_state_dependents_initialized-1;
		try {
			// initialize lower level libraries first
			for(unsigned int i=0; i < libraryCount(); ++i) {
				ImageLoader* dependentImage = libImage(i);
				if ( dependentImage != NULL ) {
					// don't try to initialize stuff "above" me yet
					if ( libIsUpward(i) ) {
						uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
						uninitUps.count++;
					}
					else if ( dependentImage->fDepth >= fDepth ) {
						dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
					}
                }
			}
			
			// record termination order
			if ( this->needsTermination() )
				context.terminationRecorder(this);

			// let objc know we are about to initialize this image
			uint64_t t1 = mach_absolute_time();
			fState = dyld_image_state_dependents_initialized;
			oldState = fState;
			context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
			
			// initialize this image
			bool hasInitializers = this->doInitialization(context);

			// let anyone know we finished initializing this image
			fState = dyld_image_state_initialized;
			oldState = fState;
			context.notifySingle(dyld_image_state_initialized, this, NULL);
			
			if ( hasInitializers ) {
				uint64_t t2 = mach_absolute_time();
				timingInfo.addTime(this->getShortName(), t2-t1);
			}
		}
		catch (const char* msg) {
			// this image is not initialized
			fState = oldState;
			recursiveSpinUnLock();
			throw;
		}
	}
	
	recursiveSpinUnLock();
}

我们看到里面有这句注释:
// let objc know we are about to initialize this image
翻译:让objc知道我们准备去初始化这个库

再往下看注释,可知doInitialization就是去初始化动态库:

// initialize this image
this->doInitialization(context)

图片.png

doImageInit和doModInitFunctions方法里面都有判断,如果libSystem库还没有被初始化的话,会报错“...that does not link with libSystem.dylib”,说明这个库是依赖libSystem库的。

继续,
// let anyone know we finished initializing this image
翻译:通知所有人我们已经完成了这个库的初始化

fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);

notifySingle就是去通知的方法:

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
        ...
        部分代码省略
        ...
        
	//dyld_image_state_dependents_initialized
	if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
		uint64_t t0 = mach_absolute_time();
		dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT,
								 (uint64_t)image->machHeader(), 0, 0);
		(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
		uint64_t t1 = mach_absolute_time();
		uint64_t t2 = mach_absolute_time();
		uint64_t timeInObjC = t1-t0;
		uint64_t emptyTime = (t2-t1)*100;
		if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
			timingInfo->addTime(image->getShortName(), timeInObjC);
		}
	}
        
        ...
        部分代码省略
        ...
}

我们看到上面方法传的第一个参数state是dyld_image_state_dependents_initialized。 在这个状态下,会调sNotifyObjCInit方法,而sNotifyObjCInit方法是在registerObjCNotifiers方法中被赋值的,继续往上找,到_dyld_objc_notify_register方法,_dyld_objc_notify_register是在objc源码中的_objc_init方法中被调用的,可见,库的初始化依赖objc。

图片.png

我们再来看下_objc_init方法是什么时候被调用的。给_objc_init加断点,bt一下:

图片.png

看到,_objc_init是在libdispatch库中调用的,而libdispatch_init方法是在libSystem.B.dylib中的libSystem_initializer中调用的。

所以,我们可知,加载动态库,要先初始化libSystem,然后初始化libdispatch,然后再初始化libobjc

我们再回头看_dyld_objc_notify_register有三个参数,第一个参数做为一个方法是在notifyBatchPartial方法里被赋值的,notifyBatchPartial在notifyBatch方法里调用的,而notifyBatch方法其实在上面的runInitializers方法里我们就已经展示过了,就在processInitializers方法下面一行:

图片.png

所以,map_image和load_image的执行时机,都是在动态库完成初始化的时候。至于这两个方法具体做了哪些工作,我们将在后面的类加载文章中来讲解。

第三个参数unmap_image会被赋值给sNotifyObjCUnmapped方法,这个方法会在removeImage方法中调用,即库被删除的时候。

总结一下:

1、dyld初始化动态库时,会走到recursiveInitialization方法,这个方法会调用doInitialization方法去初始化具体的库,等库初始化完成,就去通知和记录。同时,初始化库的过程是个递归的过程,在初始化某个库的时候,如果发现其还依赖其它的库,那么依赖的库也要去初始化。

2、最先被初始化的是libSystem库,然后是libdispatch库,然后再初始化libobjc库。