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函数。