应用启动- dyld (主程序初始化)

733 阅读5分钟

1. 准备工作

在开始探究之前,先提供需要的源码以及扩展一些储备知识

1.1 源码

在探究iOS应用启动过程中需要使用到的源码

1.2 扩展知识

  • 应用在运行过程中不能独立运行,还需要依赖一些其他的库
  • 什么是库?可以加载到内存中的二进制文件
  • iOS中分为静态库动态库,那么静态库动态库有什么区别呢,主要是链接方式的不同,静态库是静态链接,动态库是动态链接 下面用一张图总结一下静态库动态库

库依赖.png

  • 什么是macho?实际上就是可执行的二进制文件,dyld加载的时候会按照它特定的格式去解析
  • 什么是image(镜像文件)?也就是我们的二进制文件映像到内存中
  • 什么是dyld?实际上动态链接加载器,我们的app之所以能够加载,就是通过dyld来操作的

2. dyld的引出

我们都知道,我们的应用程序加载的入口是main函数,但是main函数又是谁调用的呢,main打一个断点:

main.png 发现来自libdyldstart函数,我们继续通过符号断点start,发现无法断点。 根据开发的经验我们知道load方法,实际上是早于main方法调用的,尝试在load方法打断点,查看调用堆栈:

堆栈.png 发现最终是有调用_dyld_start方法,下面我们从dyld源码的这个方法开始继续探索

3.dyld主流程

  • 由于dyld依赖的东西太多,没法编译,只能通过函数去找全局搜索_dyld_start函数
  • 搜索发现i386x86_64arm不同的架构都实现了,流程都差不多,我们主要看arm的,也就是真机
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)

bl	__ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm

通过注释和汇编bl跳转dyldbootstrap::start,这个方法主要做了啥

  1. 发出kdebug跟踪点以指示dyld引导已启动
  2. 重新绑定修复dyld位置
  3. 设置堆栈canary的随机值
  4. _subsystem_init 系统相关初始化
  5. 调用dyld main函数,传入app 的machoASLR

dyld::main 函数

main函数的主流程我之前的博客也有讲到,可以参考dyld 加载App流程源码分析 ,dyld的细节很多,如果每个点都去分析,篇幅会很长

dyld流程分析.png

4.初始化主程序(initializeMainExecutable)

void initializeMainExecutable(){
// 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]);
	}
    }
	
    // run initializers for main executable and everything it brings up 
    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 )
		ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
	if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
		ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
  • 调用runInitializers方法初始化所有的动态库镜像文件
  • 调用runInitializers方法初始化主程序
  • 注册cxa_atexit方法,程序退出时终止所有镜像
  • 环境变量,打印设置 进入ImageLoader::runInitializers方法调用
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
//...
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
//...
}

调用processInitializers,初始化,然后通过notifyBatch通知出去 继续跟踪processInitializers方法,

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.
if ( ups.count > 0 )
	processInitializers(context, thisThread, timingInfo, ups);
  • 对所有image(镜像)递归初始化, 继续进入ImageLoader::recursiveInitialization方法:
void ImageLoader::recursiveInitialization {
1.// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i){
     dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
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);
  • 先初始化底层依赖文件
  • 依赖库初始化通知context.notifySingle(dyld_image_state_dependents_initialized
  • 镜像文件初始化doInitialization
  • 再次调用通知context.notifySingle(dyld_image_state_initialized

继续跟进先看一下notifySingle,可以看到会调用sNotifyObjCInit,继续查找发现registerObjCNotifiers有一个赋值,

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

继续查找registerObjCNotifiers的上层调用,发现由_dyld_objc_notify_register调用。 那么这个方法又是在何时调用的呢,找到objc源码,打开,搜索


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();
    runtime_init();
    exception_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

_objc_init的时候就会处方该方法,也就是load_images

  • sNotifyObjCMapped ->map_images
  • sNotifyObjCInit->load_images
  • sNotifyObjCUnmapped ->unmapped 那么_objc_init又是何时调起来的呢,打断点,然后根据堆栈信息开始反推

objc_init.png _objc_init(objc) ->_os_object_init(libdispathc)->libdispatch_init(libdispathc)->libSystem_initializer->doModInitFunctions 验证libSystem_initializer->doModInitFunctions

if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
  • 通过这里libSystem initializer must run first,我们得知调用了libSystem_initializer
  • 继续反推doModInitFunctions方法实际上是由doInitialization方法调用,这个方法是不是很熟悉,这个就是我们上面推出来的方法
  1. 所以实际山是doModInitFunctions->_objc_init->_dyld_objc_notify_register,在这里就会通过_dyld_objc_notify_register方法,吧objcload_images赋值给dyld源码里面的sNotifyObjCInit
  2. 然后通过调用context.notifySingle,调用sNotifyObjCInit,实际上就会调用objcload_images方法 用一张图总结一下主程序初始化过程:

主程序,流程图.png

案例(load、c++构造函数、 main)

新建一个demo项目,运行后分别打印:

__attribute__((constructor)) void qhFunc(){
    printf("来了 : %s \n",__func__);
}

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

发现运行的顺序是load->c++构造函数->main,那么这是为什么了,既然有了源码一切都好说,下面我们通过源码分析一下. mian函数在最后面,这个应该很好理解。main函数的地址是有,dyld::main返回。所以最后执行.

  • 在构造函数打断点:

断点C++.png 通过调用栈,c++构造函数doInitialization调用。 再看:recursiveInitialization函数

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

notifySingle 回调起load_image方法,继而调用load方法。那么这里就有一个疑问了,在doInitialization方法都没有初始化load_image,之前sNotifyObjCInit为空怎么可能调用load方法。

下面我们用一个方法做演示:我们在objc源码里面也添加c++构造函数。

依赖库.png 然后在我们主程序也添加load方法和c++构造函数,运行

主程序.png 打印结果:

截屏2021-07-13 下午8.40.30.png 现打印依赖库的C++构造函数,然后后主程序load主程序C++构造方法

  • 由于第一次递归会调用依赖镜像的doInitialization,这个时候就已经初始化。也就是sNotifyObjCInit会赋值。然后第二次进来调用context.notifySingle,就会调起load方法,让后再调用主程序的doInitialization方法。