Mach-O 解析
在进入 App 的过程中,需要加载各种文件,这种文件必然是需要符合某种格式来让系统高效的进行阅读。
在 iOS 系统中,需要先进行加载的是通用二进制 (Universal Binary / Fat Binary) 文件。
通用二进制文件是一种“容器”格式,旨在解决跨架构兼容性问题(如同时支持 Intel 和 Apple Silicon Mac)。它的格式如下:
struct fat_header {
uint32_t magic; /* FAT_MAGIC (0xcafebabe) */
uint32_t nfat_arch; /* 包含的架构数量 */
};
struct fat_arch {
int32_t cputype; /* CPU 类型 (如 ARM64, x86_64) */
int32_t cpusubtype; /* CPU 子类型 */
uint32_t offset; /* 该架构 Mach-O 在文件中的偏移量 */
uint32_t size; /* 该架构 Mach-O 的大小 */
uint32_t align; /* 内存对齐 */
};
内核一开始会先读取 读取 fat_header : 发现 0xcafebabe ,确认为 Fat Binary;然后遍历 Arch 列表 : 读取随后的 fat_arch 数组;接着通过 cputype 和 cpusubtype ,与现在正在运行的 CPU 进行比对;找到了对应 CPU ,内核则通过 offset 和 size ,讲对应架构的 Mach-O 数据映射到内存中。
这样的好处在于:内核只需要找到对应的索引,就能定位到正确的位置,加载到正确架构的代码,无关代码则完全不会干涉,节省了大量的时间和 RAM。
这是典型的通过以磁盘空间换取兼容性和运行时效率的设计。
Mach-O 格式
Mach-O (Mach Object) 是 macOS 和 iOS 上使用的可执行文件格式。它主要由三大部分组成: Header 、 Load Commands 和 Data 。
-
Header (头部) 包含 Magic Number、CPU 架构、文件类型(如 MH_EXECUTE )、Load Commands 的数量和总大小。Runtime 使用 headerType (即 mach_header_64 ) 来解析它。
-
Load Commands (加载命令) 告诉内核和 Dyld 如何布局内存。LC_SEGMENT_64 : 最关键的命令,划分内存区域(Segment)。LC_LOAD_DYLIB : 声明依赖的动态库(如 Foundation )。Data (数据区 - Segments & Sections)
-
真正的“内容仓库”。
- __TEXT Segment : 只读 。存放代码指令 ( __text ) 和常量 ( __cstring )。
- __DATA Segment : 可读写 。存放全局变量和 ObjC Runtime 元数据 。
- Runtime 在初始化时 ( _objc_init -> map_images ),会通过 getSectionData 扫描这里的特定 Section:
- __objc_classlist : 读取所有类。
- __objc_selrefs : 读取方法选择器。
- __objc_protolist : 读取协议。 - __LINKEDIT Segment : 只读 。存放符号表、字符串表和代码签名,供 Dyld 链接和验签使用。