Skip to content

Day 83: std.macho模块:Mach-O格式解析

Mach-O(Mach Object)是Apple平台(包括macOS, iOS, watchOS等)使用的原生可执行文件格式。它不仅用于可执行文件(executables),还用于对象代码(.o文件)、共享库(dynamic libraries, .dylib)、内核扩展和核心转储(core dumps)。

理解Mach-O的结构对于进行二进制文件分析、调试、逆向工程或开发与Apple平台深度集成的工具至关重要。Zig的std.macho模块提供了一个强大的解析器,可以用来检查和理解Mach-O文件的内部结构。

std.macho.parse函数接收一个代表文件内容的字节切片和一个分配器,然后返回一个MachO结构体。这个结构体包含了文件所有部分的解析结果,如头部、加载命令、段(sections)和符号表。

const std = @import("std");
const macho = std.macho;
pub fn main() !void {
const allocator = std.testing.allocator;
// 读取一个可执行文件(例如 /bin/ls)
const file_bytes = try std.fs.cwd().readFileAlloc(allocator, "/bin/ls", 10 * 1024 * 1024);
defer allocator.free(file_bytes);
var file = try macho.parse(file_bytes, allocator);
defer file.deinit();
std.debug.print("File Type: {any}\n", .{file.header.filetype});
std.debug.print("CPU Type: {any}\n", .{file.header.cputype});
}

Mach-O文件被组织成一系列的“段”(Segments),例如__TEXT(代码)、__DATA(数据)和__LINKEDIT(链接器信息)。每个段又可以包含多个“节”(Sections),例如__TEXT.__text(可执行代码)和__DATA.__data(初始化的可读写数据)。

你可以遍历这些结构来分析文件的布局:

for (file.segments) |segment| {
std.debug.print("Segment: {s}\n", .{segment.name});
for (segment.sections) |section| {
std.debug.print(" Section: {s}.{s}\n", .{section.segname, section.name});
}
}

Mach-O文件通过加载命令(Load Commands)来告诉操作系统如何加载和运行它。其中一种重要的加载命令是LC_LOAD_DYLIB,它指定了该文件依赖的动态库。

for (file.load_commands) |cmd| {
if (cmd.id == .LOAD_DYLIB) {
const dylib_cmd = cmd.as(macho.DylibCommand).?;
const dylib_name = file.bytes[dylib_cmd.dylib.name_offset..][0..dylib_cmd.dylib.name_len];
std.debug.print("Depends on: {s}\n", .{dylib_name});
}
}

遍历file.symbols来打印出文件中的所有符号。对于每个符号,打印出它的名称和类型(例如,函数、数据、外部符号)。这对于理解一个二进制文件导出了哪些函数非常有用。

  • 胖二进制(Fat Binaries) 一个Mach-O文件可以是“胖”的(Fat),意味着它在一个文件中包含了多个架构(如x86_64和arm64)的代码。std.macho.Fat可以用来解析这种文件,并允许你选择要检查的特定架构的“切片”。

  • 动态库(Dylibs) std.macho可以解析动态库文件,帮助你理解它们导出了哪些符号,以及它们依赖于哪些其他库。

  • 代码签名 现代macOS和iOS上的Mach-O文件都经过了代码签名。std.macho可以解析LC_CODE_SIGNATURE加载命令,让你能够检查签名的位置和大小,但它不进行签名验证。

Mach-O是Apple生态系统的特定格式,与Linux上使用的ELF格式和Windows上使用的PE格式有很大不同。虽然它们都服务于相同的目的(描述可执行代码和数据),但它们的内部结构(如头部、段/节的组织方式、重定位和符号表的格式)完全不同。

Zig标准库为所有这三种主流格式(std.macho, std.elf, std.pe)都提供了强大的原生解析器。这使得Zig成为编写跨平台二进制分析工具、链接器、调试器或其他底层开发工具的理想语言。