aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/macho.zig493
1 files changed, 485 insertions, 8 deletions
diff --git a/lib/std/macho.zig b/lib/std/macho.zig
index dce6d10cd5..a77603c9ef 100644
--- a/lib/std/macho.zig
+++ b/lib/std/macho.zig
@@ -1,3 +1,13 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const assert = std.debug.assert;
+const io = std.io;
+const mem = std.mem;
+const meta = std.meta;
+const testing = std.testing;
+
+const Allocator = mem.Allocator;
+
pub const mach_header = extern struct {
magic: u32,
cputype: cpu_type_t,
@@ -9,14 +19,14 @@ pub const mach_header = extern struct {
};
pub const mach_header_64 = extern struct {
- magic: u32,
- cputype: cpu_type_t,
- cpusubtype: cpu_subtype_t,
- filetype: u32,
- ncmds: u32,
- sizeofcmds: u32,
- flags: u32,
- reserved: u32,
+ magic: u32 = MH_MAGIC_64,
+ cputype: cpu_type_t = 0,
+ cpusubtype: cpu_subtype_t = 0,
+ filetype: u32 = 0,
+ ncmds: u32 = 0,
+ sizeofcmds: u32 = 0,
+ flags: u32 = 0,
+ reserved: u32 = 0,
};
pub const fat_header = extern struct {
@@ -630,6 +640,10 @@ pub const segment_command_64 = extern struct {
/// number of sections in segment
nsects: u32 = 0,
flags: u32 = 0,
+
+ pub fn segName(seg: segment_command_64) []const u8 {
+ return parseName(&seg.segname);
+ }
};
/// A segment is made up of zero or more sections. Non-MH_OBJECT files have
@@ -728,8 +742,46 @@ pub const section_64 = extern struct {
/// reserved
reserved3: u32 = 0,
+
+ pub fn sectName(sect: section_64) []const u8 {
+ return parseName(&sect.sectname);
+ }
+
+ pub fn segName(sect: section_64) []const u8 {
+ return parseName(&sect.segname);
+ }
+
+ pub fn type_(sect: section_64) u8 {
+ return @truncate(u8, sect.flags & 0xff);
+ }
+
+ pub fn attrs(sect: section_64) u32 {
+ return sect.flags & 0xffffff00;
+ }
+
+ pub fn isCode(sect: section_64) bool {
+ const attr = sect.attrs();
+ return attr & S_ATTR_PURE_INSTRUCTIONS != 0 or attr & S_ATTR_SOME_INSTRUCTIONS != 0;
+ }
+
+ pub fn isDebug(sect: section_64) bool {
+ return sect.attrs() & S_ATTR_DEBUG != 0;
+ }
+
+ pub fn isDontDeadStrip(sect: section_64) bool {
+ return sect.attrs() & S_ATTR_NO_DEAD_STRIP != 0;
+ }
+
+ pub fn isDontDeadStripIfReferencesLive(sect: section_64) bool {
+ return sect.attrs() & S_ATTR_LIVE_SUPPORT != 0;
+ }
};
+fn parseName(name: *const [16]u8) []const u8 {
+ const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
+ return name[0..len];
+}
+
pub const nlist = extern struct {
n_strx: u32,
n_type: u8,
@@ -1760,3 +1812,428 @@ pub const data_in_code_entry = extern struct {
/// A DICE_KIND value.
kind: u16,
};
+
+/// A Zig wrapper for all known MachO load commands.
+/// Provides interface to read and write the load command data to a buffer.
+pub const LoadCommand = union(enum) {
+ segment: SegmentCommand,
+ dyld_info_only: dyld_info_command,
+ symtab: symtab_command,
+ dysymtab: dysymtab_command,
+ dylinker: GenericCommandWithData(dylinker_command),
+ dylib: GenericCommandWithData(dylib_command),
+ main: entry_point_command,
+ version_min: version_min_command,
+ source_version: source_version_command,
+ build_version: GenericCommandWithData(build_version_command),
+ uuid: uuid_command,
+ linkedit_data: linkedit_data_command,
+ rpath: GenericCommandWithData(rpath_command),
+ unknown: GenericCommandWithData(load_command),
+
+ pub fn read(allocator: Allocator, reader: anytype) !LoadCommand {
+ const header = try reader.readStruct(load_command);
+ var buffer = try allocator.alloc(u8, header.cmdsize);
+ defer allocator.free(buffer);
+ mem.copy(u8, buffer, mem.asBytes(&header));
+ try reader.readNoEof(buffer[@sizeOf(load_command)..]);
+ var stream = io.fixedBufferStream(buffer);
+
+ return switch (header.cmd) {
+ LC_SEGMENT_64 => LoadCommand{
+ .segment = try SegmentCommand.read(allocator, stream.reader()),
+ },
+ LC_DYLD_INFO, LC_DYLD_INFO_ONLY => LoadCommand{
+ .dyld_info_only = try stream.reader().readStruct(dyld_info_command),
+ },
+ LC_SYMTAB => LoadCommand{
+ .symtab = try stream.reader().readStruct(symtab_command),
+ },
+ LC_DYSYMTAB => LoadCommand{
+ .dysymtab = try stream.reader().readStruct(dysymtab_command),
+ },
+ LC_ID_DYLINKER, LC_LOAD_DYLINKER, LC_DYLD_ENVIRONMENT => LoadCommand{
+ .dylinker = try GenericCommandWithData(dylinker_command).read(allocator, stream.reader()),
+ },
+ LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_LOAD_DYLIB, LC_REEXPORT_DYLIB => LoadCommand{
+ .dylib = try GenericCommandWithData(dylib_command).read(allocator, stream.reader()),
+ },
+ LC_MAIN => LoadCommand{
+ .main = try stream.reader().readStruct(entry_point_command),
+ },
+ LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_VERSION_MIN_WATCHOS, LC_VERSION_MIN_TVOS => LoadCommand{
+ .version_min = try stream.reader().readStruct(version_min_command),
+ },
+ LC_SOURCE_VERSION => LoadCommand{
+ .source_version = try stream.reader().readStruct(source_version_command),
+ },
+ LC_BUILD_VERSION => LoadCommand{
+ .build_version = try GenericCommandWithData(build_version_command).read(allocator, stream.reader()),
+ },
+ LC_UUID => LoadCommand{
+ .uuid = try stream.reader().readStruct(uuid_command),
+ },
+ LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_CODE_SIGNATURE => LoadCommand{
+ .linkedit_data = try stream.reader().readStruct(linkedit_data_command),
+ },
+ LC_RPATH => LoadCommand{
+ .rpath = try GenericCommandWithData(rpath_command).read(allocator, stream.reader()),
+ },
+ else => LoadCommand{
+ .unknown = try GenericCommandWithData(load_command).read(allocator, stream.reader()),
+ },
+ };
+ }
+
+ pub fn write(self: LoadCommand, writer: anytype) !void {
+ return switch (self) {
+ .dyld_info_only => |x| writeStruct(x, writer),
+ .symtab => |x| writeStruct(x, writer),
+ .dysymtab => |x| writeStruct(x, writer),
+ .main => |x| writeStruct(x, writer),
+ .version_min => |x| writeStruct(x, writer),
+ .source_version => |x| writeStruct(x, writer),
+ .uuid => |x| writeStruct(x, writer),
+ .linkedit_data => |x| writeStruct(x, writer),
+ .segment => |x| x.write(writer),
+ .dylinker => |x| x.write(writer),
+ .dylib => |x| x.write(writer),
+ .rpath => |x| x.write(writer),
+ .build_version => |x| x.write(writer),
+ .unknown => |x| x.write(writer),
+ };
+ }
+
+ pub fn cmd(self: LoadCommand) u32 {
+ return switch (self) {
+ .dyld_info_only => |x| x.cmd,
+ .symtab => |x| x.cmd,
+ .dysymtab => |x| x.cmd,
+ .main => |x| x.cmd,
+ .version_min => |x| x.cmd,
+ .source_version => |x| x.cmd,
+ .uuid => |x| x.cmd,
+ .linkedit_data => |x| x.cmd,
+ .segment => |x| x.inner.cmd,
+ .dylinker => |x| x.inner.cmd,
+ .dylib => |x| x.inner.cmd,
+ .rpath => |x| x.inner.cmd,
+ .build_version => |x| x.inner.cmd,
+ .unknown => |x| x.inner.cmd,
+ };
+ }
+
+ pub fn cmdsize(self: LoadCommand) u32 {
+ return switch (self) {
+ .dyld_info_only => |x| x.cmdsize,
+ .symtab => |x| x.cmdsize,
+ .dysymtab => |x| x.cmdsize,
+ .main => |x| x.cmdsize,
+ .version_min => |x| x.cmdsize,
+ .source_version => |x| x.cmdsize,
+ .linkedit_data => |x| x.cmdsize,
+ .uuid => |x| x.cmdsize,
+ .segment => |x| x.inner.cmdsize,
+ .dylinker => |x| x.inner.cmdsize,
+ .dylib => |x| x.inner.cmdsize,
+ .rpath => |x| x.inner.cmdsize,
+ .build_version => |x| x.inner.cmdsize,
+ .unknown => |x| x.inner.cmdsize,
+ };
+ }
+
+ pub fn deinit(self: *LoadCommand, allocator: Allocator) void {
+ return switch (self.*) {
+ .segment => |*x| x.deinit(allocator),
+ .dylinker => |*x| x.deinit(allocator),
+ .dylib => |*x| x.deinit(allocator),
+ .rpath => |*x| x.deinit(allocator),
+ .build_version => |*x| x.deinit(allocator),
+ .unknown => |*x| x.deinit(allocator),
+ else => {},
+ };
+ }
+
+ fn writeStruct(command: anytype, writer: anytype) !void {
+ return writer.writeAll(mem.asBytes(&command));
+ }
+
+ pub fn eql(self: LoadCommand, other: LoadCommand) bool {
+ if (@as(meta.Tag(LoadCommand), self) != @as(meta.Tag(LoadCommand), other)) return false;
+ return switch (self) {
+ .dyld_info_only => |x| meta.eql(x, other.dyld_info_only),
+ .symtab => |x| meta.eql(x, other.symtab),
+ .dysymtab => |x| meta.eql(x, other.dysymtab),
+ .main => |x| meta.eql(x, other.main),
+ .version_min => |x| meta.eql(x, other.version_min),
+ .source_version => |x| meta.eql(x, other.source_version),
+ .build_version => |x| x.eql(other.build_version),
+ .uuid => |x| meta.eql(x, other.uuid),
+ .linkedit_data => |x| meta.eql(x, other.linkedit_data),
+ .segment => |x| x.eql(other.segment),
+ .dylinker => |x| x.eql(other.dylinker),
+ .dylib => |x| x.eql(other.dylib),
+ .rpath => |x| x.eql(other.rpath),
+ .unknown => |x| x.eql(other.unknown),
+ };
+ }
+};
+
+/// A Zig wrapper for segment_command_64.
+/// Encloses the extern struct together with a list of sections for this segment.
+pub const SegmentCommand = struct {
+ inner: segment_command_64,
+ sections: std.ArrayListUnmanaged(section_64) = .{},
+
+ pub fn read(allocator: Allocator, reader: anytype) !SegmentCommand {
+ const inner = try reader.readStruct(segment_command_64);
+ var segment = SegmentCommand{
+ .inner = inner,
+ };
+ try segment.sections.ensureTotalCapacityPrecise(allocator, inner.nsects);
+
+ var i: usize = 0;
+ while (i < inner.nsects) : (i += 1) {
+ const sect = try reader.readStruct(section_64);
+ segment.sections.appendAssumeCapacity(sect);
+ }
+
+ return segment;
+ }
+
+ pub fn write(self: SegmentCommand, writer: anytype) !void {
+ try writer.writeAll(mem.asBytes(&self.inner));
+ for (self.sections.items) |sect| {
+ try writer.writeAll(mem.asBytes(&sect));
+ }
+ }
+
+ pub fn deinit(self: *SegmentCommand, allocator: Allocator) void {
+ self.sections.deinit(allocator);
+ }
+
+ pub fn eql(self: SegmentCommand, other: SegmentCommand) bool {
+ if (!meta.eql(self.inner, other.inner)) return false;
+ const lhs = self.sections.items;
+ const rhs = other.sections.items;
+ var i: usize = 0;
+ while (i < self.inner.nsects) : (i += 1) {
+ if (!meta.eql(lhs[i], rhs[i])) return false;
+ }
+ return true;
+ }
+};
+
+pub fn emptyGenericCommandWithData(cmd: anytype) GenericCommandWithData(@TypeOf(cmd)) {
+ return .{ .inner = cmd };
+}
+
+/// A Zig wrapper for a generic load command with variable-length data.
+pub fn GenericCommandWithData(comptime Cmd: type) type {
+ return struct {
+ inner: Cmd,
+ /// This field remains undefined until `read` is called.
+ data: []u8 = undefined,
+
+ const Self = @This();
+
+ pub fn read(allocator: Allocator, reader: anytype) !Self {
+ const inner = try reader.readStruct(Cmd);
+ var data = try allocator.alloc(u8, inner.cmdsize - @sizeOf(Cmd));
+ errdefer allocator.free(data);
+ try reader.readNoEof(data);
+ return Self{
+ .inner = inner,
+ .data = data,
+ };
+ }
+
+ pub fn write(self: Self, writer: anytype) !void {
+ try writer.writeAll(mem.asBytes(&self.inner));
+ try writer.writeAll(self.data);
+ }
+
+ pub fn deinit(self: *Self, allocator: Allocator) void {
+ allocator.free(self.data);
+ }
+
+ pub fn eql(self: Self, other: Self) bool {
+ if (!meta.eql(self.inner, other.inner)) return false;
+ return mem.eql(u8, self.data, other.data);
+ }
+ };
+}
+
+pub fn createLoadDylibCommand(
+ allocator: Allocator,
+ name: []const u8,
+ timestamp: u32,
+ current_version: u32,
+ compatibility_version: u32,
+) !GenericCommandWithData(dylib_command) {
+ const cmdsize = @intCast(u32, mem.alignForwardGeneric(
+ u64,
+ @sizeOf(dylib_command) + name.len + 1, // +1 for nul
+ @sizeOf(u64),
+ ));
+
+ var dylib_cmd = emptyGenericCommandWithData(dylib_command{
+ .cmd = LC_LOAD_DYLIB,
+ .cmdsize = cmdsize,
+ .dylib = .{
+ .name = @sizeOf(dylib_command),
+ .timestamp = timestamp,
+ .current_version = current_version,
+ .compatibility_version = compatibility_version,
+ },
+ });
+ dylib_cmd.data = try allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
+
+ mem.set(u8, dylib_cmd.data, 0);
+ mem.copy(u8, dylib_cmd.data, name);
+
+ return dylib_cmd;
+}
+
+fn testRead(allocator: Allocator, buffer: []const u8, expected: anytype) !void {
+ var stream = io.fixedBufferStream(buffer);
+ var given = try LoadCommand.read(allocator, stream.reader());
+ defer given.deinit(allocator);
+ try testing.expect(expected.eql(given));
+}
+
+fn testWrite(buffer: []u8, cmd: LoadCommand, expected: []const u8) !void {
+ var stream = io.fixedBufferStream(buffer);
+ try cmd.write(stream.writer());
+ try testing.expect(mem.eql(u8, expected, buffer[0..expected.len]));
+}
+
+fn makeStaticString(bytes: []const u8) [16]u8 {
+ var buf = [_]u8{0} ** 16;
+ assert(bytes.len <= buf.len);
+ mem.copy(u8, &buf, bytes);
+ return buf;
+}
+
+test "read-write segment command" {
+ // TODO compiling for macOS from big-endian arch
+ if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
+
+ var gpa = testing.allocator;
+ const in_buffer = &[_]u8{
+ 0x19, 0x00, 0x00, 0x00, // cmd
+ 0x98, 0x00, 0x00, 0x00, // cmdsize
+ 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // vmaddr
+ 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // vmsize
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fileoff
+ 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // filesize
+ 0x07, 0x00, 0x00, 0x00, // maxprot
+ 0x05, 0x00, 0x00, 0x00, // initprot
+ 0x01, 0x00, 0x00, 0x00, // nsects
+ 0x00, 0x00, 0x00, 0x00, // flags
+ 0x5f, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sectname
+ 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
+ 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // address
+ 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size
+ 0x00, 0x40, 0x00, 0x00, // offset
+ 0x02, 0x00, 0x00, 0x00, // alignment
+ 0x00, 0x00, 0x00, 0x00, // reloff
+ 0x00, 0x00, 0x00, 0x00, // nreloc
+ 0x00, 0x04, 0x00, 0x80, // flags
+ 0x00, 0x00, 0x00, 0x00, // reserved1
+ 0x00, 0x00, 0x00, 0x00, // reserved2
+ 0x00, 0x00, 0x00, 0x00, // reserved3
+ };
+ var cmd = SegmentCommand{
+ .inner = .{
+ .cmdsize = 152,
+ .segname = makeStaticString("__TEXT"),
+ .vmaddr = 4294967296,
+ .vmsize = 294912,
+ .filesize = 294912,
+ .maxprot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
+ .initprot = VM_PROT_EXECUTE | VM_PROT_READ,
+ .nsects = 1,
+ },
+ };
+ try cmd.sections.append(gpa, .{
+ .sectname = makeStaticString("__text"),
+ .segname = makeStaticString("__TEXT"),
+ .addr = 4294983680,
+ .size = 448,
+ .offset = 16384,
+ .@"align" = 2,
+ .flags = S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
+ });
+ defer cmd.deinit(gpa);
+ try testRead(gpa, in_buffer, LoadCommand{ .segment = cmd });
+
+ var out_buffer: [in_buffer.len]u8 = undefined;
+ try testWrite(&out_buffer, LoadCommand{ .segment = cmd }, in_buffer);
+}
+
+test "read-write generic command with data" {
+ // TODO compiling for macOS from big-endian arch
+ if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
+
+ var gpa = testing.allocator;
+ const in_buffer = &[_]u8{
+ 0x0c, 0x00, 0x00, 0x00, // cmd
+ 0x20, 0x00, 0x00, 0x00, // cmdsize
+ 0x18, 0x00, 0x00, 0x00, // name
+ 0x02, 0x00, 0x00, 0x00, // timestamp
+ 0x00, 0x00, 0x00, 0x00, // current_version
+ 0x00, 0x00, 0x00, 0x00, // compatibility_version
+ 0x2f, 0x75, 0x73, 0x72, 0x00, 0x00, 0x00, 0x00, // data
+ };
+ var cmd = GenericCommandWithData(dylib_command){
+ .inner = .{
+ .cmd = LC_LOAD_DYLIB,
+ .cmdsize = 32,
+ .dylib = .{
+ .name = 24,
+ .timestamp = 2,
+ .current_version = 0,
+ .compatibility_version = 0,
+ },
+ },
+ };
+ cmd.data = try gpa.alloc(u8, 8);
+ defer gpa.free(cmd.data);
+ cmd.data[0] = 0x2f;
+ cmd.data[1] = 0x75;
+ cmd.data[2] = 0x73;
+ cmd.data[3] = 0x72;
+ cmd.data[4] = 0x0;
+ cmd.data[5] = 0x0;
+ cmd.data[6] = 0x0;
+ cmd.data[7] = 0x0;
+ try testRead(gpa, in_buffer, LoadCommand{ .dylib = cmd });
+
+ var out_buffer: [in_buffer.len]u8 = undefined;
+ try testWrite(&out_buffer, LoadCommand{ .dylib = cmd }, in_buffer);
+}
+
+test "read-write C struct command" {
+ // TODO compiling for macOS from big-endian arch
+ if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
+
+ var gpa = testing.allocator;
+ const in_buffer = &[_]u8{
+ 0x28, 0x00, 0x00, 0x80, // cmd
+ 0x18, 0x00, 0x00, 0x00, // cmdsize
+ 0x04, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // entryoff
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stacksize
+ };
+ const cmd = .{
+ .cmd = LC_MAIN,
+ .cmdsize = 24,
+ .entryoff = 16644,
+ .stacksize = 0,
+ };
+ try testRead(gpa, in_buffer, LoadCommand{ .main = cmd });
+
+ var out_buffer: [in_buffer.len]u8 = undefined;
+ try testWrite(&out_buffer, LoadCommand{ .main = cmd }, in_buffer);
+}