IOS底层原理之dyld程序加载

1,339 阅读16分钟

前言

前面探究了基本上都是main函数之后底层的流程,那么今天就探究下main函数之后底层究竟默默的做了哪些操作

准备工作

编译和库

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

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

编译流程图

image.png

动态库静态库

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

动态库静态库区别

静态库优缺点

优点

  • 静态库被打包到可执行文件中,编译成功后可执行文件可以独立运行,不需要依赖外部环境 缺点
  • 编译的文件会变大,如果静态库更新必须重新编译

动态库优缺点

优点

  • 减少打包之后App的大小
  • 共享内容,资源共享
  • 通过更新动态库,达到更新程序的目的 缺点
  • 可执行文件不可以单独运行,必须依赖外部环境

静动态库图解

image.png

dyld加载流程

因为是探索main函数之前的流程,那么值能通过汇编跟流程,直接在main函数断点

image.png

堆栈信息显示libdyld.dylib库的start函数是开始的位置,然后直接到了main函数。中间过程还是一无所知。load方法是比main函数先调用的,那就在load方法断点继续调试

image.png

堆栈信息清楚的展示了load方法的调用流程,简单整理下流程

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

_dyld_start是在dyld的源码库,_dyld_start详细汇编信息如下

image.png

汇编中_dyld_start之后调用的是dyldbootstrap::start方法。在dyld源码中全局搜索dyldbootstrap::start 发现没有实现的地方都是汇编,在全局搜索dyldbootstrap

image.png dyldbootstrap是一个命名空间,在dyldInitialization.cpp文件中搜索start方法

image.png

  • 重定位dyld,因为App一启动系统就会自动给App随机分配ASLRdyld需要重定位因为它需要到当前进程中获取自己的信息
  • 调用dyld::_main方法,获取返回结果

dyld::_main

dyld::_main方法中的代码有800多行,就把整体流程梳理下,掌握整体的加载启动流程感觉就可以了,细节部分感兴趣的可以自己探索

配置环境变量

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* 
                apple[], uintptr_t* startGlue)
{
	//系统内核检测
	//Check and see if there are any kernel flags
	dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));
        ...
      
	// 获取主程序的hash值
	uint8_t mainExecutableCDHashBuffer[20];
	// 主程序的hash值初始化是0
	const uint8_t* mainExecutableCDHash = nullptr;
	if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
           ...
	  mainExecutableCDHash = mainExecutableCDHashBuffer;//赋值
	}
        //根据macho头文件配置CPU架构的信息,就是一些文件配置
	getHostInfo(mainExecutableMH, mainExecutableSlide);
        
         ...
         
	uintptr_t result = 0; // result就是main函数的地址
	sMainExecutableMachHeader = mainExecutableMH;//可执行文件的头文件
        sMainExecutableSlide = mainExecutableSlide;  //加载到进程系统自动提供ASLR虚拟内存偏移
        
	{
            __block bool platformFound = false;
            //验证主程序是什么架构的是arm64还是x86的是64位的还是32位的
            ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform
            (^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
                 
                    gProcessInfo->platform = (uint32_t)platform;
                    platformFound = true;
            });
	}
        
       ...
      //设置上下文 就会把信息保存起来,保存到 gLinkContext中
      setContext(mainExecutableMH, argc, argv, envp, apple);
      ...
      //文件是否是受限的,AFMI 苹果移动文件保护机制
      configureProcessRestrictions(mainExecutableMH, envp);
      ...
      // set again because envp and apple may have changed or moved
      //再次更新上下文信息
      setContext(mainExecutableMH, argc, argv, envp, apple);
      
      //环境变量的配置,xcode配置环境变量控制台可以打印信息
      if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
      if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
            

上面就是一些读取macho的头文件信息,保存设置,以及环境变量的设置。其实就是准备工作

加载共享缓存

共享缓存IOS是必须要有共享缓存的,共享缓存中存的都是系统级别的动态库。比如UIKitCoreFoundation等。自己创建的动态库或者第三方的动态库不会放在共享缓存中 image.png

  • checkSharedRegionDisable方法是检测是否需要不同的架构是否需要共享缓存
  • mapSharedCache加载共享缓存

image.png

checkSharedRegionDisable方法很明显的提示IOS是必须要有共享缓存的。下面探究下具体是怎么加载共享缓存的mapSharedCache方法中有调用了loadDyldCache加载共享缓存的方法

image.png

共享缓存的加载共有三种情况

  • 强制私有forcePrivate = YES,表示强制私有。只加载到当前App进程中,不放在共享缓存中
  • 共享缓存已加载:如果你依赖的库在共享缓存中已经加载过了,此时就可以直接用无需其他操作
  • 第一次加载;如果你依赖的库共享缓存中没有,它就会被加载到共享缓存中

我想现在大家对共享缓存的加载有一个清晰的认知,IOS是必须有共享缓存且共享缓存中只存放系统库,创建共享缓存的目的是为了多进程共同使用系统库

dyld3dyld2

dyld3有叫做闭包模式它的加载速度更快,效率更高。IOS11 以后主程序都是用dyld3加载,IOS13以后动态库和三方库用dyld3加载

  //判断是否使用闭包模式也是dyld3的模式启动 ClosureMode::on 用dyld3 否则使用dyld2
  if ( sClosureMode == ClosureMode::Off ) {
    //dyld2
    if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closures\n");
  } else {
    //dyld3  DYLD_LAUNCH_MODE_USING_CLOSURE 用闭包模式
    sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    const dyld3::closure::LaunchClosure* mainClosure = nullptr;
    dyld3::closure::LoadedFileInfo mainFileInfo;
    mainFileInfo.fileContent = mainExecutableMH;
    mainFileInfo.path = sExecPath;
    ...
    // 首先到共享缓存中去找是否有dyld3的mainClosure
    if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
            mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
            ...
    }
 
   ...
    //如果共享缓存中有,然后去验证closure是否是有效的
    if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, 
    、mainExecutableCDHash, true, envp) ) {
            mainClosure = nullptr;
            sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
    }
    
    bool allowClosureRebuilds = false;
    if ( sClosureMode == ClosureMode::On ) {
            allowClosureRebuilds = true;
    } 
    ...
    
    //如果没有在共享缓存中找到有效的closure 此时就会自动创建一个closure
    if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
        ...
        if ( mainClosure == nullptr ) { 
        // 创建一个mainClosure
        mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, 
        bootToken);
        if ( mainClosure != nullptr )
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
        }
    }
   
    // try using launch closure
    // dyld3 开始启动
    if ( mainClosure != nullptr ) {
        CRSetCrashLogMessage("dyld3: launch started");
        ...
        //启动 launchWithClosure
        bool launched = launchWithClosure(mainClosure, 
        sSharedCacheLoadInfo.loadAddress,(dyld3::MachOLoaded*)mainExecutableMH,...);
         //启动失败                                                              
        if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // closure is out of date, build new one
                // 如果启动失败 重新去创建mainClosure
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, 
                envp, bootToken);
                if ( mainClosure != nullptr ) {
                    ...
                    //dyld3再次启动
                    launched = launchWithClosure(mainClosure,  sSharedCacheLoadInfo.loadAddress,
                    (dyld3::MachOLoaded*)mainExecutableMH,...);
                }
            }
            if ( launched ) {
                    gLinkContext.startedInitializingMainExecutable = true;
                    if (sSkipMain)
                    //启动成功直接返回main函数的地址
                    result = (uintptr_t)&fake_main;
                    return result;
            }
            else {  
            //启动失败      
            }
    }
}

dyld3启动过程是经过很多次的尝试,系统给了很多次机会,一般情况不会出现启动失败的情况

如果不采用dyld3的方式就会采用dyld2的模式

// could not use closure info, launch old way
	// 用dyld2的模式,不用dyld3
	sLaunchModeUsed = 0;
	// 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);
	sAddLoadImageCallbacks.reserve(4);
	sImageFilesNeedingTermination.reserve(16);
	sImageFilesNeedingDOFUnregistration.reserve(8);

#if !TARGET_OS_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
	  file generation process
	WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif
	try {
		// add dyld itself to UUID list
		addDyldImageToUUIDList();
                ...
 }

今天主要看下探究下dyld3的模式,因为这个以后会逐渐取代dyld2,后面有时间再去探究下dyld2过程

总结dyld3的启动流程

  • 从共享缓存中获取dyld3的实例mainClosure
  • 验证mainClosure是否有效
  • 再去共享缓存中查找有效的mainClosure,如果有直接启动
  • 如果没有,创建mainClosure
  • 启动mainClosure,启动dyld3
  • 启动成功以后,主程序启动成功,result就是main函数的地址,返回到dyldbootstrap::start方法,然后进入mian函数

实例化主程序

dyld3dyld2走的流程都是一样的只不过dyld3用的闭包模式。image在源码中经常出现,image并不是图片的意思,而是镜像文件镜像文件就是从磁盘映射到内存的macho文件。可以理解为只要是加载到内存的macho文件就叫镜像文件

image.png

实例化主程序就是把需要的主程序的部分信息加载到内存中,通过instantiateMainExecutable方法返回ImageLoader类型的实例对象,然后对主程序进行签名

image.png

将实例化的image添加到镜像文件数组中,在这注意一点主程序的image是第一个添加到数组中的。现在探究下 ImageLoaderMachO::instantiateMainExecutable方法

image.png

加载macho文件中command信息,以及校验。sniffLoadCommands中代码取部分重要的探究

image.png sniffLoadCommands中加载segmentcommod信息,以及一些校验

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

大家可能对segmentcommand以及macho头文件还是有点模糊,通过MachOView工具认识下可执行文件

image.png 图中很清晰明了macho头文件就是架构信息以及文件类型等。macho文件主要是3块内容HeaderCommodsData

插入动态库

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

链接主程序

image.png

link中调用了ImageLoader::link方法,ImageLoader负责加载image文件(主程序,动态库)每个image对应一个ImageLoader类的实例

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

	... 
        //链接过程中可以统计链接动态库的时间,在环境变量设置可以打印出信息 	 
}
  • 递归加载所有的动态库
  • 递归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依赖的动态库去加载各自需要的动态库

链接动态库

image.png

链接动态库和链接主程序的逻辑基本一样,注意下循环取image文件的时候是从1开始,因为第0个位置是主程序,下面来验证下

image.png

image list中第一个数据就是主程序

弱绑定主程序

image.png

可能大家会有疑问在链接主程序的时候里面不是有弱符号绑定嘛。因为在链接主程序之linkingMainExecutable = true,所以link里面的弱绑定在主程序时是不调用的,等动态库的都进行了弱绑定,最后对主程序进行弱绑定

运行初始化方法

image.png

initializeMainExecutable在这不展开因为内容很多,放在后面单独探究

返回main函数

image.png

获取到main函数以后,就会进入大家熟悉main函数中

总结

dyld加载流程: dyld::_main --> 配置环境变量 --> 加载共享缓存 --> 实例化主程序 --> 插入动态库 --> 链接主程序 --> 链接动态库 --> 弱绑定主程序 --> 运行初始化方法 --> 返回main函数

initializeMainExecutable

void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs
    // 运行所有的dylibs中的initialzers方法
    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]);

    ...
}

先运行动态库的初始化方法,再运行主程序的初始化方法

runInitializers

全局搜索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
         //优先初始化依赖最深的库
        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();
}
  • 需要初始化的动态库image是从libImage()中获取,而libImage()的数据是在链接动态库的时recursiveLoadLibraries中的setLibImage保存的image
  • 系统会根据每个库的依赖深度去初始化,深度值最大的先去初始化,每次初始化都会有一个image文件
  • image都会调用 context.notifySingle方法去调用load_images调用load方法
  • doInitialization是初始化没有依赖的库
  • context.notifySingle(dyld_image_state_initialized, this, NULL) 实际没啥作用,notifySingle方法中并没有判断,有可能是在objc注册回调时里面根据dyld_image_state_initialized状态去调用系统库

下面探究下notifySingledoInitialization,全局搜索notifySingle源码如下

image.png

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

image.png

registerObjCNotifiers方法中对sNotifyObjCInit进行赋值,全局搜索registerObjCNotifiers, 源码如下

image.png

全局搜索_dyld_objc_notify_register,发现dyld源码中并没有调用的这个方法的地方。下面该怎么办呢,没法探索了嘛,不知道大家有没有忘记前面探索时有一种符号断点的方法。在项目工程中添加_dyld_objc_notify_register符号断点,我的项目工程跑运行的是Mac用的是dyld2,如果运行的是IOS用的就是dyld3

image.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系统库中,objc源码库最熟悉

下面就来探究下*sNotifyObjCInit具体执行了什么内容,首先找到赋值的地方_objc_init调用的_dyld_objc_notify_register所以赋值应该在_objc_init方法

image.png

sNotifyObjCInit= load_images 其实就是调用load_images方法,下面探究下load_images方法

image.png

call_load_methods 从方法名字来看就是调用load方法,接着往下探究call_load_methods 方法

image.png

循环遍历的load方法,探究下call_class_loadscall_category_loads方法

image.png

image.png

image.png

类和分类都会调用load方法,从这里调用顺序可不可得出这样一个结论

  • 类的load比分类的load方法先调用,类中load方法调用完才开始调用分类的load方法
  • 类中的load方法按编译先后顺序,谁先编译谁的load方法先调用
  • 分类中的的load方法按编译先后顺序,谁先编译谁的load方法先调用
  • 哎呀,一不小心又解决了一个面试题 load加载流程正如我们堆栈信息显示的流程是一样的,一步一步验证了堆栈的信息

_objc_init流程

_objc_initobjc系统库中,除了是大家最熟悉的源码库还有就是 _objc_init起到承上启下的作用。所以从_objc_init反推整个流程。调用_objc_init方法的是_os_object_init方法,在libdispatch源码库中全局搜索_os_object_init

image.png

_os_object_init方法确实调用_objc_init方法。_os_object_init方法是被libdispatch_init调用,继续验证

image.png

libdispatch_init方法确实调用_os_object_init方法。libdispatch_initlibSystem_initializer调用,libSystem_initializer方法是在libSystem系统库中

image.png

libSystem_initializer方法确实调用libdispatch_init方法。libSystem_initializer方法是被doModInitFunctions调用, doModInitFunctions方法是在dyld源码库中的

image.png libSystem的初始化程序,必须最先运行,其它的都给我往后稍一稍,doModInitFunctions 调用所有的C++函数

main函数下面添加一个全局的C++函数,断点看下堆栈信息

image.png

堆栈信息显示是doModInitFunctions方法调用了全局的c++方法,是在load方法之后

image.png doModInitFunctions方法是被doInitialization调用,看到doInitialization应该很熟悉

image.png

recursiveInitialization调用doInitialization方法,又会到了递归的方法里,完美的串联起来

总结

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 完美形成一个完整的闭环

main函数入口

上面的探究发现C++函数是在load方法之后执行,但是是在main函数之前执行,在C++函数中加上断点一步步调试,一定会进入main函数中的

image.png

断点汇编显示在C++函数中,此时一步步调试进入下个流程找到main函数调用的地方

image.png dyldbootstrap::start返回了main函数的地址,读取x86_64寄存器发现第一个寄存器rax存放的是main函数的地址,最后一步跳转到main函数

注意:不要奇奇怪怪的修改main函数的名称,如果修改编译直接报错,main函数是固定函数

image.png

dyld加载流程图

image.png

总结

dyld探究是比较枯燥、乏味、复杂的,在探索学习的过程遇到了很多问题。贵在坚持,基本上把握了dyld整体的加载流程