diff options
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/macho.zig | 493 |
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(§.sectname); + } + + pub fn segName(sect: section_64) []const u8 { + return parseName(§.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(§)); + } + } + + 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); +} |
