diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 08a34075b0..ed1ddb7d91 100644 --- a/lib/std/c/darwin.zig +++ b/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; diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 935802a45f..be323a722e 100644 --- a/lib/std/macho.zig +++ b/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, diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index e2e93624f1..39c959e70b 100644 --- a/src-self-hosted/link/MachO.zig +++ b/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); +}