7. Mach-O 文件格式

196 阅读7分钟

就像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文件的结构,包括:HeaderLoad CommandSectionOther 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 文件的魔数。 FATOxcafebabe, ARMV7Oxfeedface, ARM64Oxfeedfacf (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: 被引用的类列表。