欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS 应用程序加载

程序员文章站 2024-03-23 22:06:16
...

1. APP 加载分析

1.1 动静态库

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

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

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

  3. 动静态库的区别?

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

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

编译过程:

iOS 应用程序加载

动静态库示例:

iOS 应用程序加载

1.2 加载过程

iOS 应用程序加载

2._dyld_start 分析

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

iOS 应用程序加载
通过bt,查看调用堆栈

iOS 应用程序加载

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

iOS 应用程序加载
全局搜索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列表

iOS 应用程序加载

  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 方法中

iOS 应用程序加载

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

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

iOS 应用程序加载

iOS 应用程序加载

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

iOS 应用程序加载

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

2.2 initializeMainExecutable 运行所有初始化程序

initializeMainExecutable方法:

iOS 应用程序加载
initializeMainExecutable 方法中,runInitializers运行主程序的可执行文件,在runInitializers方法中,代码如下:
iOS 应用程序加载
processInitializers方法中,进行初始化准备,遍历iamge.count,递归一个个开始初始化条件images[i]->recursiveInitialization,代码如下:
iOS 应用程序加载

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

iOS 应用程序加载

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

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

iOS 应用程序加载

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

iOS 应用程序加载

iOS 应用程序加载

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

iOS 应用程序加载

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

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

iOS 应用程序加载

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

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

iOS 应用程序加载

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,

iOS 应用程序加载

iOS 应用程序加载

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

libdispatch_init部分代码:
iOS 应用程序加载

_os_object_init代码:
iOS 应用程序加载

最终调用到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方法之后的原因。
iOS 应用程序加载

总结

  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函数。
相关标签: iOS底层探索