authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-24 21:18:27+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-24 21:18:27+02:00
log43b6d0e4b17524fb016fcb396e87bf2df4e32456
treee4676f228f9112e0f4b9d1ecc155dfcd701db741
parent9745e7b5126a5429da0be67899d89803e769498c
signaturelock-open Commit is signed but in an unrecognized format.

Add (empty) __TEXT segment load command

Also, link against `libSystem` by default when targeting macOS. Signed-off-by: Jakub Konka <kubkon@jakubkonka.com>

2 files changed, 111 insertions(+), 67 deletions(-)

lib/std/macho.zig+13
......@@ -797,3 +797,16 @@ pub const CPU_SUBTYPE_X86_64_ALL: cpu_subtype_t = 0x3;
797797
798798/// All ARM-based Macs
799799pub const CPU_SUBTYPE_ARM_ALL: cpu_subtype_t = 0x0;
800
801// Protection values defined as bits within the vm_prot_t type
802/// No VM protection
803pub const VM_PROT_NONE: vm_prot_t = 0x0;
804
805/// VM read permission
806pub const VM_PROT_READ: vm_prot_t = 0x1;
807
808/// VM write permission
809pub const VM_PROT_WRITE: vm_prot_t = 0x2;
810
811/// VM execute permission
812pub const VM_PROT_EXECUTE: vm_prot_t = 0x4;
src-self-hosted/link/MachO.zig+98-67
......@@ -30,24 +30,26 @@ command_file_offset: ?u64 = null,
3030/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
3131/// Same order as in the file.
3232segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){},
33/// Section (headers) *always* follow segment (load commands) directly!
3334sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){},
34segment_table_offset: ?u64 = null,
35
36/// Offset (index) into __TEXT segment load command.
37text_segment_offset: ?u64 = null,
38/// Offset (index) into __LINKEDIT segment load command.
39linkedit_segment_offset: ?u664 = null,
3540
3641/// Entry point load command
3742entry_point_cmd: ?macho.entry_point_command = null,
3843entry_addr: ?u64 = null,
3944
40/// Default VM start address set at 4GB
45/// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment.
46/// This is also the start address for our binary.
4147vm_start_address: u64 = 0x100000000,
4248
4349seg_table_dirty: bool = false,
4450
4551error_flags: File.ErrorFlags = File.ErrorFlags{},
4652
47/// TODO ultimately this will be propagated down from main() and set (in this form or another)
48/// when user links against system lib.
49link_against_system: bool = false,
50
5153/// `alloc_num / alloc_den` is the factor of padding when allocating.
5254const alloc_num = 4;
5355const alloc_den = 3;
......@@ -138,8 +140,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
138140 .vmsize = self.vm_start_address,
139141 .fileoff = 0,
140142 .filesize = 0,
141 .maxprot = 0,
142 .initprot = 0,
143 .maxprot = macho.VM_PROT_NONE,
144 .initprot = macho.VM_PROT_NONE,
143145 .nsects = 0,
144146 .flags = 0,
145147 };
......@@ -225,66 +227,62 @@ pub fn flush(self: *MachO, module: *Module) !void {
225227
226228 switch (self.base.options.output_mode) {
227229 .Exe => {
228 if (self.link_against_system) {
229 if (is_darwin) {
230 {
231 // Specify path to dynamic linker dyld
232 const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)));
233 const load_dylinker = [1]macho.dylinker_command{
234 .{
235 .cmd = macho.LC_LOAD_DYLINKER,
236 .cmdsize = cmdsize,
237 .name = @sizeOf(macho.dylinker_command),
238 },
239 };
240 try self.commands.append(self.base.allocator, .{
230 if (is_darwin) {
231 {
232 // Specify path to dynamic linker dyld
233 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
234 const load_dylinker = [1]macho.dylinker_command{
235 .{
241236 .cmd = macho.LC_LOAD_DYLINKER,
242237 .cmdsize = cmdsize,
243 });
244
245 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
246
247 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
248 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
249
250 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
251 self.command_file_offset.? += cmdsize;
252 }
253
254 {
255 // Link against libSystem
256 const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)));
257 // According to Apple's manual, we should obtain current libSystem version using libc call
258 // NSVersionOfRunTimeLibrary.
259 const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME);
260 const dylib = .{
261 .name = @sizeOf(macho.dylib_command),
262 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
263 .current_version = version,
264 .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering
265 };
266 const load_dylib = [1]macho.dylib_command{
267 .{
268 .cmd = macho.LC_LOAD_DYLIB,
269 .cmdsize = cmdsize,
270 .dylib = dylib,
271 },
272 };
273 try self.commands.append(self.base.allocator, .{
238 .name = @sizeOf(macho.dylinker_command),
239 },
240 };
241 try self.commands.append(self.base.allocator, .{
242 .cmd = macho.LC_LOAD_DYLINKER,
243 .cmdsize = cmdsize,
244 });
245
246 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
247
248 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
249 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
250
251 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
252 self.command_file_offset.? += cmdsize;
253 }
254
255 {
256 // Link against libSystem
257 const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH));
258 // According to Apple's manual, we should obtain current libSystem version using libc call
259 // NSVersionOfRunTimeLibrary.
260 const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME);
261 const dylib = .{
262 .name = @sizeOf(macho.dylib_command),
263 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
264 .current_version = version,
265 .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering
266 };
267 const load_dylib = [1]macho.dylib_command{
268 .{
274269 .cmd = macho.LC_LOAD_DYLIB,
275270 .cmdsize = cmdsize,
276 });
271 .dylib = dylib,
272 },
273 };
274 try self.commands.append(self.base.allocator, .{
275 .cmd = macho.LC_LOAD_DYLIB,
276 .cmdsize = cmdsize,
277 });
277278
278 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
279 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
279280
280 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
281 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
281 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
282 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
282283
283 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
284 self.command_file_offset.? += cmdsize;
285 }
286 } else {
287 @panic("linking against libSystem on non-native target is unsupported");
284 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
285 self.command_file_offset.? += cmdsize;
288286 }
289287 }
290288 },
......@@ -327,20 +325,53 @@ pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
327325 @panic("TODO implement getDeclVAddr for MachO");
328326}
329327
330pub fn populateMissingMetadata(self: *MachO) !void {}
328pub fn populateMissingMetadata(self: *MachO) !void {
329 if (self.text_segment_offset == null) {
330 self.text_segment_offset = @intCast(u64, self.segments.items.len);
331 const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000);
332 log.debug("vmsize/filesize = {}", .{file_size});
333 const file_offset = 0;
334 const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM
335 const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
336 const cmdsize = commandSize(@sizeOf(macho.segment_command_64));
337 const text_segment = .{
338 .cmd = macho.LC_SEGMENT_64,
339 .cmdsize = cmdsize,
340 .segname = makeString("__TEXT"),
341 .vmaddr = vm_address,
342 .vmsize = file_size,
343 .fileoff = 0, // __TEXT segment *always* starts at 0 file offset
344 .filesize = 0, //file_size,
345 .maxprot = protection,
346 .initprot = protection,
347 .nsects = 0,
348 .flags = 0,
349 };
350 try self.commands.append(self.base.allocator, .{
351 .cmd = macho.LC_SEGMENT_64,
352 .cmdsize = cmdsize,
353 });
354 try self.segments.append(self.base.allocator, text_segment);
355 }
356}
331357
332358fn makeString(comptime bytes: []const u8) [16]u8 {
333 var buf: [16]u8 = undefined;
359 var buf = [_]u8{0} ** 16;
334360 if (bytes.len > buf.len) @compileError("MachO segment/section name too long");
335361 mem.copy(u8, buf[0..], bytes);
336362 return buf;
337363}
338364
339fn commandSize(min_size: u32) u32 {
340 if (min_size % @sizeOf(u64) == 0) return min_size;
365fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {
366 const size = @intCast(Int, min_size);
367 if (size % alignment == 0) return size;
368
369 const div = size / alignment;
370 return (div + 1) * alignment;
371}
341372
342 const div = min_size / @sizeOf(u64);
343 return (div + 1) * @sizeOf(u64);
373fn commandSize(min_size: anytype) u32 {
374 return alignSize(u32, min_size, @sizeOf(u64));
344375}
345376
346377fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {