0x00 摘要
在OS X
或者IOS
上运行一个程序时,dyld除了需要加载主要的执行程序之外,还需要加载需要的库文件以及库文件依赖的库文件。
1 | // instantiate ImageLoader for main executable |
这一条函数调用加载了需要运行的程序,具体详细分析可以参考dyld中mach-o文件加载的简单分析。
而在link
主程序之前还会动态的加载一些其他的库文件。例如DYLD_INSERT_LIBRARIES
。
1 | // load any inserted libraries |
loadInsertedDylib
的实现其实就是简单的调用load
函数。通过简单的分析load
函数,增强对dyld工作流程的理解。
0x01 为什么要分析Load函数
通过一个简单的图,可以看到Load
函数的调用者都是哪些函数。
除了前面提到的loadInsertedDylib
函数之外接触的比较多的就是dlopen
了,所以,可以看出load
是dyld
动态加载一个Mach-O
文件的重要接口。在动态加载一个Mach
-o文件的时候,最终都调用了load
这个API。
0x02 Load函数分析
Load
函数的实现为一系列的loadPhase*
函数,主要可以分为这几个部分
- 处理环境变量,生成各种搜索路径。
- 如果该
lib
已经加载过,则利用share_cache
中已经存在的imageloader
实例。 - 如果该
lib
没有加载过,通过读取文件,将mach-o文件映射到内存中,生成imageloader
的实例。
2.1 load
通过一幅图可以简单的理解load
函数的流程。load
函数主要做的这几件事情:
处理suffix字段。
通过
loadPhase0
函数从share_cache
中加载image
。如果
share_cache
中不存在image
,则再使用不同的参数调用loadPhase0
函数,通过open
函数读取文件并加载image
到内存中。函数调用结束后的内存管理。
1 | // 根据所有的环境变量生成路径,去加载一个ImageLoader |
2.2 loadPhase0函数
loadPhase0
函数逻辑比较简单
- 遍历
DYLD_ROOT_PATH
环境变量,生成加载路径,调用loadPhase1
。 - 如果不存在
DYLD_ROOT_PATH
环境变量,则使用原始的路径
1 | // try root substitutions |
2.3 loadPhase1
loadPhase
主要处理内容:
- 通过
LD_LIBRARY_PATH
参数组成的所有路径,通过loadPhase2
尝试加载image
。 - 当无法通过
LD_LIBRARY_PATH
获取image
时,则通过DYLD_FRAMEWORK_PATH
与DYLD_LIBRARY_PATH
组成的路径,通过loadPhase2
尝试加载image
。 - 如果上面两个流程都无法加载到image则通过原始路径通过
loadPhase3
尝试加载image
。 - 如果依然无法加载到image则通过
DYLD_FALLBACK_FRAMEWORK_PATH
环境变量,组成路径最后尝试加载image
。
这里要注意一下,因为不同的分支使用的Phase
函数有可能是不同的。同时该函数也确定了环境变量在动态加载时的优先级。
1 | static ImageLoader* loadPhase1(const char* path, const char* orgPath, const LoadContext& context, std::vector<const char*>* exceptions) |
2.4 loadPhase2,loadPhase3,loadPhase4
这两个函数纯粹的实现了路径修改的逻辑,通过不同的方式去生成最终的加载路径,逻辑与loadPhase0
基本类似,有兴趣可以自行查看代码。
2.5 loadPhase5
loadPhase5
根据参数exceptions
的不同形成了两个不同的分支。
- loadPhase5load:通过读取文件,加载文件到内存中,实例化
ImageLoader
。 - loadPhase5check: 通过遍历已经加载的
ImageLoader
的容器,获取已经加载的ImageLoader
。
1 | // open or check existing |
loadPhase5check
的逻辑非常简单,就是遍历容器,取出相同名字的imageLoader对象。有兴趣的可以自己查看loadPhase5check。
2.6 loadPhase5load
这里是真正的加载逻辑
- 防止Image改名,在Image的容器里面遍历,检查是否已经加载
- 在
SharedCache
寻找是否存在Image的缓存,如果存在的使用ImageLoaderMachO::instantiateFromCache
来实例化ImageLoader
。 - 如果上面两个都没有找到的话,就通过
loadPhase5open
打开文件,并读取到内存。
1 | static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, std::vector<const char*>* exceptions) |
2.7 loadPhase5open与loadPhase6
loadPhase5open
只是一个简单的封装。
1 | //根据路径打开文件 |
做了错误提示之后,调用了loadPhase6
。
1 | static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* path, const LoadContext& context) |
- 做了Fat格式的检测,子类型文件提取。
- 检测Mach-O类型,只有
MH_EXECUTE
,MH_DYLIB
,MH_BUNDLE
三种文件才可被动态加载。 - 通过
ImageLoaderMachO::instantiateFromFile
生成ImageLoader
的实例。
0x03 小结
Load
的函数调用流程就是一系列的loadPhase*函数的调用,在load
最后都会通过ImageLoader的构造函数,实例化ImageLoader,接下来 就需要分析ImageLoader的几个不同的构造函数。