最近在看《程序员的自我修养》,学到了一些关于编译、linker、dyld等知识,当浏览一些大神的关于dyld的blog时,加上一些open source上源码,有了自己的理解,记录一下。
dyld 加载的宏观过程
系统加载dyld执行顺序:
dyld中的_main()的内容
先看看 _main()注释??:
??_main()的主要内容:??
ImageLoader简介
ImageLoader是 抽象类,其子类负责把 mach-o文件 实例化为 image(image :ImageLoader子类的实例), image大概表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等。
ImageLoader 抽象类的作用是将这些images 加载进内存。
dyld加载 主程序的过程(第一步)
instantiate ImageLoader for main executable
在dyld的_main() 中,把主程序的mach-o文件加载成image,并持有image,看code:
instantiateFromLoadedImage()
把.o 实例化为 image
addImage()
把image添加到images容器中,并更新内存分布
//看到了我们熟悉的 DYLD_PRINT_LIBRARIES,就知道为什么设置环境变量DYLD_PRINT_LIBRARIES后,会打印dylib的path了
dyld 加载其他的dylibs(第二步)
load any inserted libraries
dyld在link主程序之前还会动态的加载一些其他的库文件,
小记:DYLD_INSERT_LIBRARIES就是需要插入的dylib的路径(多个dylib以“:”为分隔符)。
DYLD_INSERT_LIBRARIES应用场景:如果我们想 hook 一个app,swizzle 某个类的方法,那么我们定义一个dylib,在其类的+load()中进行swizzle。那么如何让自定义dylib加入内存呢?就是需要设置需要加载的dylib的path到 DYLD_INSERT_LIBRARIES中就可以了,app在执行 主程序的main()之前就把它加载好了。
除此之外,环境变量DYLD_INSERT_LIBRARIES也是可以判断机器是否越狱,值为NULL说明未越狱,其他址一般会含有Path,就代表已经越狱了(希望app在启动时加载某些dylibs)
其中 loadInsertedDylib()中会调用当前文件的load()【不是NSObject中的load()】,
这里的load()的目的是: 加载mach-o并实例化为image,得到image
Load函数的实现为一系列的loadPhase*(loadPhase0、loadPhase1…)函数,主要可以分为这几个部分:
- 处理环境变量,生成各种搜索路径,去加载image。
- 如果该lib已经加载过,则利用share_cache中已经存在的imageloader实例。
- 如果该lib没有加载过,通过读取文件,将mach-o文件映射到内存中,生成imageloader的实例。
初始化dylibs 和 主程序(第五步)
// run all initializers
即调用 initializeMainExecutable();
注意下面的 两个run initialzers…
Q: 在自己 Class 的 +load 方法时能不能替换系统 framework(比如 UIKit)中的某个类的方法实现
A: 可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的。
其中,看到环境变量 DYLD_PRINT_STATISTICS:统计main()之前的时间消耗
初始化dylibs
run initialzers for any inserted dylibs
当初始化libSystem时,会间接的调用 _objc_init(),其中:_objc_init()就是 objc 和 runtime 的初始化入口,除了 runtime 环境的初始化外,_objc_init中绑定了新 image 被加载后的 callback(map_2_images & load_images)。
调用顺序:
libSystem_initializer() -> libdispatch_init() -> _os_object_init() -> _objc_init()
在_objc_init()中注册的callback
map_2_images() & load_images()
map_2_images()
对这个二进制中的ObjC相关的信息进行初始化,把class、category、method…一些信息注册起来。
load_images()
顺序:parent load-> load -> category load
执行所有 class 的 +load()【看看??的call_load_methods的注释】
可以看出:
由于 runtime 在 _objc_init()中 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理。
runtime 执行callback是 map_2_images 与 load_images,解释如下:
(1) map_2_images:All class registration and fixups are performed
(2) 至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,在这之后,runtime 的那些方法(动态添加 Class、swizzle 等等)才能生效
(3) load_images:Call all pending class and category +load methods.
可以在 +load()中 进行swizzle操作…
走一遍流程
load main exe -> shared cache -> load inserted dylibs -> link main exe -> link dylibs -> init ( 先dylibs 后 main exe ) -> 返回main exe的入口地址。
其中的init dylibs -> objc register callback when image load-> execute callback-> call_load_methods()
参考:
iOS 程序 main 函数之前发生了什么 · sunnyxx的技术博客
dyld与ObjC - 刘坤的技术博客
dyld中mach-o文件加载的简单分析 | mrh的学习分享
dyld源码分析-动态加载load | mrh的学习分享
原文:大专栏 从 dyld 到 runtime