Mach-O文件结构

一、概述

运行时架构(runtime architecture)是针对软件运行环境定义的一系列规则,包括但不限于:

  1. 如何为代码和数据(code and data)排位;
  2. 在内存中怎样去加载或者追踪程序的部分代码;
  3. 告诉编译器应该如何组装代码;
  4. 如何调用系统服务,如加载插件;
  5. 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

  1. 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 源码解读

  1. Load Command 由多个 command 组成;
  2. command 主要有两种类型:指向具体数据、不指向具体数据;
  3. 代码层面上 load_command 结构体相当于基类,很少被使用;

4. Load Command 和 segment/section 的关系

上文中讲到 Load Command 主要分为指向数据实体和不指向数据实体两种类型。

不指向数据实体的 command 主要作用是为 dyld 提供信息,而指向数据实体的 command 才是 command 和 segment/section 关系的体现;

如 LC_SEGMENT 指向具体的 segment,这个 segment 的实体部分就是 Mach-O 文件的第三部分,主要内容是代码和数据;

延伸官方的图片,绘制如下

image.png

六、常见的 segment

常见的 segment 如下:

  1. __PAGEZERO;
  2. __TEXT;
  3. __DATA;
  4. __DATA_CONST;
  5. __LINKEDIT;

所以,不需要纠结有哪些 segment,只需要关注几点:

command 分为指向具体的数据和不指向具体数据两种类型;
section 指向 data 中一团一团的数据,segment 整合 section,在虚拟内存的加载时,屏蔽掉分页对 section 位置的影响;

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享