应用程序加载
准备条件:dyld-732.8 源码
编译过程
源文件 -> 预编译 -> 编译 -> 汇编 -> 链接(.a/.lib.so) -> 可执行文件
编译过程 编译过程静态库
在链接阶段,会将汇编生成的目标与引用的库一起链接打包到可执行文件中
静态库动态库
程序编译并不会链接到目标代码中,而是在程序运行时才被载入
动态库-
优势
- 减少打包只有的APP的大小
- 共享内存,节约资源
- 通过更新动态库,达到更新程序的目的
-
常见动态库
- UIkit
- libdispatch
- libobjc.dyld
APP加载流程
APP加载流程- APP启动
- 加载
libSystem
- Runtime 向
dyld
注册回调函数 - 加载新的
image(镜像文件)
- 执行
map_images
、load_images
(执行这一步之后,会回到第4步之前,ImageLoader 加载 image) - 调用
main
函数
_dyld 分析
- _dyld 是动态链接器
- _dyld 将所有动态库链接到内存中
- _dyld_start:链接开始
程序加载的顺序
- load 方法
- load 前还有 _dyld_start
- 找到 arm64 的流程
通过注释找到
call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
- 找到 start 源码
- 第一步:
rebaseDyld(dyldsMachHeader);
。调整dyld,在磁盘中,所有的dyld的 DATA 段都连在一起,所以需要修复成真正的指针,以保证程序运行 - 第二步:
__guard_setup(apple);
。设置随机偏移值,这个是内存段的偏移,虚拟地址映射物理地址 - 第三步:
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
5. 找到 main 源码
整个 main 函数在 5880-6365部分,接近500行代码
-
第一部分:函数开始到下面这一段代码,前面都是环境判断、初始化操作;
CRSetCrashLogMessage("dyld: launch started"); setContext(mainExecutableMH, argc, argv, envp, apple);
-
第二部分:将dyld添加到uuidArray中以启用堆栈符号。中间其他代码都是环境判断以及日志处理
addDyldImageToUUIDList();
-
第三部分:
reloadAllImages:
,这段代码之后才是真正的开始加载镜像的一个分水岭。 -
第四部分:
instantiateFromLoadedImage
,为main执行文件初始化ImageLoader -
第五部分:
sInsertedDylibCount = sAllImages.size()-1;
,记录插入库的数量并描述插入库的顺序。- 先插入库:inserted libraries
- 然后是主库:then main
- 然后是其他库:then others
-
第六部分:
gLinkContext
的处理- 链接 main 执行文件:
gLinkContext.linkingMainExecutable = true;
- 申请插入式缓存:
ImageLoader::applyInterposingToDyldCache(gLinkContext);
- 通知dyld状态:
gLinkContext.notifyBatch(dyld_image_state_bound, false);
- 断开 main 执行文件的链接:
gLinkContext.linkingMainExecutable = false;
- 链接 main 执行文件:
-
第七部分:
initializeMainExecutable();
,初始化主执行文件,这里才是真正的初始化、绑定等操作-
执行初始化器:
runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
-
处理初始化器:
processInitializers(context, thisThread, timingInfo, up);
-
递归初始化:对images列表中的所有映像调用recursive init,构建未初始化的向上依赖项的新列表。
recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&);
-
-
第八部分:
notifyMonitoringDyldMain();
,通知所有的监听对象(进程),当前进程即将进入main()
方法。这里通过 mach_msg 进行的通知- sendMessage
- mach_msg
- reloadAllImages
- instantiateFromLoadedImage
- sniffLoadCommands
dyld 流程:main函数分析
应用程序的入口:dyld::_main uintptr_t start(...)
1. 环境变量相关处理
- 从环境变量中读取可执行文件 cdHash
- checkEnvironmentVariables(envp);
- defaultUninitializedFallbackPaths(envp);
2. 加载共享缓存
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH,mainExecutableSlide);
mapSharedCache();
3. 将 dyld 本身添加到UUID列表
addDyldImageToUUIDList();
4. reloadAllImages:
-
实例化主程序:
instantiateFromLoadedImage
-
ImageLoaderMachO::instantiateMainExecutable
-
sniffLoadCommands
:确定此 mach-o 文件是否具有压缩的 LINKEDIT 以及 段数 addImage(image);
-
-
内核会映射到主要可执行文件中。我们需要为已经映射到主可执行文件中的文件中的文件创建一个
ImageLoader *
-
-
加载任何插入的动态库
loadInsertedDylib(*lib);
-
链接库
- 遍历:
sInsertedDylibCount
ImageLoader* image = sAllImages[i+1];
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
-
this->recursiveApplyInterposing(context);
:插入任何动态加载的镜像文件
- 遍历:
5. 运行所有初始化程序
-
initializeMainExecutable();
-
runInitializers
-
初始化准备:processInitializers(images.count)
-
递归一个个开始初始化:images.images[i]->recursiveInitialization
-
notifySingle 单个镜像通知-开始行动
- 获取镜像文件真实地址:
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
-
初始化objc通知
初始化objc通知 - 遍历初始化
this->doInitialization(context);
-
doImageInit(context);
Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide); libSystemInitialized; //libSystem 初始化必须提前
-
C++函数处理
doModInitFunctions(context);
-
- 获取镜像文件真实地址:
6. 通知监听 dyld 的main
notifyMonitoringDyldMain();
网友评论