From 43b6d0e4b17524fb016fcb396e87bf2df4e32456 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 24 Aug 2020 21:18:27 +0200 Subject: [PATCH 1/2] Add (empty) __TEXT segment load command Also, link against `libSystem` by default when targeting macOS. Signed-off-by: Jakub Konka --- lib/std/macho.zig | 13 +++ src-self-hosted/link/MachO.zig | 157 ++++++++++++++++++++------------- 2 files changed, 107 insertions(+), 63 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 4057d3dc99..982dad5124 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -797,3 +797,16 @@ pub const CPU_SUBTYPE_X86_64_ALL: cpu_subtype_t = 0x3; /// All ARM-based Macs pub const CPU_SUBTYPE_ARM_ALL: cpu_subtype_t = 0x0; + +// Protection values defined as bits within the vm_prot_t type +/// No VM protection +pub const VM_PROT_NONE: vm_prot_t = 0x0; + +/// VM read permission +pub const VM_PROT_READ: vm_prot_t = 0x1; + +/// VM write permission +pub const VM_PROT_WRITE: vm_prot_t = 0x2; + +/// VM execute permission +pub const VM_PROT_EXECUTE: vm_prot_t = 0x4; diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index a65366261a..2461ba25b4 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -30,24 +30,26 @@ command_file_offset: ?u64 = null, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, +/// Section (headers) *always* follow segment (load commands) directly! sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, -segment_table_offset: ?u64 = null, + +/// Offset (index) into __TEXT segment load command. +text_segment_offset: ?u64 = null, +/// Offset (index) into __LINKEDIT segment load command. +linkedit_segment_offset: ?u664 = null, /// Entry point load command entry_point_cmd: ?macho.entry_point_command = null, entry_addr: ?u64 = null, -/// Default VM start address set at 4GB +/// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment. +/// This is also the start address for our binary. vm_start_address: u64 = 0x100000000, seg_table_dirty: bool = false, error_flags: File.ErrorFlags = File.ErrorFlags{}, -/// TODO ultimately this will be propagated down from main() and set (in this form or another) -/// when user links against system lib. -link_against_system: bool = false, - /// `alloc_num / alloc_den` is the factor of padding when allocating. const alloc_num = 4; const alloc_den = 3; @@ -138,8 +140,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach .vmsize = self.vm_start_address, .fileoff = 0, .filesize = 0, - .maxprot = 0, - .initprot = 0, + .maxprot = macho.VM_PROT_NONE, + .initprot = macho.VM_PROT_NONE, .nsects = 0, .flags = 0, }; @@ -225,66 +227,62 @@ pub fn flush(self: *MachO, module: *Module) !void { switch (self.base.options.output_mode) { .Exe => { - if (self.link_against_system) { - if (is_darwin) { - { - // Specify path to dynamic linker dyld - const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH))); - const load_dylinker = [1]macho.dylinker_command{ - .{ - .cmd = macho.LC_LOAD_DYLINKER, - .cmdsize = cmdsize, - .name = @sizeOf(macho.dylinker_command), - }, - }; - try self.commands.append(self.base.allocator, .{ + if (is_darwin) { + { + // Specify path to dynamic linker dyld + const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); + const load_dylinker = [1]macho.dylinker_command{ + .{ .cmd = macho.LC_LOAD_DYLINKER, .cmdsize = cmdsize, - }); + .name = @sizeOf(macho.dylinker_command), + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); - const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); - try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); - self.command_file_offset.? += cmdsize; - } + try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); + self.command_file_offset.? += cmdsize; + } - { - // Link against libSystem - const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH))); - // According to Apple's manual, we should obtain current libSystem version using libc call - // NSVersionOfRunTimeLibrary. - const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME); - const dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = version, - .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering - }; - const load_dylib = [1]macho.dylib_command{ - .{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = dylib, - }, - }; - try self.commands.append(self.base.allocator, .{ + { + // Link against libSystem + const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)); + // According to Apple's manual, we should obtain current libSystem version using libc call + // NSVersionOfRunTimeLibrary. + const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME); + const dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = version, + .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering + }; + const load_dylib = [1]macho.dylib_command{ + .{ .cmd = macho.LC_LOAD_DYLIB, .cmdsize = cmdsize, - }); + .dylib = dylib, + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); - const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); - try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); - self.command_file_offset.? += cmdsize; - } - } else { - @panic("linking against libSystem on non-native target is unsupported"); + try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); + self.command_file_offset.? += cmdsize; } } }, @@ -327,20 +325,53 @@ pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { @panic("TODO implement getDeclVAddr for MachO"); } -pub fn populateMissingMetadata(self: *MachO) !void {} +pub fn populateMissingMetadata(self: *MachO) !void { + if (self.text_segment_offset == null) { + self.text_segment_offset = @intCast(u64, self.segments.items.len); + const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000); + log.debug("vmsize/filesize = {}", .{file_size}); + const file_offset = 0; + const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM + const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; + const cmdsize = commandSize(@sizeOf(macho.segment_command_64)); + const text_segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = cmdsize, + .segname = makeString("__TEXT"), + .vmaddr = vm_address, + .vmsize = file_size, + .fileoff = 0, // __TEXT segment *always* starts at 0 file offset + .filesize = 0, //file_size, + .maxprot = protection, + .initprot = protection, + .nsects = 0, + .flags = 0, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = cmdsize, + }); + try self.segments.append(self.base.allocator, text_segment); + } +} fn makeString(comptime bytes: []const u8) [16]u8 { - var buf: [16]u8 = undefined; + var buf = [_]u8{0} ** 16; if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); mem.copy(u8, buf[0..], bytes); return buf; } -fn commandSize(min_size: u32) u32 { - if (min_size % @sizeOf(u64) == 0) return min_size; +fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { + const size = @intCast(Int, min_size); + if (size % alignment == 0) return size; - const div = min_size / @sizeOf(u64); - return (div + 1) * @sizeOf(u64); + const div = size / alignment; + return (div + 1) * alignment; +} + +fn commandSize(min_size: anytype) u32 { + return alignSize(u32, min_size, @sizeOf(u64)); } fn addPadding(self: *MachO, size: u32, file_offset: u64) !void { From 55dc81ba2a86519a2fd5bfd985d2d6465ccf4966 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 26 Aug 2020 06:30:54 +0200 Subject: [PATCH 2/2] Hardcode runtime (libSystem) version to minimum possible While we try to work out what the correlation between the OS and runtime versions is, this commit hardcodes the latter to the minimum (compat) version of 1.0.0. Signed-off-by: Jakub Konka --- src-self-hosted/link/MachO.zig | 92 ++++++++++++++++------------------ 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 2461ba25b4..047e62f950 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -16,8 +16,6 @@ const Module = @import("../Module.zig"); const link = @import("../link.zig"); const File = link.File; -const is_darwin = std.Target.current.os.tag.isDarwin(); - pub const base_tag: File.Tag = File.Tag.macho; base: File, @@ -227,63 +225,61 @@ pub fn flush(self: *MachO, module: *Module) !void { switch (self.base.options.output_mode) { .Exe => { - if (is_darwin) { - { - // Specify path to dynamic linker dyld - const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); - const load_dylinker = [1]macho.dylinker_command{ - .{ - .cmd = macho.LC_LOAD_DYLINKER, - .cmdsize = cmdsize, - .name = @sizeOf(macho.dylinker_command), - }, - }; - try self.commands.append(self.base.allocator, .{ + { + // Specify path to dynamic linker dyld + const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); + const load_dylinker = [1]macho.dylinker_command{ + .{ .cmd = macho.LC_LOAD_DYLINKER, .cmdsize = cmdsize, - }); + .name = @sizeOf(macho.dylinker_command), + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); - const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); - try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); - self.command_file_offset.? += cmdsize; - } + try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); + self.command_file_offset.? += cmdsize; + } - { - // Link against libSystem - const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)); - // According to Apple's manual, we should obtain current libSystem version using libc call - // NSVersionOfRunTimeLibrary. - const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME); - const dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = version, - .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering - }; - const load_dylib = [1]macho.dylib_command{ - .{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = dylib, - }, - }; - try self.commands.append(self.base.allocator, .{ + { + // Link against libSystem + const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)); + // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. + // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0. + const min_version = 0x10000; + const dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = min_version, + .compatibility_version = min_version, + }; + const load_dylib = [1]macho.dylib_command{ + .{ .cmd = macho.LC_LOAD_DYLIB, .cmdsize = cmdsize, - }); + .dylib = dylib, + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); - const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); - try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); - self.command_file_offset.? += cmdsize; - } + try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); + self.command_file_offset.? += cmdsize; } }, .Obj => return error.TODOImplementWritingObjFiles,