Load方法之前做了什么?dyld应用程序加载流程分析

564 阅读7分钟

引入

我们都知道app运行起来load方法调用在main函数之前。

  • load方法之前做了些什么事情
  • 苹果这些动态库是怎么加载到内存中的
  • 动态库之前是怎么联系起来的

带着这些问题我们引入一个非常牛B的东西dyld链接器。

dyld简介

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。而且它是开源的,任何人可以通过苹果官网下载它的源码来阅读理解它的运作方式,了解系统加载动态库的细节。

准备工作

编译和库

IOS程序员一般是写上层代码,最熟悉的就是.h.m文件,在编译的时候.h.m文件具体经过哪些流程最终生成了可执行文件

  • 源文件:.h.m.cpp等文件
  • 预处理:在预处理的时候,注释被删除、条件编译被处理、头文件展开、宏被替换
  • 编译:进行词法分析语法分析以及中间层IR文件,最后生成汇编文件.s文件
  • 汇编:将.s文件转换成机器语言生成.o文件
  • 链接:将所有的.o文件以及链接的第三方库,生成一个macho类型的可执行文件

编译过程

dyld链接器在哪个过程工作请看下图

1.png

dyld引入和流程分析

首先我们应该明确dyld链接是在main函数之前。我们创建一个新的app工程在main函数下断点打印堆栈看下。

1.png

堆栈显示了libdyld.dylibstart -> main`函数,但中间过程我们不清楚。找一个在main函数之前的方法load方法在下断点打印堆栈。

2.png

  • load堆栈流程 1._dyld_start -> 2.dyldbootstrap::start -> 3.dyld::_main -> 4.dyld::initializeMainExecutable() -> 5.runInitializers -> 6.processInitializers -> 7.recursiveInitialization -> 8.dyld::notifySingle -> 9.load_images -> 10.[ViewController load]

  • 流程比较多我们一一来分析,总体思路就是抓住dyld是干什么的?链接器,分析dyld源码还是比较困然的,希望给自己点耐心。既然开始分析dyld了下面就一起来看一下源码dyld,dyld源码在准备工作中有链接,请先下载好。

_dyld_start

  • 打开dyld源码定位_dyld_start方法

4.png

  • bl 指令是调用函数dyldbootstrap::start
  • dyldbootstrap::start C++语法我们先搜索命名空间dyldbootstrap 里面的start方法

dyldbootstrap::start

5.png

  • 之前都是对dyld的引导处理代码以及处理c++代码
  • 返回值是调用了dyld::_main我们定位dyld::_main函数

dyld::_main

  • dyld::_main 总代码1000行左右大部分都是条件准备:环境,平台信息,路径。主机信息。我们不一一分析。我们重点不是这些。有兴趣的可以上wwdc看细节。

1.png

1.png

  • getHostInfo(mainExecutableMH, mainExecutableSlide)平台信息的处理;

2.png

  • dyld条件准备阶段

mapSharedCache共享缓存

在iOS系统中,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个加载到内存,然而,很多系统库几乎是每个程序都会用到的,如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下

8.png

  • 我们关注到两个函数checkSharedRegionDisable检查共享缓存是否禁用没有共享区域iOS无法运行,mapSharedCache调用loadDyldCache 加载缓存

mapSharedCache共享缓存加载

9.png

共享缓存加载又分为三种情况:

  • 仅加载到当前进程,调用mapCachePrivate()
  • 共享缓存已加载,不做任何处理。
  • 当前进程首次加载共享缓存,调用mapCacheSystemWide()

instantiateFromLoadedImage为主程序初始化镜像加载器

1.png

  • segment段的个数最大是256
  • command的个数最大是4096
  • 确保必须依赖了libSystem

segmentcommand以及macho,通过MachOView工具认识下可执行文件

1.jpg

loadInsertedDylib 插入动态库

1.png

通过loadInsertedDylib插入动态库,此时有的动态库数量是所有的镜像文件减一

link链接主程序

2.png

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);
	 ...
	__block uint64_t t2, t3, t4, t5;
	{
		dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
		t2 = mach_absolute_time();
		//递归重定位
		this->recursiveRebaseWithAccounting(context);
		context.notifyBatch(dyld_image_state_rebased, false);

		t3 = mach_absolute_time();
		if ( !context.linkingMainExecutable )
		     //递归绑定非懒加载
		     this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

		t4 = mach_absolute_time();
		if ( !context.linkingMainExecutable )
			//弱绑定
			this->weakBind(context);
		t5 = mach_absolute_time();
	}      	 
}

link中调用了ImageLoader::link方法,ImageLoader负责加载image文件

  • 递归加载所有的动态库
  • 递归image重定位
  • 递归绑定非懒加载
  • 弱绑定

recursiveLoadLibraries递归绑定动态库

void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool 
preflightOnly, const RPathChain& loaderRPaths, const char* loadPath){
    ...
    // get list of libraries this image needs
    //获取当前的image依赖的动态库
    DependentLibraryInfo libraryInfos[fLibraryCount]; 
    this->doGetDependentLibraries(libraryInfos);

    // get list of rpaths that this image adds
    //获取当前的image依赖的动态库的文件路径
    std::vector<const char*> rpathsFromThisImage;
    this->getRPaths(context, rpathsFromThisImage);
    const RPathChain thisRPaths(&loaderRPaths, &rpathsFromThisImage);

    // 加载image依赖的动态库
    for(unsigned int i=0; i < fLibraryCount; ++i){
      ...
      dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(),
      &thisRPaths, cacheIndex);
      // 保存加载的动态库
      setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward);	 
      ...
    `}`

    //告诉image依赖的动态库去加载各自需要的动态库
    for(unsigned int i=0; i < libraryCount(); ++i) {
            ImageLoader* dependentImage = libImage(i);
            if ( dependentImage != NULL ) {
                 dependentImage->recursiveLoadLibraries(context, preflightOnly,
                 thisRPaths, libraryInfos[i].name);
            }
    }
}

  • 获取当前image依赖的动态库和动态库的文件路径
  • 加载image依赖的动态库,并保存起来
  • 告诉image依赖的动态库去加载各自需要的动态库

weakBind 弱绑定主程序

3.png

  • 前期所有的准备都是initializeMainExecutable()下面重点来分析他

initializeMainExecutable初始化主程序

4.png

  • 先运行动态库的初始化方法,再运行主程序的初始化方法
  • 全局搜runInitializers
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.imagesAndPaths[0] = { this, this->getPath() };
        // 递归当前image的镜像列表实例化
	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
// upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
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;
	// Calling recursive init on all images in images list, building a new list of
	// uninitialized upward dependencies.
        //递归所有镜像列表中的所有`image`,如果有没有初始化就去初始化
	for (uintptr_t i=0; i < images.count; ++i) {
		images.imagesAndPaths[i].first->recursiveInitialization(context, 
                thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
        // 为了保证所有的向上依赖关系都初始化,再次把没有初始化的image去初始化
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}

  • 全局搜索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);
        ...
        // initialize lower level libraries first
         //优先初始化依赖最深的库比如libsystem,必须先等这些最基础库初始化完成
        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);
                }
           }
        }
			
        // 将要初始化的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
        // 初始化image
        bool hasInitializers = this->doInitialization(context);
        // image初始化完成
        // let anyone know we finished initializing this image
        fState = dyld_image_state_initialized;
        oldState = fState;
        //下句柄通知
        context.notifySingle(dyld_image_state_initialized, this, NULL);
	recursiveSpinUnLock();
}
  • recursiveInitialization 主要做了三件事情。

  • 1.初始化系统库

  • 2.初始化系统库依赖的镜像文件

    1. 初始化镜像文件doInitialization以及通知注入初始化完成
  • context.notifySingle 中的context 是 const LinkContext 它是struct 类型调用了函数

void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);

  • 下面重点分析notifySingle 流程 和 doInitialization

notifySingle 通知注入

全局搜索 notifySingle

gLinkContext.notifySingle = &notifySingle;

1.png

  • (*sNotifyObjCInit)(image->getRealPath(), image->machHeader())这是函数调用,而且参数是跟image有关的,现在只要搞清楚在哪里赋值就可以了。全局搜索sNotifyObjCInit,源码如下

5.png

  • 在registerObjCNotifiers 中的第二个参数 init 它的类型是_dyld_objc_notify_initsNotifyObjCInit赋值,那么_dyld_objc_notify_init又等于什么了?

  • 全局搜_dyld_objc_notify_init没有找到一处关于_dyld_objc_notify_init的赋值。解决办法只能通过下符号断点跟流程看看

5.png

  • 堆栈信息看出_dyld_objc_notify_registerlibobjc.A.dylib_objc_init` 发起,我们跟进libobjc源码定位看下。

6.png

  • 流程分析由 doInitialization --> doModInitFunctions --> libSystem_initializer --> libdispatch_init --> _os_object_init --> _objc_init --> _dyld_objc_notify_register --> registerObjCNotifiers

  • libSystem_initializer方法在libSystem系统库中

  • libdispatch_init_os_object_init方法在libdispatch系统库中

  • _objc_init方法在libobjc系统库中

  • 从这里我们就能得出了完整的闭环有dyld调起libSystem初始化以及调起libdispatch,libobjc一系列的动态库,等到镜像文件和依赖文件初始化完成,发出通知链接libobjc去做方法和类等各项工作

dyld流程图

还在制作中。

总结

load 方法的调用流程

  • _dyld_start --> dyldbootstrap::start --> dyld::_main --> intializeMainExecutable --> runInitializers --> processInitializers --> runInitializers -->recursiveInitialization --> notifySingle --> load_images -->+[ViewController load]

_objc_init方法的调用流程

  • doInitialization --> doModInitFunctions --> libSystem_initializer --> libdispatch_init --> _os_object_init --> _objc_init --> _dyld_objc_notify_register -->registerObjCNotifiers

这两个调用流程通过doInitializationnotifySingle 完美形成一个完整的闭环