dyld分析

423 阅读10分钟

前言:

动态库和静态库

  我们都知道,一段程序的运行,都会依赖各种各样的库,那么什么是程序依赖的库呢?简而言之,库就是一个可执行的二进制文件,作为程序运行的支撑。通俗的说,当我们需要造一辆汽车的时候,库就是轮子。我们常见windows系列的库就是.dll文件,linux系列库就是.so文件。

库有动态库、静态库之分,那么iOS使用的库有哪些呢?

  • 静态库:.a、.lib
  • 动态库:framework

动态库和静态库加载的区别:

  • 静态库在链接阶段,会将汇编生成的目标与引用的库一起链接打包到可执行的文件中。
  • 动态库并不会链接到目标代码中,在运行时才会被载入。

动态库的优势:

  • 减少打包之后App的大小
  • 共享内容
  • 动态更新

常见的动态库:

  • UIKit
  • libdispatch
  • libobjc.dyld

程序的编译过程

  程序的编译过程其实就是将源文件转化成可执行文件的过程:

App加载分析

  当我们运行一个iOS App的时候,它的加载过程是什么呢?当我们点击桌面应用图标的时候,App就会进入启动流程,系统先会加载libSystemruntimedyld注册回调函数,然后加载新的镜像文件(image),执行map_images、load_images,最后调用main函数,这样App就会启动起来。

我们创建一个新程序,在ViewController.m文件中实现如下的方法:

+ (void)load{
    NSLog(@"%s",__func__);
}

然后在main函数的入口处打一个断点,当程序停在断点的时候,我们会发现控制台已经输出如下log:

+[ViewController load]

说明,当程序进入main函数之前,已经执行了类的load方法。 查看旁边的调用顺序:

我们可以看到,在main函数之前,会调用一个start方法,该方法即

libdyld.dylib`start

那么,dyld是什么呢?

dyld

dyld简介

dyld: the dynamic link editor,苹果系统的动态链接器。它的作用是在需要使用动态库的时候将其加载到内存中。那么dyld是怎样将动态库加载到内存中的?dyld的加载流程又是什么样的呢?

dyld加载流程

在上面的代码中,我们可以看出,在main函数之前,会先调用dyld_start函数,通过查看dyld相关源码,我们来逐步分析其加载流程:

汇编部分

首先进行全局搜索dyld_start,进入dyldStartup.s文件,找到arm64相关部分,如图:

如图所示,在进行一系列汇编操作之后,程序会跳转到:

__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm

c++部分

也就是调用一个c++方法:

dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)

全局搜索“start(”,即可找到该方法对应的实现部分:

uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
				intptr_t slide, const struct macho_header* dyldsMachHeader,
				uintptr_t* startGlue)
{
	// 忽略其它条件
	return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

该方法最终会调用dyld::_main方法:
由于该方法比较长,我们就不一一贴出代码,择重要的展示:

// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)
{
    // 设置上下文
    setContext(mainExecutableMH, argc, argv, envp, apple);
    
    // 检查环境变量的相关操作
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);
    
    // 加载共享缓存
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    mapSharedCache();
    
    // 将dyld添加到UUID列表
    addDyldImageToUUIDList();
    
    // 为主要可执行文件实例化ImageLoader
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

    // 加载任何插入的库
    if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
		for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
				loadInsertedDylib(*lib);
	}
	
	// 链接库
	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(gLinkContext);
		}
	}
	
	// 运行所有的初始化
	initializeMainExecutable(); 

    // 通知任何监听的进程此进程即将进入main()函数
    notifyMonitoringDyldMain();
}

具体   通过该方法的注释,我们可以知道dyld::_maindyld方法的入口,内核设置了寄存器,并且跳转到__dyld_start,然后调用了该方法。

准备工作:
  • 设置上下文信息
  • 检查环境变量的配置

// 此处只展示较为重要的代码
static void checkEnvironmentVariables(const char* envp[])
{
	if ( !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsPrint )
		return;
	const char** p;
	for(p = envp; *p != NULL; p++) {
		......
        processDyldEnvironmentVariable(key, value, NULL);
	}
	......
}
加载共享缓存
// 此处只展示较为重要的代码
static void mapSharedCache()
{
	dyld3::SharedCacheOptions opts;
	opts.cacheDirOverride	= sSharedCacheOverrideDir;
	opts.forcePrivate		= (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);
	opts.verbose			= gLinkContext.verboseMapping;
	loadDyldCache(opts, &sSharedCacheLoadInfo);
	....
}

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

if ( options.forcePrivate ) {
        // 仅加载当前进程的缓存
        return mapCachePrivate(options, results);
    }
    else {
        // 缓存已经被加载到共享缓存文件了
        bool hasError = false;
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // 第一次加载缓存
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
    ......
}
加载镜像文件:
  1. 为可执行文件实例化imageLoader
  2. 为主可执行文件创建image
  3. 加载任何插入动态库
  4. 链接动态库

为可执行文件实例化imageLoader

// 此处只展示较为重要的代码
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath)

static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
	// try mach-o loader
	if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
	    // 为主可执行文件创建image
		ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
		
		// 添加镜像
		addImage(image);
		return (ImageLoaderMachO*)image;
	}
}

为主可执行文件创建image:

// 此处只展示较为重要的代码
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
	bool compressed;
	unsigned int segCount;
	unsigned int libCount;
	const linkedit_data_command* codeSigCmd;
	const encryption_info_command* encryptCmd;
	
	// 确定此mach-o文件是否具有经典或压缩的LINKEDIT以及其具有的段数
	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);
}

加载任何插入动态库:

// 此处只展示较为重要的代码
static void loadInsertedDylib(const char* path)
{
	ImageLoader* image = NULL;
	unsigned cacheIndex;
	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.enforceIOSMac		= true;
		context.origin				= NULL;	// can't use @loader_path with DYLD_INSERT_LIBRARIES
		context.rpath				= NULL;
		image = load(path, context, cacheIndex);
	}
	......
}

链接程序:

// 此处只展示较为重要的代码
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();
	}
		
	for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
		ImageLoader* image = sAllImages[i+1];
		image->registerInterposing(gLinkContext);
	}
}

// 递归加载所有依赖库
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
	(*context.setErrorStrings)(0, NULL, NULL, NULL);

	uint64_t t0 = mach_absolute_time();
	this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
	......
}

void ImageLoaderMegaDylib::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath)
{
	unsigned index = findImageIndex(context, loadPath);
	recursiveMarkLoaded(context, index);
}
运行所有的初始化:

一步一步递归,初始化任何插入的库。

// 此处只展示较为重要的代码
void initializeMainExecutable()
{
	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]);
		}
	}
	
	......
}
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);
	......
}


void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
									 InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
	uint32_t maxImageCount = context.imageCount()+2;
	ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
	ImageLoader::UninitedUpwards& ups = upsBuffer[0];
	ups.count = 0;

	for (uintptr_t i=0; i < images.count; ++i) {
		images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
	}
	
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}


// 在每一个image初始化完成之后,通知objc和anyone,当前image的初始化工作完成了
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);

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

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

那么当image加载完成之后,它是怎们告诉大家它加载完成了呢?答案是指针回调。加载完成之后,会调用下面的方法:

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

而这两个方法的实现如下:

// 此处只展示较为重要的代码
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    ......
	if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
	    ......
	    
		(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
	   
	   ......
	}
    ......
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// record functions to call
	sNotifyObjCMapped	= mapped;
	sNotifyObjCInit		= init;
	sNotifyObjCUnmapped = unmapped;

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

// 该函数的注释大概可以知道:
// _dyld_objc_notify_mapped是dyld已经加载完成image进行映射操作
// _dyld_objc_notify_init 是初始化image操作
// _dyld_objc_notify_unmapped 解除映射操作
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
	dyld::registerObjCNotifiers(mapped, init, unmapped);
}
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

追踪该函数的调用,我们发现当调用到_objc_init函数时,此处就注册了image加载完成的通知。那么什么时候会调用_objc_init方法呢?在此处打一个断点,运行程序,在lldb查看使用bt指令,就可以看到代码调用的顺序如下:

分析得到如下流程图:

总结:dyld加载顺序

    1. __dyly_start(汇编)
    1. uintptr_t start()
    1. uintptr_t _main()
    • 3.1 配置上下文
    • 3.2 处理环境变量
    • 3.3 加载共享缓存
    • 3.4 将dyld加入UUID列表
    • 3.5 加载所有image
      • 3.5.1 为可执行文件实例化imageLoader
      • 3.5.2 为主可执行文件创建image
      • 3.5.3 加载任何插入动态库
      • 3.5.4 链接动态库
    • 3.6 初始化所有程序
      • 3.6.1 遍历初始化image
      • 3.6.2 通知image初始化完成
      • 3.6.3 初始化libSystem
      • 3.6.4 初始化libdispatch
      • 3.6.5 初始化libobjc
    • 3.7 进入主程序的main函数

Tips: 共享缓存(dyld_shared_cache)

dyld加载时,为了优化程序启动,使用了共享缓存(dyld_shared_cache)。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。

update_dyld_shared_cache程序确保了dyld的共享缓存是最新的,它会扫描 /var/db/dyld/shared_region_roots/目录下paths路径文件,这些paths文件包含了需要加入到共享缓存的Mach-O文件路径列表,update_dyld_shared_cache()会挨个将这些Mach-O文件及其依赖的dylib都加共享缓存中去。

  共享缓存是以文件形式存放在/var/db/dyld/目录下的,生成共享缓存的update_dyld_shared_cache程序位于是/usr/bin/目录下。

Tips: 函数指针

定义:指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
用途:调用函数和做函数的参数。

eg:

void (*funcP)(int num);
void printFunc(int num) {
    printf("num:%d\n",num);
}
int main(int argc,char *argv[]) {
    funcP = printFunc;
    (*funcP)(100);
    funcP(100);
    return 0;
}

运行以上程序,控制台就会输出100;100。因为funcP是一个函数指针,当执行funcP = printFunc的时候,其实就是让funcP指向printFunc,所以调用(*funcP)(100)实际上就是调用printFunc(100)。而funcP(100)的结果和(*funcP)(100)是一样的,只是写法不同。

Tips: 回调函数

定义:通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 eg:

//函数功能:实现求和
int func_sum(int n,int b) {
    return a + b;
}

//这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int m, int n, int (*p)(int))
{
    return p(m, n);
}

int main(void)
{
    printf("the sum is %d\n", callback(1, 2, func_sum));       
    return 0;
}

此处输出控制台输出3,

Tips:指针函数

定义:返回值是指针的函数。 eg:

int * func_sum(int a, int, b)
{
    sum = a + b;
    int *p = &sum;
    return p;
}