本文将会结合苹果官方文档,来详细解读Mach-O
本文会说明Mach-O
的定义,及设计原理,其中与链接相关的部分原理不会在本文说明,后面会有专门的链接文章,结合demo操作和Mach-O
来详细说明。
那么通过本文可以收获什么?
带着问题学习
- Mach-O是什么(定义)?
- Mach-O的内容是什么?
- 为什么要有“段”?
定义
Mach-O
又称Mach object
,是不同运行时规则下的 可执行文件的文件类型。
关于编译和链接会有另一篇文章来结合Mach object
具体说明文件类型
1
2
3
4
5MH_OBJECT 0x1 目标文件
MH_EXECUTE 0x2 可执行文件
MH_DYLIB 0x6 动态库
MH_DYLINKER 0x7 动态连接器
MH_DSYM 0xa 存储二进制文件符号信息,用于Debug分析
目标文件和可执行文件的区别
假设现在有a.m,b.m两个文件,经过编译之后,会分别产生a.o,和b.o两个文件,此时的a.o和b.o就是目标文件,当经过链接之后,或产生新的文件c.out,此时的c文件就是可执行文件。
内容
已mac 的应用 计算器为例,找到计算器app,右键显示包内容,contents->MacOS->Calculator,使用MachOView打开,
注意看第一条,Executable
,说明是可执行文件。
对于Mach-O
的定义可查看mach-o/loader.h
的定义
Mach-O 文件包含三个区域
Header
Mach-O Header:包含字节顺序,magic,cpu 类型,加载指令的数量等
先来看下定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/*
* 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 */
1. cafebade :跨处理器架构的通用格式
2. feedface :32
3. feadfacf :64
cpu_type_t cputype; /* cpu specifier */CPU类型,比如 arm
cpu_subtype_t cpusubtype; /* machine specifier */对应的具体类型,比如arm64、armv7
uint32_t filetype; /* type of file */ 文件类型,比如可执行文件、库文件、Dsym文件,见<mach-o/loader.h>
uint32_t ncmds; /* number of load commands */加载命令条数
uint32_t sizeofcmds; /* the size of all the load commands */ 命令总size
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
再来看图
Load Commands
Load Commands:包含很多内容的表,包括区域的位置,符号表,动态符号表等。每个加载指令包含一个元信息,比如指令类型,名称,在二进制中的位置等。
1 |
|
其中__TEXT segment
包含被执行的代码以只读和可执行的方式映射。1
2
3
4__text section 包含编译后的机器码。
stubs 和 stub_helper 是给动态链接器 dyld 使用,可以允许延迟链接。
__cstring 可执行文件中的字符串。
__const 不可变的常量。
__DATA segment
以可读写和不可执行的方式映射,里面是会被更改的数据。
1 | __nl_symbol_ptr 非延迟指针。可执行文件加载同时加载。 |
section 的定义1
2
3
4
5
6
7
8
9
10
11
12
13struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
其中 reloff
和nreloc
会表明重定向入口信息
更多 section 类型介绍可以查看苹果文档: OS X Assembler Reference
Data
最大的部分,包含了代码,数据,比如符号表,动态符号表等。
1 |
|
为什么要分段
- 数据和指令可以被映射到两个不同的虚拟内存区域。数据区域是可读写的,指令区域是只读可执行。那就可以方便分别设置这两个区域的操作权限。
- 两个区域分离,有助于提高缓存的命中率。(提高了程序的局部性)
- 最主要是,系统运行多个该程序的副本时,它们指令是一样的,那内存只需要保存一份指令部分,可读写的数据区域进程私有。是不是节约了内存,动态链接那篇也是讲这样的方式来节约内存。