authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-24 09:41:14+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-24 17:59:44+02:00
log1698e6d7a7af7fa2b92f11c1c58581f297bc44cb
tree0ad4f1d2757cd1a2e3928577588f1cf78fc9ed08
parent2516db9645f3e8103ea6bff0b59d3a39fcce97a9

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>

3 files changed, 82 insertions(+), 5 deletions(-)

lib/std/c/darwin.zig+1
......@@ -11,6 +11,7 @@ const macho = std.macho;
1111usingnamespace @import("../os/bits.zig");
1212
1313extern "c" fn __error() *c_int;
14pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
1415pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int;
1516pub extern "c" fn _dyld_image_count() u32;
1617pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
lib/std/macho.zig+32
......@@ -119,6 +119,38 @@ pub const dylinker_command = extern struct {
119119 name: u32,
120120};
121121
122pub const dylib_command = extern struct {
123 /// LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_LOAD_DYLIB, LC_REEXPORT_DYLIB
124 cmd: u32,
125
126 /// includes pathname string
127 cmdsize: u32,
128
129 /// the library identification
130 dylib: dylib,
131};
132
133/// Dynamicaly linked shared libraries are identified by two things. The
134/// pathname (the name of the library as found for execution), and the
135/// compatibility version number. The pathname must match and the compatibility
136/// number in the user of the library must be greater than or equal to the
137/// library being used. The time stamp is used to record the time a library was
138/// built and copied into user so it can be use to determined if the library used
139/// at runtime is exactly the same as used to built the program.
140pub const dylib = extern struct {
141 /// library's pathname (offset pointing at the end of dylib_command)
142 name: u32,
143
144 /// library's build timestamp
145 timestamp: u32,
146
147 /// library's current version number
148 current_version: u32,
149
150 /// library's compatibility version number
151 compatibility_version: u32,
152};
153
122154/// The segment load command indicates that a part of this file is to be
123155/// mapped into the task's address space. The size of this segment in memory,
124156/// vmsize, maybe equal to or larger than the amount to map from this file,
src-self-hosted/link/MachO.zig+49-5
......@@ -49,6 +49,10 @@ const alloc_den = 3;
4949/// Default path to dyld
5050const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
5151
52/// We always have to link against libSystem since macOS Catalina (TODO link)
53const LIB_SYSTEM_NAME: [*:0]const u8 = "System";
54const LIB_SYSTEM_PATH: [*:0]const u8 = "/usr/lib/libSystem.B.dylib";
55
5256pub const TextBlock = struct {
5357 pub const empty = TextBlock{};
5458};
......@@ -226,12 +230,41 @@ pub fn flush(self: *MachO, module: *Module) !void {
226230
227231 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
228232
229 const padded_path = try self.base.allocator.alloc(u8, cmdsize - @sizeOf(macho.dylinker_command));
230 defer self.base.allocator.free(padded_path);
231 mem.set(u8, padded_path[0..], 0);
232 mem.copy(u8, padded_path[0..], mem.spanZ(DEFAULT_DYLD_PATH));
233 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
234 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
233235
234 try self.base.file.?.pwriteAll(padded_path, self.command_file_offset.? + @sizeOf(macho.dylinker_command));
236 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
237 self.command_file_offset.? += cmdsize;
238 }
239
240 {
241 // Link against libSystem
242 const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)));
243 const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME);
244 const dylib = .{
245 .name = @sizeOf(macho.dylib_command),
246 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
247 .current_version = version,
248 .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering
249 };
250 const load_dylib = [1]macho.dylib_command{
251 .{
252 .cmd = macho.LC_LOAD_DYLIB,
253 .cmdsize = cmdsize,
254 .dylib = dylib,
255 },
256 };
257 try self.commands.append(self.base.allocator, .{
258 .cmd = macho.LC_LOAD_DYLIB,
259 .cmdsize = cmdsize,
260 });
261
262 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
263
264 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
265 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
266
267 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
235268 self.command_file_offset.? += cmdsize;
236269 }
237270 },
......@@ -431,3 +464,14 @@ fn commandSize(min_size: u32) u32 {
431464 const div = min_size / @sizeOf(u64);
432465 return (div + 1) * @sizeOf(u64);
433466}
467
468fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
469 if (size == 0) return;
470
471 const buf = try self.base.allocator.alloc(u8, size);
472 defer self.base.allocator.free(buf);
473
474 mem.set(u8, buf[0..], 0);
475
476 try self.base.file.?.pwriteAll(buf, file_offset);
477}