Mach-O文件格式

众所周知,我们用Xcode构建一个程序过程中,会把源文件转换成一个可执行文件,这个可执行文件中包含的字节码会被CPU执行。
就像Windows上的PE和Linux上的ELF,OS X系统可执行文件的格式为Mach-O,接下来简单谈谈Mach-O。

先来看看苹果官方文档对Mach-O结构组成的描述

794D3F35-0A43-45E0-9EA4-CA2637954096

由图可知,Mach-O主要分为3大部分:
1.Header:用于快速确认一些关于当前文件对应的处理器,文件类型,Load connamd个数基本信息
2.Load commands:紧跟在Header之后,用于告诉加载器怎样处理二进制数据
3.Data:包含Segment中的具体信息

Headers

headers的定义在开源内核代码中可以找到(/usr/include/mach-o/loader.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct mach_header {
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 */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
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 */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
/*

根据mach_header和mach_header_64的定义,可以很明显的看出Headers的主要作用就是迅速确认当前文件的文件类型运行环境等。

这里找个例子,首先创建一个helloworld.c文件,

1
2
3
4
5
6
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}

保存后在终端后再当前目录下输入代码
clang helloworld.c
如果我们没有指定名字的话,编译器会默认把helloworld.c文件编译成名为a.out的二进制文件

我们用MachOView打开这个二进制文件,来看看headers中的详细信息(这里可以通过otool -l a.out来查看,但看起来比较吃力)

4C4E0C28-68AB-4D4E-9B38-8A520350E25F

  • Magic:魔数,用于快速确认该文件用于64位还是32位,32位是0xfeedface,64位是0xfeedfacf
  • Cpu Type:CPU类型,
  • Cpusub Type:对应的具体类型,比如arm64、armv7
  • File Type:文件类型,比如可执行文件、库文件、Dsym文件,图中代表可执行文件,如例子中的MH_EXECUTE为可执行文件
  • MH_PIE:加载程序在随机的地址空间,只在MH_EXECUTE中使用
  • MH_TWOLEVEL:两级空间名称
  • MH_DYLDLINK:连接器linker
  • dyld:动态链接器,他是苹果开源的一个项目,可以在这里下载,当内核执行LC_DYLINK时,连接器会启动,查找进程所依赖的动态库,并加载到内存中。dyld是一个用户态进程,他不属于内核部分,而是作为一个单独的开源项目被苹果维护(但是属于Darwin部分)
  • 随机地址空间ASLR:进程每一次启动,地址空间都会简单的随机化,否则,如果程序每一次启动的虚拟内存镜像都是一致的,容易被黑客采取重写内存的方式来破解。

Load Commands

1
2
3
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */

Load Commands直接跟在headers后面,加载headers之后通过解析LoadCommands来加载接下来的数据,LC_SEGMENT_64和LC_SEGMENT是加载的主要命令,他们负责指导内核设置进程的内存空间

F784BC98-6C5A-4723-8CE9-77A3042481E7

关于具体如何通过load_commands的cmd字段来加载mach-o 文件,有兴趣可以去/usr/include/mach-o 下面看看源码,这里只要知道根据cmd字段的类型不同,使用了不同的函数来加载,基本看语义都可以解决。

这里大概讲一下前面这些段分别代表的含义,_TEXT对应的就是代码段,_DATA对应的是可读或可写的数据,_LINKEDIT是支持dyld的,里面包含符号表等数据。

Section

一个可执行文件包含多个段,也就是多个 section。可执行文件不同的部分将加载进不同的section,并且不同的section会转换进不同的segment中。这个概念对于所有的可执行文件都是成立的。

参考文章:https://objccn.io/issue-6-3/

文章目录
  1. 1. Headers
  2. 2. Load Commands
  3. 3. Section
|