iOS 应用程序加载

1. APP 加载分析

1.1 动静态库

  1. app依赖很多底层库,底层库是很什么?

    可执行的代码的二进制,可以被操作系统写入到内存

  2. 库分为几种?
    静态库: .a .lib,动态库: framework .so .dll

  3. 动静态库的区别?

    静态库:在链接阶段,会将汇编生成的目标文件与引用的库一起链接打包到可执行文件中,可能会重复编译多次

    动态库:程序编译并不会链接到目标代码中,而是程序运行时才被载入。
    优势:减少打包之后APP的大小,共享内容,节约资源,通过更新动态库,达到更新程序的目的。
    常见动态库:UIKit,libdispatch、libobj.dyld

编译过程:

动静态库示例:

1.2 加载过程

2._dyld_start 分析

通过在+ (void)load方法中添断点,查看调用堆栈,通过汇编查看,在程序启动的时,调用dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*),那么在这之前还有一系列操作,


通过bt,查看调用堆栈

接下来我们分析一下_dyld_start,查看dyld源码,全局搜索_dyld_start,发现会跳转dyldbootstrap::start 方法,为c++方法,


全局搜索start(,找到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)
{
	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
    slide = slideOfMainExecutable(dyldsMachHeader);
    bool shouldRebase = slide != 0;
#if __has_feature(ptrauth_calls)
    shouldRebase = true;
#endif
    if ( shouldRebase ) {
        rebaseDyld(dyldsMachHeader, slide);
    }

	// allow dyld to use mach messaging
	mach_init();

	// kernel sets up env pointer to be just past end of agv array
	const char** envp = &argv[argc+1];
	
	// kernel sets up apple pointer to be just past end of envp array
	const char** apple = envp;
	while(*apple != NULL) { ++apple; }
	++apple;

	// set up random value for stack canary
	__guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
	// run all C++ initializers inside dyld
	runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

	// now that we are done bootstrapping dyld, call dyld's main
	uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
	return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

进入dyld::_main(),

  1. 环境变量相关处理,先 从环境中获取主可执行文件的cdHash, checkEnvironmentVariables(envp);,然后defaultUninitializedFallbackPaths(envp);

  2. 加载共享资源checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

  3. dyld本身,添加到UUID列表

  1. reloadAllImages

  2. 运行所有初始化程序initializeMainExecutable()

  3. 通知监听dyldmain,然后进入main函数。

2.1 reloadAllImages 分析

1.实例化主程序

CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

instantiateFromLoadedImage 方法中

先读取image, addImage()读取加载镜像文件。

  1. 加载任何插入动态库loadInsertedDylib(*lib),读取为image

  1. 链接库,遍历代码iamge,然后link

link的过程中,会递归,插入任何动态加载的镜像文件

2.2 initializeMainExecutable 运行所有初始化程序

initializeMainExecutable方法:


initializeMainExecutable 方法中,runInitializers运行主程序的可执行文件,在runInitializers方法中,代码如下:

processInitializers方法中,进行初始化准备,遍历iamge.count,递归一个个开始初始化条件images[i]->recursiveInitialization,代码如下:

recursiveInitialization 中,通过上下文的notifySingle方法,通知要进行单个镜像的初始化。

notifySingle方法是怎么进行通知呢?

通过查看notifySingle()方法,在此方法中,通过(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());获取镜像文件的真实地址,如下图:

那么sNotifyObjCInit指针地址又是什么时候传进来的呢?
通过搜索发现sNotifyObjCInit是在registerObjCNotifiers()方法中进行赋值,而registerObjCNotifiers()方法又是在_dyld_objc_notify_register()方法中调用,通过传进来的init地址,回调函数,最终加载镜像文件

那么_dyld_objc_notify_register方法是在什么时候调用,给sNotifyObjCInit赋值,让(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());回调函数有意义,将镜像文件传递回去呢?接下来我们查看一下libobjc源码

由此猜测:是在_objc_init()方法中,注册通知,传入函数地址,然后最终回调镜像文件的。那么这个回调地址又是怎样在dyld库和libobjc之间传递的呢?

_objc_init()方法中断点,然后bt,打印堆栈信息如下:

这也从另一个方面进一步验证了上面所说的启动流程,当在recursiveInitialization方法中通过notifySingle进行通知要初始化镜像文件,此时,sNotifyObjCInitnil,则调用无意义,无法完成通知。

那么在notifySingle之后,调用了this->doInitialization(context),也验证了调用堆栈,

doInitialization 方法中,

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

先调用doImageInit(context),确保libSystem库必须提前初始化完成。

再调用doModInitFunctions()方法,必然会调用libSystem库中的libSystem_initializer方法。

libSystem_initializer方法中调用libdispatch_init,

然后在libdispatch库中,调用libdispatch_init函数。

libdispatch_init部分代码:

_os_object_init代码:

最终调用到libobjc库中的_objc_init的方法,调用_dyld_objc_notify_register,传入地址给sNotifyObjCInit,然后回调镜像文件。

而先调用notifySingle方法,在调用doInitialization方法,而notifySingle方法中的sNotifyObjCInit指针是从_objc_init方法中_dyld_objc_notify_register(&map_images, load_images, unmap_image)load_images传递过去的,这也解释了C++函数和构造函数的调用在load方法之后的原因。

总结

  1. APP加载过程:程序启动依次加载dyldlibSystemlibdispathc.dyldlibobjc动态库,最终调用_objc_init()方法,在此方法中Runtimedyld注册回调函数,加载新的image,执行map_imagesload_imagesimageLoader加载image,调用main函数

  2. dyld中,__dyld_start链接开始,调用start()方法,调用dyld::_main()方法。在此方法中,

    • 环境变量相关处理,先获取可执行文件的cdHashcheckEnvironmentVariables(envp)defaultUninitializedFallbackPaths(envp)
    • 加载共享缓存,通过checkSharedRegionDisable()验证共享缓存路径,然后mapSharedCache(),加载共享缓存。
    • dyld本身,添加到UUID列表,addDyldImageToUUIDList()
    • 然后加载所有的镜像文件,reloadAllImages
    • 运行所有初始化程序initializeMainExecutable()
    • 通知监听dyldmain,然后进入main函数,notifyMonitoringDyldMain()
  3. reloadAllImages加载镜像文件的步骤:

    • 实例化主程序instantiateFromLoadedImage(),内核会映射到主要可执行文件中,我们需要为映射到主可执行文件的文件,创建ImageLoader。在此方法中,然后读取image,然后addImage()读取加载镜像文件。会先在instantiateMainExecutable()中,会确认此mach-o文件中是否具有压缩的LINKEDIT以及段数。
    • 加载插入任何动态库loadInsertedDylib(*lib),将其读取为镜像文件iamge
    • 链接库。先遍历,读取image,然后link。在link中,递归插入动态加载的镜像文件。
  4. initializeMainExecutable()运行所有初始化程序步骤:

    • runInitializers()
    • processInitializers初始化准备。
    • processInitializers中,遍历iamge.count,递归一个个开始初始化条件images[i]->recursiveInitialization
    • 在递归开始初始化条件中recursiveInitialization,通过notifySingle方法,对单个镜像通知开始初始化。获取镜像文件的真实地址(*sNotifyObjCInit)(image->getRealPath(), image->machHeader()), 而notifySingle中的sNotifyObjCInit是在objc_init()中注册传递过来的,所以只有当objc_init()调用时,重新加载image
    • notifySingle方法之后,遍历初始化this->doInitialization(context)
    • doInitialization方法中,先调用doImageInit(context),确保libSystem库必须提前初始化完成。再调用doModInitFunctions()方法,对 C++和构造函数处理,然后调用libSystem_initializer方法,调用libdispatch_init,调用_os_object_init,最终调用_objc_init方法。
    • _objc_init方法来注册回调函数,重新加载images,执行map_imagesload_imagesimageLoader加载image,调用main函数。
发布了17 篇原创文章 · 获赞 9 · 访问量 170

猜你喜欢

转载自blog.csdn.net/LiangliangSpeak/article/details/105458289