diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-12-10 14:13:43 +0100 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-12-10 18:18:28 +0100 |
| commit | 828f61e8dfcf17e0f7c42552311e6589bb187880 (patch) | |
| tree | 6972d08f0c0292d27ebb1e8a74bc7c9a3087feae /lib/std | |
| parent | 81e7d8505c086a93accb74e9f1a84abb8ff7cf24 (diff) | |
| download | zig-828f61e8dfcf17e0f7c42552311e6589bb187880.tar.gz zig-828f61e8dfcf17e0f7c42552311e6589bb187880.zip | |
macho: move all helpers from commands.zig into std.macho
This way we will finally be able to share common parsing logic
between different Zig components and 3rd party packages.
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/macho.zig | 435 |
1 files changed, 434 insertions, 1 deletions
diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 296d8d4307..a77603c9ef 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1,4 +1,12 @@ 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, @@ -770,7 +778,7 @@ pub const section_64 = extern struct { }; fn parseName(name: *const [16]u8) []const u8 { - const len = std.mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; + const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; return name[0..len]; } @@ -1804,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); +} |
