iOS 应用程序加载
- 1. APP 加载分析
- 1.1 动静态库
- 1.2 加载过程
- 2.1 `reloadAllImages` 分析
- 2.2 `initializeMainExecutable` 运行所有初始化程序
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 variablesslide = slideOfMainExecutable(dyldsMachHeader);bool shouldRebase = slide != 0;#if __has_feature(ptrauth_calls)shouldRebase = true;#endifif ( shouldRebase ) {rebaseDyld(dyldsMachHeader, slide);}// allow dyld to use mach messagingmach_init();// kernel sets up env pointer to be just past end of agv arrayconst char** envp = &argv[argc+1];// kernel sets up apple pointer to be just past end of envp arrayconst 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 dyldrunDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);#endif// now that we are done bootstrapping dyld, call dyld\'s mainuintptr_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 executablesMainExecutable = 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 initializersdoImageInit(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
函数。
- 点赞1
- 收藏
- 分享
- 文章举报
亮亮不想说话发布了18 篇原创文章 · 获赞 10 · 访问量 263私信关注