未完待续~(Mach-O头文件格式以及dyld都能在开源苹果上查看到,链接为https://opensource.apple.com/)
概述
- 进程是特殊文件在内存中加载得到的结果。这种文件必须使用操作系统能够理解的格式,这样操作系统才能解析这个文件,建立所需要的依赖(例如库),初始化运行时环境并且开始执行。
- 在OS X中加载和链接程序的过程主要涉及两个对象: OS X内核和动态链接器。
- 首先内核加载指定的可执行文件,并检查mach_header,该结构在文件的开始处。内核验证可执行文件是一个有效的mach-O文件,并解释存储在标题中的加载命令。
- 然后内核将“加载命令”指定的动态链接器加载到内存中,并执行动态链接器。
- 动态链接器加载主程序链接的所有共享库,并绑定符号。然后调用入口点函数。
- 在构建时,静态链接器添加入口函数到主可执行文件中。该入口函数是从目标文件/usr/lib/crt1.o中获取。
- 该函数为内核设置运行时环境状态,并调用C++对象的静态初始化器,初始化OC Runtime,然后调用程序的主函数。
执行流程
- 内核通过“magic”判断文件的二进制格式,如果是支持的二进制格式,那么就会调用正确的加载器函数。
- Mach-O文件头中包含了非常详细的指令,这些指令在被调用时清晰地指导了如何设置并加载二进制数据。这些指令,或称为加载指令
- 有一些指令是由内核加载器直接使用的,其他指令时由动态链接器处理的。
- 在内核部分的指令负责新进程的基本设置——分配虚拟内存,创建主线程,以及处理任何可能的代码签名/加密的工作。
- 对于动态链接的可执行文件(大部分可执行文件都是动态链接的)来说,真正的库加载和符号解析的工作都是通过LC_LOAD_DYLINKER指令指定的动态链接器在用户态完成的。控制权会转交给链接器,链接器进而接着处理文件头中的其他加载指令。
- LC_SEGMENT命令是最主要的加载命令,这条命令指导内核如何设置新运行的进程的内存空间。这些“段”直接从Mach-O二进制加载到内存中。
- 对与每一个段,将文件中相应的内容加载到内存中:从偏移量为fileeoff出加载filesize字节到虚拟内存地址vmaddr出的vmsize字节。每一个段的页面都根据initprot进行初始化,initprot指定了如果通过读/写/执行位初始化页面的保护级别。段的保护设置可以动态改变,但不能超过maxprot中指定的值。
- 内核加载器的作用,主要是根据段的描述,初始化进程地址空间以及执行其他命令。
- Mach-O镜像中有很多”空洞”(即对外部的库和符号的引用)这些空洞要在程序启动时填补。这项工作就需要由动态链接器来完成。这个过程有时候也称为符号绑定(binding).
参考资料
- 深入解析Mac OS X&iOS操作系统
- Mach-O Programming Topics
https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/