iOS 应用程序加载
iOS 应用程序加载
1. APP 加载分析
1.1 动静态库
-
app依赖很多底层库,底层库是很什么?
可执行的代码的二进制,可以被操作系统写入到内存
-
库分为几种?
静态库: .a .lib,动态库: framework .so .dll -
动静态库的区别?
静态库:在链接阶段,会将汇编生成的目标文件与引用的库一起链接打包到可执行文件中,可能会重复编译多次
动态库:程序编译并不会链接到目标代码中,而是程序运行时才被载入。
优势:减少打包之后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()
,
-
环境变量相关处理,先 从环境中获取主可执行文件的
cdHash
,checkEnvironmentVariables(envp);
,然后defaultUninitializedFallbackPaths(envp);
-
加载共享资源
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
-
将
dyld
本身,添加到UUID
列表
-
reloadAllImages
-
运行所有初始化程序
initializeMainExecutable()
-
通知监听
dyld
的main
,然后进入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()
读取加载镜像文件。
- 加载任何插入动态库
loadInsertedDylib(*lib)
,读取为image
。
- 链接库,遍历代码
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
进行通知要初始化镜像文件,此时,sNotifyObjCInit
为nil
,则调用无意义,无法完成通知。
那么在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
方法之后的原因。
总结
-
APP加载过程:程序启动依次加载
dyld
、libSystem
、libdispathc.dyld
、libobjc
动态库,最终调用_objc_init()
方法,在此方法中Runtime
向dyld
注册回调函数,加载新的image
,执行map_images
、load_images
,imageLoader
加载image
,调用main
函数 -
在
dyld
中,__dyld_start
链接开始,调用start()
方法,调用dyld::_main()
方法。在此方法中,- 环境变量相关处理,先获取可执行文件的
cdHash
,checkEnvironmentVariables(envp)
、defaultUninitializedFallbackPaths(envp)
- 加载共享缓存,通过
checkSharedRegionDisable()
验证共享缓存路径,然后mapSharedCache()
,加载共享缓存。 - 将
dyld
本身,添加到UUID
列表,addDyldImageToUUIDList()
。 - 然后加载所有的镜像文件,
reloadAllImages
。 - 运行所有初始化程序
initializeMainExecutable()
- 通知监听
dyld
的main
,然后进入main
函数,notifyMonitoringDyldMain()
。
- 环境变量相关处理,先获取可执行文件的
-
reloadAllImages
加载镜像文件的步骤:- 实例化主程序
instantiateFromLoadedImage()
,内核会映射到主要可执行文件中,我们需要为映射到主可执行文件的文件,创建ImageLoader
。在此方法中,然后读取image
,然后addImage()
读取加载镜像文件。会先在instantiateMainExecutable()
中,会确认此mach-o文件中是否具有压缩的LINKEDIT
以及段数。 - 加载插入任何动态库
loadInsertedDylib(*lib)
,将其读取为镜像文件iamge
。 - 链接库。先遍历,读取
image
,然后link
。在link
中,递归插入动态加载的镜像文件。
- 实例化主程序
-
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_images
、load_images
,imageLoader
加载image
,调用main
函数。
上一篇: SpringMVC 常见面试问题
下一篇: PAT文件的输入输出