一、什么是Mach-O文件?

Mach-O 即 Mach Object,它是一种文件格式(Mac OS 二进制可执行文件)。

二、Mach-O 文件内容详解

  • Mach-O 二进制文件由段(segment)组成,可通过 MachOView 查看。
  • 一个segment由零个或者多个section组成,每个section里面会放置不同的数据或代码。
  • segment需要页对齐(Mac OS 页大小4k,iOS 页大小16k),section不一定是页面对齐的。
  • segment、section命名规则:
    (1)segment 名称采用双下划线开头 +全字母大写(如 __DATA);
    (2)section 名称采用双下划线开头+全字母小写(如 __data);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

常见segment的含义:
__TEXT:包含可执行代码和其他只读数据
__DATA:包含可所有读写内容,如:全局变量,静态变量等
__LINKEDIT:加载程序(动态链接器)使用的元数据,如:符号,字符串和重定位的表项

常见__TEXT段中section的含义:
__TEXT,__text: 只放置可执行代码(机器码)
__TEXT,__cstring:字符串常量(最终构建产品时会删除其中重复项)
__TEXT,__const:初始化的const变量

常见__DATA段中section的含义:
__DATA,__data:初始化的可变变量,如C字符串和数据数组
__DATA,__la_symbol_ptr:懒符号指针
__DATA,__objc_selrefs:引用的objc方法
__DATA,__objc_classrefs:引用的objc类
__DATA,__objc_superrefs:引用的objc超类
__DATA,__objc_classlist:objc类列表
__DATA,__objc_nlclslist:objc非懒加载类列表
__DATA,__objc_catlist:objc分类列表
__DATA,__objc_nlcatlist:objc非懒加载分类列表
__DATA,__objc_protolist:objc协议列表
__DATA,__objc_protorefs:引用的objc协议

三、Mach-O 文件结构

一个Mach-O文件一般会包含三个主要区域:Header、Load commands、Data。

1、Header 结构

每个Mach-O文件的开始是一个head structure用来标识这个文件是一个Mach-O文件。header中还包含了其他基本文件类型、目标体系结构等信息。

我们先简单看看mach_header(本文以64位为准)结构,通过usr/include/mach-0/loader.h我们可以找到mach_header_64结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

struct mach_header_64 {
uint32_t magic; /* mach 魔数标识符 */
cpu_type_t cputype; /* cpu 说明 */
cpu_subtype_t cpusubtype; /* cpu子类型说明 */
uint32_t filetype; /* 文件类型 */
uint32_t ncmds; /* 加载命令条数 */
uint32_t sizeofcmds; /* 所有load commands大小 */
uint32_t flags; /* 标志 */
uint32_t reserved; /* 保留字段 */
};

/* mach_header_64 的魔数字段的常量(64位架构) */
#define MH_MAGIC_64 0xfeedfacf /* 64位 mach 魔数 */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

(1)cputypecpusubtype 字段常量值定义在 usr/include/mach/machine.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

#define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */

#define CPU_TYPE_ARM ((cpu_type_t) 12)
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)


/*
* ARM subtypes
*/
#define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) /* ARMv7-A and ARMv7-R */


/*
* ARM64 subtypes
*/
#define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0)


cputype、cpusubtype示例:

cputype 12 //表示arm架构CPU
cputype 9 // 表示armv7


cputype 16777228 //表示arm64架构CPU,(CPU_TYPE_ARM | CPU_ARCH_ABI64) = (12 | 0x01000000) = 0x0100000C, 0x0100000C转换成十进制等于16777228
cpusubtype 0 // 表示arm64通用架构

(2)filetype字段常见的文件类型常量

1
2
3
4
5
6
7

#define MH_OBJECT 0x1 /* 源码编译后的文件,文件扩展名.o */
#define MH_EXECUTE 0x2 /* 二进制可执行文件 */
#define MH_DYLIB 0x6 /* 动态库 */
#define MH_DYLINKER 0x7 /* 动态链接器 */
#define MH_BUNDLE 0x8 /* 运行时加载的代码,文件扩展名.bundle */
#define MH_DSYM 0xa /* 带有调试信息(对应二进制文件的符号信息)的文件dsym */

(3)flags 字段常见的常量

1
2
3
4
5
6
7
8
9
10

#define MH_NOUNDEFS 0x1 /* 目标文件中没有未定义的引用 */
#define MH_DYLDLINK 0x4 /* 对象文件是动态链接器的输入,无法再次静态编辑链接 */
#define MH_TWOLEVEL 0x80 /* 镜像使用两级命名空间 */
#define MH_WEAK_DEFINES 0x8000 /* 最终链接的镜像包含外部弱符号*/
#define MH_BINDS_TO_WEAK 0x10000 /* 最终链接的镜像使用弱符号 */
#define MH_PIE 0x200000 /* 设置此位时, 操作系统将会在随机地址处加载主可执行文件。仅用于MH_EXECUTE文件类型。 */

示例:
flags 0x00218085 // 0x1+0x4+0x80+0x8000+0x10000+0x200000 = 0x00218085

2、Load commands 结构

1
2
3
4
5

struct load_command {
uint32_t cmd; /* 加载命令类型 */
uint32_t cmdsize; /* 命令大小 */
};

(1)加载命令的cmd字段常见的常量值:

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

#define LC_REQ_DYLD 0x80000000

#define LC_SEGMENT 0x1 /* 文件映射的段 */
#define LC_SYMTAB 0x2 /* 链接编辑stab符号表信息 */
#define LC_SYMSEG 0x3 /* 链接编辑gdb符号表信息(已过时) */
#define LC_THREAD 0x4 /* 线程 */
#define LC_UNIXTHREAD 0x5 /* Unix线程(包括堆栈) */
#define LC_DYSYMTAB 0xb /* 动态链接器符号表信息 */
#define LC_LOAD_DYLIB 0xc /* 加载动态链接共享库 */
#define LC_LOAD_DYLINKER 0xe /* 加载动态链接器 */
#define LC_TWOLEVEL_HINTS 0x16 /* 两级命名空间查找提示 */
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) /* 加载允许丢失的动态链接共享库(所有符号均弱导入)*/
#define LC_SEGMENT_64 0x19 /* 64位文件映射的段 */
#define LC_UUID 0x1b /* uuid */
#define. LC_RPATH (0x1c | LC_REQ_DYLD) /* 运行路径添加 */
#define LC_CODE_SIGNATURE 0x1d /* 本地代码签名 */
#define LC_LAZY_LOAD_DYLIB 0x20 /* 将dylib的加载延迟到首次使用 */
#define LC_ENCRYPTION_INFO 0x21 /* 加密段 information */
#define LC_DYLD_INFO 0x22 /* 压缩的dyld信息 */
#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* 仅压缩的dyld信息 */
#define LC_VERSION_MIN_MACOSX 0x24 /* 适用于MacOSX的最低版本 */
#define LC_VERSION_MIN_IPHONEOS 0x25 /* 适用于iOS的最低版本 */
#define LC_FUNCTION_STARTS 0x26 /* 函数起始地址的压缩表 */
#define LC_MAIN (0x28|LC_REQ_DYLD) /* 代替 LC_UNIXTHREAD */
#define LC_DATA_IN_CODE 0x29 /* __text中的非指令表 */
#define LC_SOURCE_VERSION 0x2A /* 用于构建二进制文件的源代码版本 */
#define LC_ENCRYPTION_INFO_64 0x2C /* 64位加密段信息 */
#define LC_BUILD_VERSION 0x32 /* 适用于构建平台OS最低版本 */

具体的命令结构这里就不多做介绍了,请自行查看:usr/include/mach-0/loader.h

四、通用二进制可执行文件

五、常见应用场景

六、参考资料

完整优秀版请移步小专栏:
Mach-O文件初识

更多好文推荐,扫描下方的二维码,关注《iOS开发秘籍》公众号,免费解锁完整版
iOS开发秘籍

本文内容中部分来自网络,后续会持续更新完善。欢迎一起学习交流!

如需转载,请注明出处

Mach-O文件初识