前几天学习了MachO文件的基本结构,简单了解了MathO的工作原理,但是里面经常会涉及到dyld加载器,本篇主要学习dyld加载应用程序中的那些事。
dyld
每个程序我们看到的入口函数都是main函数,但是程序并不是从该函数开始执行的,在执行mian函数之前就已经执行了+load和constructor构造函数,首先看看main函数在执行之前都发生了什么。
dyld简介
程序在运行时会依赖很多系统动态库,系统动态库会通过动态库加载器dyld加载到内存中,在操作系统内核做好程序的准备工作之后,后续工作就交给dyld。
dyld加载流程
首先编写一个demo,定义一个+ load函数,下断点,同时在main函数下断点,运行发现确实是在main函数之前断下的,接着来研究一下,加载main函数之前都干啥了。
dyldbootstrap::start
系统启动应用的入口是_dyld_start,首先dyld会调用dyldbootstrap::start,该方法会返回main函数指针,看一下dyldbootstrap的start做了什么。
|
|
从start函数的源码可知:dlyd会内存中找到一块地址给MachO使用,也就是ASLR,内存偏移。
最后start函数调用了dyld::_main并返回程序main函数的指针。
dyld::_main
main函数很多,这里只分析了一些关键的代码
配置环境变量
从main函数的初始,到函数getHostInfo()之前都是在配置一些环境变量,贴出部分代码功能。
加载共享缓存
在iOS系统中,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个加载到内存,然而如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下,按不同的架构保存分别保存着。其中包括UIKit,Foundation等基础库。
实例化主程序
主程序的实例化主要是将MachO文件中LoadCommons段内信息放入内存中。
加载动态链接库
加载外部的动态链接库,注入涉及到的库也是通过这种方式添加。
链接主程序
链接其他动态链接库
加载load方法&寻找main函数
从加载load方法到main函数中间这个过程相对比较重要,由于再三还是花一些功夫去研究一下,跳过这个复杂的过程总觉得这波白搞了,那么继续搞。
- 首先进入initializeMainExecutable源码,主要是循环遍历,都会执行runInitializers方法
- 进入runInitializers方法,这个防护核心是processInitializers函数的调用
- 进入processInitializers函数,其中对镜像列表调用recursiveInitialization函数进行递归实例化
- 全局搜索recursiveInitialization函数
- 首先分析notifySingle函数,定位到加载image->getRealPath(), image->machHeader()的sNotifyObjCInit
- 全局搜索sNotifyObjCInit,找到其赋值操作
- 搜索registerObjCNotifiers在哪里调用了,发现在_dyld_objc_notify_register进行了调用
- 在dyld源码中搜索_dyld_objc_notify_register函数的调用点,无法找到,我们用符号断点的方法定位其调用
- 符号断点断下之后,lldb输入finish命令,发现是objc_init调用了_dyld_objc_notify_register函数
- 在objc源码中搜索_dyld_objc_notify_register的调用位置
-进入load_images函数,查看load函数调用
- 进入call_load_methods,发现其核心是通过do-while循环调用+load方法
- 最终在call_class_loads方法中找到了调用load方法的位置
- 另外在notifySingle方法下面紧跟着doInitialization方法是调用系统C++构造函数的方法
总结
至此dyld加载main函数的基本流程大致分析完了,有幸找了一位大佬总结的dyld流程图做个总结