From 9164daaa39a0ebffcfff27d6d8c91f5857bdfe04 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 20 Aug 2020 08:30:54 +0200 Subject: [PATCH 1/2] Write page zero as first segment for Mach-O exes According to the Mach-O file format reference, the first load command should be a `__PAGEZERO` segment command. The segment is located at virtual memory location 0, has no protection rights, and causes acccesses to NULL to immediately crash. Signed-off-by: Jakub Konka --- src-self-hosted/link/MachO.zig | 60 +++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index e2a84d8ceb..5ba8b6f27b 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -6,6 +6,8 @@ const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); const macho = std.macho; +const math = std.math; +const mem = std.mem; const Module = @import("../Module.zig"); const link = @import("../link.zig"); @@ -15,6 +17,14 @@ pub const base_tag: Tag = File.Tag.macho; base: File, +/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. +/// Same order as in the file. +segment_cmds: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, + +/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. +/// Same order as in the file. +sections: std.ArrayListUnmanaged(macho.@"section_64") = std.ArrayListUnmanaged(macho.@"section_64"){}, + entry_addr: ?u64 = null, error_flags: File.ErrorFlags = File.ErrorFlags{}, @@ -86,9 +96,34 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach }; errdefer self.deinit(); + if (options.output_mode == .Exe) { + // The first segment command for executables is always a __PAGEZERO segment. + try self.segment_cmds.append(allocator, .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = self.makeString("__PAGEZERO"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }); + } + return self; } +fn makeString(self: *MachO, comptime bytes: []const u8) [16]u8 { + if (bytes.len > 16) @compileError("MachO segment/section name too long"); + + var buf: [16]u8 = undefined; + mem.copy(u8, buf[0..], bytes); + return buf; +} + fn writeMachOHeader(self: *MachO) !void { var hdr: macho.mach_header_64 = undefined; hdr.magic = macho.MH_MAGIC_64; @@ -122,9 +157,12 @@ fn writeMachOHeader(self: *MachO) !void { }; hdr.filetype = filetype; - // TODO the rest of the header - hdr.ncmds = 0; - hdr.sizeofcmds = 0; + // TODO consider other commands + const ncmds = try math.cast(u32, self.segment_cmds.items.len); + hdr.ncmds = ncmds; + hdr.sizeofcmds = ncmds * @sizeOf(macho.segment_command_64); + + // TODO should these be set to something else? hdr.flags = 0; hdr.reserved = 0; @@ -133,6 +171,17 @@ fn writeMachOHeader(self: *MachO) !void { pub fn flush(self: *MachO, module: *Module) !void { // TODO implement flush + { + const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segment_cmds.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*seg, i| { + seg.* = self.segment_cmds.items[i]; + } + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64)); + } + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; @@ -143,7 +192,10 @@ pub fn flush(self: *MachO, module: *Module) !void { } } -pub fn deinit(self: *MachO) void {} +pub fn deinit(self: *MachO) void { + self.segment_cmds.deinit(self.base.allocator); + self.@"sections".deinit(self.base.allocator); +} pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {} From ad79b80524820d3f0ec60d4d21461208227cd4c8 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 20 Aug 2020 09:14:37 +0200 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Andrew Kelley --- src-self-hosted/link/MachO.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 5ba8b6f27b..9947530398 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -23,7 +23,7 @@ segment_cmds: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUn /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. -sections: std.ArrayListUnmanaged(macho.@"section_64") = std.ArrayListUnmanaged(macho.@"section_64"){}, +sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, entry_addr: ?u64 = null, @@ -117,9 +117,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach } fn makeString(self: *MachO, comptime bytes: []const u8) [16]u8 { - if (bytes.len > 16) @compileError("MachO segment/section name too long"); - var buf: [16]u8 = undefined; + if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); mem.copy(u8, buf[0..], bytes); return buf; } @@ -194,7 +193,7 @@ pub fn flush(self: *MachO, module: *Module) !void { pub fn deinit(self: *MachO) void { self.segment_cmds.deinit(self.base.allocator); - self.@"sections".deinit(self.base.allocator); + self.sections.deinit(self.base.allocator); } pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {}