You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mach-O中Objective-C元数据实体指针的编码存储方式探究

Mach-O中Objective-C元数据实体指针的编码机制

背景与测试场景

我正在编写一个解析器,用于从输入的Mach-O二进制文件中提取Objective-C元数据实体,希望深入了解Mach-O中指向元数据实体的指针的存储/编码方式。

示例代码

#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void) someMethod;

@end

@implementation Person

- (void) someMethod {}

@end

int main() {
    return 0;
}

两次编译测试

1. ARM64 iOS目标编译

clang++ -target arm64-apple-ios16 -isysroot /path/to/iphoneos_sdk \
  -framework Foundation -o test test.m

objdump -s test输出片段:

...
Contents of section __DATA_CONST.__objc_classlist:
 100008000 c0c00000 00000000                    ........        
...
Contents of section __DATA.__objc_data:
 10000c098 01000000 00001080 01000000 00001080  ................
 10000c0a8 00000000 00002080 00000000 00000000  ...... .........
 10000c0b8 00c00000 00001000 98c00000 00001000  ................
 10000c0c8 02000000 00001080 00000000 00002080  .............. .
 10000c0d8 00000000 00000000 48c00000 00000000  ........H.......

注意:__objc_classlist段中存储的类指针为0x000000000000C0C0(小端序解析后),而类实际位于__objc_data段的0x00010000C0C0地址。

2. x86_64 macOS目标编译

clang++ -framework Foundation -o test test.m

objdump -s test输出片段:

...
Contents of section __DATA_CONST.__objc_classlist:
 100004000 d8800000 01000000                    ........        
...
Contents of section __DATA.__objc_data:
 1000080b0 00000000 00000000 00000000 00000000  ................
 1000080c0 00000000 00000000 00000000 00000000  ................
 1000080d0 00800000 01000000 b0800000 01000000  ................
 1000080e0 00000000 00000000 00000000 00000000  ................
 1000080f0 00000000 00000000 68800000 01000000  ........h.......

此场景下,__objc_classlist段中存储的类指针为0x0001000080D8(小端序解析后),可直接指向__objc_data段中类的实际存储位置。


我还发现了其他指针编码方式,例如在ARM64目标中,某元数据实体的存储指针为0x0000900000003FAF,而实际位置为0x000100003FAF。


我的问题是:Objective-C/clang是如何在Mach-O文件中编码元数据实体指针的?


解答

Mach-O中Objective-C元数据指针的编码方式完全依赖于目标架构和Apple的链接优化策略,核心差异体现在x86_64与ARM64(Apple Silicon/iOS)平台之间:

1. x86_64平台:直接存储完整虚拟地址

x86_64架构下,编译器默认不启用指针压缩优化,Objective-C元数据(如类、分类、方法列表)的指针会直接存储完整的64位虚拟地址:

  • 测试中x86_64的__objc_classlist里的指针与__objc_data段中类的实际地址完全一致,无需额外解码。
  • 这是因为x86_64的地址空间足够大,直接存储完整地址的开销在可接受范围内,且早期没有强制推行指针压缩。

2. ARM64平台(Apple Silicon/iOS):指针压缩与地址偏移

Apple的ARM64平台全面启用了**指针压缩(Pointer Compression)和指针认证(Pointer Authentication, PAC)**优化,Objective-C元数据指针会以压缩形式存储,常见的编码方式有两种:

2.1 基于加载基地址的偏移存储

对于大部分Objective-C元数据(如类列表__objc_classlist中的指针),编译器会存储相对于二进制加载基地址的低32位偏移量:

  • 测试中ARM64的__objc_classlist里的指针解析后为0x000000000000C0C0,而类的实际地址是0x00010000C0C0。这里的0x000100000000是二进制的加载基地址(ASLR后的基地址),将基地址与存储的偏移量拼接即可得到实际地址:0x000100000000 + 0xC0C0 = 0x00010000C0C0。
  • 这种方式将64位指针压缩到32位,大幅减少元数据的内存占用。

2.2 带指针认证(PAC)的压缩指针

对于部分敏感元数据(如方法列表、属性列表的指针),编译器会在指针中嵌入指针认证码,存储形式为[PAC掩码][低48位地址]:

  • 你提到的案例中,存储指针为0x0000900000003FAF,实际地址为0x000100003FAF。这里的0x90000000是PAC认证码的高位部分,运行时系统会通过authenticate指令移除PAC掩码,还原出原始的完整地址。
  • PAC的作用是防止指针篡改,提升二进制的安全性,同时结合指针压缩保持内存效率。

解析压缩指针的关键步骤

如果要编写解析器处理ARM64的Mach-O文件,需要:

  1. 获取加载基地址:从Mach-O的LC_SEGMENT_64命令中读取__TEXT段的vmaddr字段,即为二进制加载基地址的起始。
  2. 区分指针类型:
    • 对于__objc_classlist、__objc_catlist等列表中的指针,直接将加载基地址与存储的偏移量(低32位)拼接。
    • 对于带PAC的指针,通过按位与操作指针 & 0x00007FFFFFFFFFFF保留低47位有效地址,再拼接加载基地址的高位。
  3. 处理字节序:Mach-O文件采用小端序存储多字节数据,解析时需要先将存储的字节序列转换为正确的整数表示。

内容的提问来源于stack exchange,提问作者Kareem Ergawy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:27:00