一、概述
运行时架构(runtime architecture)是针对软件运行环境定义的一系列规则,包括但不限于:
- 如何为代码和数据(code and data)排位;
- 在内存中怎样去加载或者追踪程序的部分代码;
- 告诉编译器应该如何组装代码;
- 如何调用系统服务,如加载插件;
- Mac 系统支持多种运行时架构,但是内核可以直接读取的可执行文件只有一种:Mach-O。因此,mac 的运行时架构也被命名为:Mach-O Runtime Architecture;因此,Mach-O 是一种存储标准,用于 Mach-O runtime architecture 架构中对程序的磁盘存储;
Mach-O 是 mach object 的缩写,在 -objc解决分类不加载的问题的官方文档中,明确指出所有的源文件都会被转化成一个 objcet,只不过最后经过链接操作,工程或被转化成静态库、动态库或者是可执行文件(类型不同的 mach-O);
Mach-O 文件分为三大部分:
mach-header;
load commands;
segment and section;
二、mach_header
header 位于 Mach-O 文件的头部,其作用是:
识别 Mach-O 的格式;
文件类型;
CPU 架构信息;
64 位 header 结构体如下:
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
复制代码
1. magic
一个整数,用于标识该文件为 Mach-O 类型。可以理解成多种类型的文件会被加载,而该 Image 如果值为特定的值,则该 Image 为 Mach-O 类型。
另外,如果该 Mach-O 的架构和编译该 Mach-O 文件的 CPU 字节序(大小端)一致,则使用 MH_MAGIC,相反则使用 MH_CIGAM;
如 dyld 源码中使用magic这个字段来判断是否为 Mach-O 文件:
2. cputype
一个整数,标志该文件将被使用在何种 CPU 架构上;
3. subtype:
arm 架构下有 arm_v7、arm_all 之类的区别,而 subtype 就是表示这个,部分定义如下:
4. filetype
filetype 就是我们熟知的 Mach-O 文件的类型,比如动态库、主工程生成可执行文件、bundle 等等
5. ncmds && sizeofcmds
表示 header 之后的 Load Command 的段数和大小
三、Load Command
- Load Command 作用概述
其作用有:
Mach-O 文件的布局;
这一点和 Mach-O 本身的设计有关,Load Command 本身不包含数据,Load Command 中的 segment 和section 类似于一个指针的作用,其描述(指向)的 segment 或者 section 实体才是真正存储数据或代码的地方。
链接信息;
这一点主要是通过几个段(LC_SYMTAB、LC_LOAD_DYNLINER __Linkedit 等) 来描述符号表相关的信息,链接器位置等。dyld 通过这些信息进行符号表的 rebase 和 bind 等操作;
Mach-O 文件在虚拟内存中的初始化布局;
这一点应该跟 __PAGEZERO 有关,具体??待补充
符号表的位置;
是链接信息的一部分,主要由 LC_SYMTAB、LC_DYSYMTAB、__LINKEDIT 来描述符号表、动态符号表、字符串表的位置;
程序 main 线程的初始执行状态;
这里指的应该是 LC_MAIN 段描述的程序的入口函数位置;
主工程所导入的共享库信息;
这一点就不多说了,在 machOView 中可以直观的看到,也可以通过 otool 指令来获取;
2. Load Command 的理解
以上是官方文档对 Load Command 的表述,这里加上自己的理解。
Load Commands 由多个 command 组成,其大小由 command 的数量和 command 的 size 决定。Load Commands 更多的是一个统称的概念;
如果 Load Command 按照是否指向数据实体来分类,分为两种:
指向具体数据段
该种 command 存储了一些信息,且指向 Data 部分的具体数据。
如 LC_SEGMENT(segment_command) 指向存放函数代码的 __TEXT 段,程序员打交道最多的 __DATA / __DATA_CONST 段;
再比如 LC_CODE_SIGNATURE 指向 Data 中的签名数据:
再比如动态链接相关的 __LINKEDIT 对应的 command 指向 Data 区域的 __LINKEDIT 段;
不指向具体数据段
该种 command 一般不包含数据实体,只起到描述性作用。
不像 LC_SEGMENT 一般会指向一个 SEGMENT,比如 __TEXT。而 LC_MAIN、LC_RPATH 等这些 command 都只是告诉 dyld 一些信息,不指向具体的数据段。常见的 command 如下在会问会有列举;
3. Load Command 源码解读
- Load Command 由多个 command 组成;
- command 主要有两种类型:指向具体数据、不指向具体数据;
- 代码层面上 load_command 结构体相当于基类,很少被使用;
4. Load Command 和 segment/section 的关系
上文中讲到 Load Command 主要分为指向数据实体和不指向数据实体两种类型。
不指向数据实体的 command 主要作用是为 dyld 提供信息,而指向数据实体的 command 才是 command 和 segment/section 关系的体现;
如 LC_SEGMENT 指向具体的 segment,这个 segment 的实体部分就是 Mach-O 文件的第三部分,主要内容是代码和数据;
延伸官方的图片,绘制如下
六、常见的 segment
常见的 segment 如下:
- __PAGEZERO;
- __TEXT;
- __DATA;
- __DATA_CONST;
- __LINKEDIT;
所以,不需要纠结有哪些 segment,只需要关注几点:
command 分为指向具体的数据和不指向具体数据两种类型;
section 指向 data 中一团一团的数据,segment 整合 section,在虚拟内存的加载时,屏蔽掉分页对 section 位置的影响;