Commit 9164daaa39

Jakub Konka <kubkon@jakubkonka.com>
2020-08-20 08:30:54
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 <kubkon@jakubkonka.com>
1 parent 91de5c2
Changed files (1)
src-self-hosted
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 {}