IosBX's Blog

IosbX's Blog
记录自己的每一次进步
  1. 首页
  2. iOS
  3. 正文

Mach-O文件结构分析

2026年 5月 19日 119点热度 1人点赞 0条评论

一.概述

Mach-O(Mach Object) 是 Apple 操作系统(包括 macOS、iOS、iPadOS、watchOS 和 tvOS)上可执行文件、目标代码、共享库(.dylib)和动态加载器(dyld)的专属文件格式,可由 Clang / LLVM、Swiftc、GCC、Rustc 编译。

常见 Mach-o 文件类型

  1. Executable:可执行文件(.out .o)

  2. Dylib:动态链接库

  3. Bundle:不能被链接,只能在运行时使用dlopen()加载

  4. Image:包含Executable、Dylib和Bundle

  5. Framework:包含Dylib、资源文件和头文件的文件夹

二.Mach-O文件结构

使用MachOView看Mach-O,效果如下:

Mach-O文件中包含三个主要的部分:

  1. Header(头部):标明文件的基本信息(如架构、文件类型)

  2. Load Commands(加载命令):指导操作系统如何加载和解析这个文件

  3. Data(数据区):存放具体的代码(Code)和数据(Data)

其次还有:

  1. Function Starts (函数起始地址表):列出了二进制文件中所有函数的起始偏移量

  2. Symbol Table (符号表):存储了程序中定义和引用的所有符号

  3. Data in Code Entries (代码中的数据项):记录在代码段(__TEXT)中混杂的非指令数据(即数据常量)的位置和类型

  4. Dynamic Symbol Table(动态符号表):对 Symbol Table 的索引,区分函数是内部定义还是外部引用

  5. String Table(字符串表):长字符串缓冲区

  6. Code Signature (代码签名):存放开发者证书、签名哈希值以及权限声明(Entitlements)

三.Header

Header 位于文件的最开始。它决定了系统如何看待这个文件。在底层(<mach-o/loader.h>)中,64位架构的头部结构体定义为 mach_header_64。

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
	uint32_t	magic;		/* 32位或者64位,系统内核用来判断是否是mach-o格式 */
	int32_t		cputype;	/* CPU架构类型 */
	int32_t		cpusubtype;	/* CPU的具体类型 */
	uint32_t	filetype;	/* mach-o文件类型 */
	uint32_t	ncmds;		/* LoadCommands加载命令的条数 */
	uint32_t	sizeofcmds;	/* 全部LoadCommands加载命令的大小 */
	uint32_t	flags;		/* 标志位标识二进制文件支持的功 */
};
/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
	uint32_t	magic;		/* 32位或者64位,系统内核用来判断是否是mach-o格式 */
	int32_t		cputype;	/* CPU架构类型 */
	int32_t		cpusubtype;	/* CPU的具体类型 */
	uint32_t	filetype;	/* mach-o文件类型 */
	uint32_t	ncmds;		/* LoadCommands加载命令的条数 */
	uint32_t	sizeofcmds;	/* 全部LoadCommands加载命令的大小 */
	uint32_t	flags;		/* 标志位标识二进制文件支持的功能 */
	uint32_t	reserved;	/* 保留字段 */
};

常见的文件类型有以下几种:

#define MH_OBJECT 0x1 /* 中间目标文件 */
#define MH_EXECUTE 0x2 /* 标准可执行文件 */
#define MH_DYLIB 0x6 /* 动态链接库 */
#define MH_DYLINKER 0x7 /* 动态链接器 */
#define MH_DSYM 0xa /* 调试符号文件(.dSYM),用于debug分析 */
#define MH_KEXT_BUNDLE 0xb /* 内核扩展模块 */

四.Data

Load Commands在 Mach-O 文件加载解析时,会被内核(XNU)和动态链接器(dyld)调用。这些指令都采用 Type-Size-Value 这种格式,即:32 位的 cmd 值(表示类型),32 位的 cmdsize 值(32 位二级制位 4 的倍数,64 位位 8 的倍数),以及命令本身(由 cmdsize 指定的长度)。内核加载器使用的命令可以参看 xnu来学习,其他命令则是由动态链接器处理的。

在 Mach-O 中,数据并不是杂乱无章的,而是采用了两级管理结构:Segment(段) 和 Section(节)。Segment 是一个较大的内存管理单位,而每个段内部会包含一个或多个具体的 Section。Segment 是内存对齐和权限控制的最小单位,而 Section 则是根据数据类型进行的更细致的分类。

Segment 的数据结构如下:

struct segment_command_64 {      /* 适用于 64 位架构的段命令结构体 */
	uint32_t	cmd;            /* 加载命令类型,对于当前结构体固定为 LC_SEGMENT_64 */
	uint32_t	cmdsize;        /* 当前加载命令的总大小,包含紧随其后的所有 section_64 结构体的大小 */
	char		segname[16];    /* 段的名称(固定 16 字节,例如 __TEXT, __DATA, __LINKEDIT) */
	uint64_t	vmaddr;         /* 该段在虚拟内存中的起始目标地址 */
	uint64_t	vmsize;         /* 该段在虚拟内存中所占用的内存大小 */
	uint64_t	fileoff;        /* 该段在磁盘文件中的偏移量(从文件开头算起) */
	uint64_t	filesize;       /* 该段在文件中实际占用的大小(需要从文件中映射的数据量) */
	int32_t		maxprot;        /* 该内存段允许的最高虚拟内存保护权限(如读、写、执行权限组合) */
	int32_t		initprot;       /* 该内存段在初始化加载时的默认虚拟内存保护权限 */
	uint32_t	nsects;         /* 该段下包含的 section(节)的数量 */
	uint32_t	flags;          /* 标志位(例如 SG_HIGHVM,用于控制段的加载行为) */
};

Section 的数据结构如下:

struct section_64 {              /* 适用于 64 位架构的节结构体 */
	char		sectname[16];	/* 节的名称(固定 16 字节,例如 __text, __bss, __cstring) */
	char		segname[16];	/* 该节所属的段的名称(固定 16 字节,例如 __TEXT, __DATA) */
	uint64_t	addr;		/* 该节在虚拟内存中的起始目标地址 */
	uint64_t	size;		/* 该节在内存中所占用的字节大小 */
	uint32_t	offset;		/* 该节在磁盘文件中的偏移量(从文件开头算起) */
	uint32_t	align;		/* 节的内存对齐要求(以 2 的幂次方表示,例如 3 表示 2^3 = 8 字节对齐) */
	uint32_t	reloff;		/* 该节的重定位条目(Relocation Entries)在文件中的偏移量 */
	uint32_t	nreloc;		/* 该节所包含的重定位条目的数量 */
	uint32_t	flags;		/* 标志位,分为两部分:低 8 位表示节的类型(Type),高 24 位表示属性(Attributes) */
	uint32_t	reserved1;	/* 保留字段1(在特定类型的节中用作偏移量或索引,如间接符号表索引) */
	uint32_t	reserved2;	/* 保留字段2(在特定类型的节中用作计数或结构体大小,如 stub 的大小) */
	uint32_t	reserved3;	/* 保留字段3(纯保留字段,目前通常填充为 0) */
};

常见的5个 Segment 如下:

#define	SEG_PAGEZERO	"__PAGEZERO"	/* 空指针捕获段 */
#define	SEG_TEXT	"__TEXT"        /* 传统的 UNIX 代码段(文本段) */
#define	SEG_DATA	"__DATA"	/* 传统的 UNIX 数据段 */
#define	SEG_OBJC	"__OBJC"	/* Objective-C 运行时(Runtime)专用的段 */
#define	SEG_LINKEDIT	"__LINKEDIT"    /* 链接器输出段 */

① __TEXT(代码段)

  • 权限:只读、可执行(r-x)。

  • 作用:存放编译后的机器指令和只读数据。因为不可写,所以这一段在内存中可以被多个进程共享。

  • 常见 Sections:

    • __text:真正的可执行机器代码(C/OC/Swift 函数主体)。

    • __cstring:硬编码的 C 语言字符串常量(如 printf("hello") 中的字符串)。

    • __const:只读常量数据(如用 const 修饰的全局变量)。

    • __stubs:符号桩代码。用于调用外部动态库函数的占位跳转代码(与延迟绑定相关)。

    • __stubs_helper:辅助桩代码。当第一次调用外部函数时,负责通知 dyld 去寻找真实函数地址。

    • __objc_methname:Objective-C 方法名字符串(如 "init", "viewDidLoad"),Runtime 注册方法时使用。

    • __objc_methtype:Objective-C 方法的类型编码(Type Encoding)字符串(描述返回值和参数类型)。

    • __objc_classname:Objective-C 类名字符串。

② __DATA(数据段)

  • 权限:可读、可写、不可执行(rw-)。

  • 作用:存放全局变量、静态变量以及动态链接器(dyld)需要修改的指针。

  • 常用 Sections:

    • __data:已初始化的全局变量或静态变量。

    • __la_symbol_ptr:懒加载符号指针表(Lazy Symbol Pointer)。与 __stubs 对应,第一次调用外部函数时由 dyld 动态解析并写入真实地址。

    • nl_symbol_ptr:非懒加载符号指针表(Non-Lazy Symbol Pointer)。在程序启动加载时,dyld 必须立刻解析出真实地址的外部指针。

    • __const:需要重定位的常量数据(包含需要 dyld 在启动时修复指针的常量)。

    • __cfstring:Core Foundation 字符串对象(即 Objective-C 的 NSString 常量对象,内部包含指向 __cstring 的指针和长度等元数据)。

    • __bss:未初始化的全局变量(在文件中不占空间,加载到内存时才分配并清零)。

    • __common:未初始化的符号定义(通常用于传统的 C 语言公有块变量)。

    • __objc_classlist:Objective-C 的类列表(指针数组,指向程序中定义的所有类结构)。

    • __objc_protolist:Objective-C 协议列表。

    • __objc_imginfo:Objective-C 镜像信息。包含 ObjC 运行时的版本号(如 2.0)和一些编译标记(Flags)。

    • __objc_selfrefs:Objective-C 方法选择器(Selector)引用表。程序中使用的 @selector 指针列表。

    • __objc_protorefs:Objective-C 协议引用表。程序中用到的外部/内部协议的引用指针。

    • __objc_superrefs:Objective-C 父类引用表。用于子类通过 super 调用父类方法时快速定位。

③ __LINKEDIT(链接段)

  • 权限:只读(r--)。

  • 作用:包含动态链接器(dyld)所需的元数据,如符号表、字符串表、代码签名、重定位信息等。没有它,动态链接和调试就无法进行。

④ __PAGEZERO(空页段)

  • 权限:不可读、不可写、不可执行(---)。

  • 作用:位于文件和虚拟内存的最开始(在 64 位系统上通常占用 4GB 空间)。它是一个特殊的空白区域,用来捕捉 NULL 指针崩溃。如果程序尝试读写这个区域的地址,系统会直接抛出 EXC_BAD_ACCESS 错误。

⑤ __OBJC(ObjC 运行时段)

  • 权限:可读、可写、不可执行(rw-)。

  • 作用:存放 Objective-C 语言特有的类结构、方法名、协议、分类(Category)以及实例变量(Ivars)等元数据。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2026年 5月 19日

IosBX

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2026 IosBX's Blog. ALL RIGHTS RESERVED.