『底层探索』10 - Mach-O 文件分析
不同的系统下,可执行文件的格式也是不同的。我们今天探索的是 iOS 的 Mach-O 文件格式。
前言
我们编写的程序经过编译后会生成一个可执行文件,如果我们想要运行这个可执行文件,也就是运行我们编写的程序,离不开操作系统的支持。
那么对于操作系统来说,肯定要知道可执行文件的格式。可执行文件格式跟操作系统和编译器密切相关,不同的系统平台下有不同的格式。
Windows 下是 PE(Portable Executable)格式,Linux 下是 ELF(Executable Linkable Format)格式。iOS 和 Mac OS 是 Mach-O 格式的。本文即将探索的是 Mach-O
。
在 Mac OS 系统中,我们可以通过 file 来查看文件格式。
1 | file AppLoad |
Mach-O 简介
Mach-O 是 Mach Object 文件格式的缩写。它是一种用于可执行文件、目标代码、动态库的文件格式。作为 a.out
格式的替代,Mach-O 提供了更强的扩展性。并提升了符号表中信息的访问速度。
可执行格式决定了二进制文件中的代码和数据读入内存中的顺序,代码和数据的顺序会影响内存的使用和分页活动,因此会直接影响程序的性能。
一个 Mach-O 二进制文件是以 Segment(段) 的方式组织的。每个 Segment 包含 1 个或多个 Section (节)。Segment 在内存中是从 page boundary(页边界)开始的,Section 不是页面对齐的。
可能有同学不知道内存分页,这里简单说下。内存分页是一种内存管理技术,用于控制如何共享计算机或虚拟机的内存资源。操作系统会将虚拟内存和物理内存都划分为多个同样大小(4KB)的单元,每个单元称为页。
Segment 的大小由它包含的所有 Section 加起来的字节数,然后向上取整到下一个页边界。所有 Segment 的大小始终是 4KB 的整数倍。
Mach-O 中的 Segment 和 Section 是根据用途进行命名的。
- Segment 命名是
__
后跟全大写字母,如 __TEXT。 - Section 命名是
__
后跟全小写字母,如 __text。
Mach-O 文件结构
Mach-O 文件主要由 3 部分组成(如图所示)
- Header,用于标识二进制文件为 Mach-O 可执行文件,除此之外还包含二进制文件的基本信息,包括
- 指令集架构、CPU 类型和详细类型、文件类型、加载命令的数量、大小及标志位等。
- Load Commands, 这部分内容紧跟在 header 后面,是用于描述如何加载后面 Data 的数据,包括
- 内存布局信息、链接信息、符号表的位置、共享库的名称等
- Data,是二进制文件中的数据部分,是以 Section 的方式组织的,是特定类型的代码或数据。
- 程序源代码编译后的机器指令一般放在代码段,也就是
__TEXT
,这个 Segment 是只读的。 - 全局变量和局部静态变量数据一般放在数据段,也就是
__DATA
,这个 Segment 可读可写。
- 程序源代码编译后的机器指令一般放在代码段,也就是
用 MachOView 查看可执行文件
下图是我们通过 MachOView
打开一个 App 的可执行文件得到的结构图。
__TEXT 中的主要 section 如下:
Section | Description |
---|---|
__text | The compiled machine code for the executable |
__const | The general constant data for the executable |
__cstring | Literal string constants (quoted strings in source code) |
__picsymbol_stub | Position-independent code stub routines used by the dynamic linker (dyld). |
__DATA 中的主要 section 如下
Section | Description |
---|---|
__data | Initialized global variables (for example int a = 1; or static int a = 1;). |
__const | Constant data needing relocation (for example, char * const p = “foo”;). |
__bss | Uninitialized static variables (for example, static int a;). |
__common | Uninitialized external globals (for example, int a; outside function blocks). |
__dyld | A placeholder section, used by the dynamic linker. |
__la_symbol_ptr | “Lazy” symbol pointers. Symbol pointers for each undefined function called by the executable. |
__nl_symbol_ptr | “Non lazy” symbol pointers. Symbol pointers for each undefined data symbol referenced by the executable. |
可执行文件中的代码结构表示
mach_header_64
在 usr/include/mach-o/loader.h
文件中,我们可以找到 mach_header
和 mach_header_64
的定义。64位架构比 32 位架构少了一个 reserved
字段。
1 | struct mach_header_64 { |
我们可以通过 magic 的值来快速识别是哪个架构,使用的是大端还是小端。
1 | #define MH_MAGIC 0xfeedface /* the mach magic number */ |
filetype 用于表示 mach-o 文件的类型。我们看看定义了哪些类型, 我摘录了部分类型,完整的请查阅 loader.h
文件。
1 |
load_command
每个 load command 有两个属性,一个是 type,一个是 total size。
1 | struct load_command { |
根据 load command 的 define,可以得到如下的类型,我摘录了部分类型,完整的请查阅 loader.h
文件。
1 |
|
我们再看看 LC_SEGMENT 类型 Load Command 的结构体表示。
1 | struct segment_command_64 { /* for 64-bit architectures */ |
Section
对于每个 Section,是这样表示的,下面是 64 架构的 Section。
1 | struct section_64 { /* for 64-bit architectures */ |
参考资料
后记
我是穆哥,卖码维生的一朵浪花。我们下回见。
文章作者:muhlenXi
原始链接:https://muhlenxi.com/2020/09/28/084-mach-o/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!