应用启动- dyld 加载分析

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方法
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享