就像PE是Windows的可执行文件类型、ELF是Linux的可执行文件类型一样,苹果也有自己的可执行文件类型————Mach-O
。
1. Mach-O 文件的基本格式
使用file
执行查看MachODemo
MachODemo: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]
MachODemo (for architecture armv7): Mach-O executable arm_v7
MachODemo (for architecture arm64): Mach-O 64-bit executable arm64
看到MachODemo
文件包含了两种架构,这种包含多个架构的文件成为FAT
文件。
使用MachOView
查看。
从上图可以了解到Mach-O
文件的结构,包括:Header
、Load Command
、Section
、Other Data
等。
2. Mach-O 头部
Header
的结构可以在苹果开源代码中找到,opensource.apple.com/source/xnu/…
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
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) */
- magic:
Mach-0
文件的魔数。FAT
为Oxcafebabe
,ARMV7
为Oxfeedface
,ARM64
为Oxfeedfacf
(Mac 是小端模式)。 - cputype, cpusubtype:
CPU
架构和子版本。 - fletype: 文件类型。常见有的
MH_OBJECT
(目标文件)、MH_EXECUTABLE
(可执行二进制文件)、MH_DYLIB
(动态库)。 - ncmds: 加载命令的数量。
- sizeofcmds: 所有加载命令的大小。
- flags:
dyld
加载需要的一些标记。其中,MH_PIE
表示启用地址空间布局随机化。 - reserved: 64位的保留字段。
在MachOView
中查看对应数据。
3. Load Command
Load Command
告诉操作系统应该如何加载文件中的数据,对系统内核加载器和动态链接器起指导作用。
Load Command
包含以下部分:
- LC_SEGMENT_64:定义一个段,加载后被映射到内存中,包括里面的节。
- LC_DYLD_INF0_ONLY:记录了有关链接的重要信息,包括在
_LINKEDIT
中动态链接相关信息的具体偏移和大小。ONLY
表示这个加载指令是程序运行所必需的,如果旧的链接器无法识别它,程序就会出错。 - LC_SYMTAB:为文件定义符号表和字符串表,在链接文件时被链接器使用,同时也用于调试器映射符号到源文件。符号表定义的本地符号仅用于调试,而已定义和未定义的
external
符号被链接器使用。 - LC_DYSYMTAB:将符号表中给出符号的额外符号信息提供给动态链接器。
- LC_LOAD_DYLINKER:默认的加载器路径。
- LC_UUID:用于标识
Mach-0
文件的ID,也用于崩溃堆栈和符号文件的对应解析。 - LC_VERSION_MIN_IPHONEOS:系统要求的最低版本。
- LC_SOURCE_VERSION:构建二进制文件的源代码版本号。
- LC_MAIN:程序的入口。
dyld
获取该地址,然后跳转到该处执行。 - LC_ENCRYPTION_INFO_64:文件是否加密的标志,加密内容的偏移和大小。
- LC_LOAD_DYLIB:依赖的动态库,包括动态库名称、当前版本号、兼容版本号。可以使用
otool -L xx
命令查看。 - LC_RPATH:
Runpath Search Palhs
,@rpath
搜索的路径。 - LC_FUNCTION_STARTS:函数起始地址表,使调试器和其他程序能很容易地看到一个地址是否在函数内。
- LC_DATA_IN_CODE:定义在代码段内的非指令的表。
- LC_CODE_SIGNATURE:代码签名信息
3.1 LC_SEGMENT_64
LC_SEGMENT_64
定义了一个64位的段
,当文件加载后映射到地址空间(包括里面的节
)。
64为段的定义
/*
* The 64-bit segment load command indicates that a part of this file is to be
* mapped into a 64-bit task's address space. If the 64-bit segment has
* sections then section_64 structures directly follow the 64-bit segment
* command and their size is reflected in cmdsize.
*/
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
- cmd:Load Command的类型。
- cmdsize:
Load Command
结构的大小 - segname:
段
的名字 - vmaddr: 映射到虚拟地址的偏移
- vmsize: 映射到虚拟地址的大小
- fileoff: 对应于当前架构文件的偏移
- filesize: 对应的
段
文件的大小 - maxprot: 段页面的最高内存保护
- initprot: 初始内存保护
- nsects: 包含的
节
的个数 - flags:
段
页面标志
系统将fileoff
偏移出filesize
大小的内容加载到虚拟内存vmaddr
处,大小为vmsize
,段
页面的权限由initprot
进行初始化。它的权限可以动态改变,但是不能超过maxprot
的值,例如__TEXT
初始化和最大权限都是可读/可执行/不可写。
示例的文件中包括以下4种段:
- __PAGEZERO: 空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对
NULL指针
的引用 - __TEXT: 代码段/只读数据段
- __DATA: 读取和写入数据的段
- __LINKEDIT: 动态链接器需要使用的信息,包括重定位信息、绑定信息、懒加载信息等。
3.2 SECTION64
段
里面可以包含不同的节(Section)
,定义如下:
/*
* A segment is made up of zero or more sections. Non-MH_OBJECT files have
* all of their segments with the proper sections in each, and padded to the
* specified segment alignment when produced by the link editor. The first
* segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header
* and load commands of the object file before its first section. The zero
* fill sections are always last in their segment (in all formats). This
* allows the zeroed segment padding to be mapped into memory where zero fill
* sections might be. The gigabyte zero fill sections, those with the section
* type S_GB_ZEROFILL, can only be in a segment with sections of this type.
* These segments are then placed after all other segments.
*
* The MH_OBJECT format has all of its sections in one segment for
* compactness. There is no padding to a specified segment boundary and the
* mach_header and load commands are not part of the segment.
*
* Sections with the same section name, sectname, going into the same segment,
* segname, are combined by the link editor. The resulting section is aligned
* to the maximum alignment of the combined sections and is the new section's
* alignment. The combined sections are aligned to their original alignment in
* the combined section. Any padded bytes to get the specified alignment are
* zeroed.
*
* The format of the relocation entries referenced by the reloff and nreloc
* fields of the section structure for mach object files is described in the
* header file <reloc.h>.
*/
struct 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 */
};
- sectname: 节的名字。
- segname: 段的名字。
- addr: 映射到虚拟地址的偏移。
- size: 节的大小。
- offset: 节在当前架构文件中的偏移。
- align: 节的字节对齐大小 n,计算结果为 2^n。
- reloff: 重定位人口的文件偏移。
- nreloc: 重定位人口的个数。
- nags: 节的类型和属性。
- reserved: 保留位。
下面我们来看看_TEXT
段和_DATA
段下面都有哪些节。
首先来看_TEXT
段。
- _text: 程序可执行的代码区域。
- _slubs: 间接符号存根,跳转到懒加载指针表。
- _stub_helper: 帮助解决懒加载符号加载的辅助函数。
- _objc_methname: 方法名
- _objc_classname: 类名
- _objc_methtype: 方法签名
- _cstring: 只读的C风格字符串,包含 OC的部分字符串和属性名。
_DATA 段下面的节如下:
- _nl_symbol_ptr: 非懒加载指针表,在
dyld
加载时会立即绑定值。 - Lla_symbol_ptr: 懒加载指针表,第1次调用时才会绑定值。
- got: 非懒加载全局指针表。
- _mod_init_func: constructor 函数
- mod_term_func: destructor 函数
- _cfstring: OC 字符串
- _objc_classlist: 程序中类的列表
- _objc_nlclslist: 程序中自己实现了+load 方法的类。
- -objc_protolist: 协议的列表。
- _objc_classrefs: 被引用的类列表。