Commit 1698e6d7a7

Jakub Konka <kubkon@jakubkonka.com>
2020-08-24 09:41:14
Link against libSystem when generating Mach-O exe
This is required when generating an exe on macOS. Signed-off-by: Jakub Konka <kubkon@jakubkonka.com>
1 parent 2516db9
Changed files (3)
lib
src-self-hosted
lib/std/c/darwin.zig
@@ -11,6 +11,7 @@ const macho = std.macho;
 usingnamespace @import("../os/bits.zig");
 
 extern "c" fn __error() *c_int;
+pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
 pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int;
 pub extern "c" fn _dyld_image_count() u32;
 pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
lib/std/macho.zig
@@ -119,6 +119,38 @@ pub const dylinker_command = extern struct {
     name: u32,
 };
 
+pub const dylib_command = extern struct {
+    /// LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_LOAD_DYLIB, LC_REEXPORT_DYLIB
+    cmd: u32,
+
+    /// includes pathname string
+    cmdsize: u32,
+
+    /// the library identification
+    dylib: dylib,
+};
+
+/// Dynamicaly linked shared libraries are identified by two things.  The
+/// pathname (the name of the library as found for execution), and the
+/// compatibility version number.  The pathname must match and the compatibility
+/// number in the user of the library must be greater than or equal to the
+/// library being used.  The time stamp is used to record the time a library was
+/// built and copied into user so it can be use to determined if the library used
+/// at runtime is exactly the same as used to built the program.
+pub const dylib = extern struct {
+    /// library's pathname (offset pointing at the end of dylib_command)
+    name: u32,
+
+    /// library's build timestamp
+    timestamp: u32,
+
+    /// library's current version number
+    current_version: u32,
+
+    /// library's compatibility version number
+    compatibility_version: u32,
+};
+
 /// The segment load command indicates that a part of this file is to be
 /// mapped into the task's address space.  The size of this segment in memory,
 /// vmsize, maybe equal to or larger than the amount to map from this file,
src-self-hosted/link/MachO.zig
@@ -49,6 +49,10 @@ const alloc_den = 3;
 /// Default path to dyld
 const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
 
+/// We always have to link against libSystem since macOS Catalina (TODO link)
+const LIB_SYSTEM_NAME: [*:0]const u8 = "System";
+const LIB_SYSTEM_PATH: [*:0]const u8 = "/usr/lib/libSystem.B.dylib";
+
 pub const TextBlock = struct {
     pub const empty = TextBlock{};
 };
@@ -226,12 +230,41 @@ pub fn flush(self: *MachO, module: *Module) !void {
 
                 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
 
-                const padded_path = try self.base.allocator.alloc(u8, cmdsize - @sizeOf(macho.dylinker_command));
-                defer self.base.allocator.free(padded_path);
-                mem.set(u8, padded_path[0..], 0);
-                mem.copy(u8, padded_path[0..], mem.spanZ(DEFAULT_DYLD_PATH));
+                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(padded_path, self.command_file_offset.? + @sizeOf(macho.dylinker_command));
+                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)));
+                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.?);
+
+                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;
             }
         },
@@ -431,3 +464,14 @@ fn commandSize(min_size: u32) u32 {
     const div = min_size / @sizeOf(u64);
     return (div + 1) * @sizeOf(u64);
 }
+
+fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
+    if (size == 0) return;
+
+    const buf = try self.base.allocator.alloc(u8, size);
+    defer self.base.allocator.free(buf);
+
+    mem.set(u8, buf[0..], 0);
+
+    try self.base.file.?.pwriteAll(buf, file_offset);
+}