From 72310db1da792aae05edffe96082d97bb04cf7e5 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 12 Nov 2020 21:07:06 +0100 Subject: [PATCH 01/37] stage2 MachO: add min OS version load cmd --- lib/std/macho.zig | 17 +++++++++++++++++ src/link/MachO.zig | 26 ++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 4e4e64881246..2dcc4b4b372c 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -705,6 +705,23 @@ pub const relocation_info = packed struct { r_type: u4, }; +/// The version_min_command contains the min OS version on which this +/// binary was built to run. +pub const version_min_command = extern struct { + /// LC_VERSION_MIN_MACOSX or LC_VERSION_MIN_IPHONEOS or LC_VERSION_MIN_WATCHOS + /// or LC_VERSION_MIN_TVOS + cmd: u32, + + /// sizeof(struct min_version_command) + cmdsize: u32, + + /// X.Y.Z is encoded in nibbles xxxx.yy.zz + version: u32, + + /// X.Y.Z is encoded in nibbles xxxx.yy.zz + sdk: u32, +}; + /// After MacOS X 10.1 when a new load command is added that is required to be /// understood by the dynamic linker for the image to execute properly the /// LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 799a94bb20f6..611f3af54107 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -33,6 +33,7 @@ const LoadCommand = union(enum) { Dylinker: macho.dylinker_command, Dylib: macho.dylib_command, EntryPoint: macho.entry_point_command, + MinVersion: macho.version_min_command, pub fn cmdsize(self: LoadCommand) u32 { return switch (self) { @@ -44,6 +45,7 @@ const LoadCommand = union(enum) { .Dylinker => |x| x.cmdsize, .Dylib => |x| x.cmdsize, .EntryPoint => |x| x.cmdsize, + .MinVersion => |x| x.cmdsize, }; } @@ -57,6 +59,7 @@ const LoadCommand = union(enum) { .Dylinker => |cmd| writeGeneric(cmd, file, offset), .Dylib => |cmd| writeGeneric(cmd, file, offset), .EntryPoint => |cmd| writeGeneric(cmd, file, offset), + .MinVersion => |cmd| writeGeneric(cmd, file, offset), }; } @@ -96,6 +99,8 @@ function_starts_cmd_index: ?u16 = null, /// Specifies offset wrt __TEXT segment start address to the main entry point /// of the binary. main_cmd_index: ?u16 = null, +/// Minimum OS version +version_min_cmd_index: ?u16 = null, /// Table of all sections sections: std.ArrayListUnmanaged(macho.section_64) = .{}, @@ -317,8 +322,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.writeAllGlobalSymbols(); try self.writeAllUndefSymbols(); - try self.writeStringTable(); - switch (self.base.options.output_mode) { .Exe => { // Write export trie. @@ -379,8 +382,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const nundefs = @intCast(u32, self.undef_symbols.items.len); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; symtab.nsyms = nlocals + nglobals + nundefs; + symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); } + try self.writeStringTable(); + if (self.cmd_table_dirty) { try self.writeCmdHeaders(); try self.writeMachOHeader(); @@ -1329,8 +1335,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), @sizeOf(u64)); // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0. - const min_version = 0x10000; + // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. + const min_version = 0x0; const dylib = .{ .name = @sizeOf(macho.dylib_command), .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files @@ -1359,6 +1365,18 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } + if (self.version_min_cmd_index == null) { + self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + // TODO allow for different targets and different versions + .MinVersion = .{ + .cmd = macho.LC_VERSION_MIN_MACOSX, + .cmdsize = @sizeOf(macho.version_min_command), + .version = 0xB0001, // 11.0.1 BigSur + .sdk = 0xB0001, // 11.0.1 BigSur + }, + }); + } { const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; From 1bec531cf234f04818e22b7b792b18a88c5faf88 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 12 Nov 2020 21:29:25 +0100 Subject: [PATCH 02/37] stage2 MachO: add source version load cmd --- lib/std/macho.zig | 13 +++++++++++++ src/link/MachO.zig | 15 +++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 2dcc4b4b372c..a4a8bbdd108a 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -722,6 +722,19 @@ pub const version_min_command = extern struct { sdk: u32, }; +/// The source_version_command is an optional load command containing +/// the version of the sources used to build the binary. +pub const source_version_command = extern struct { + /// LC_SOURCE_VERSION + cmd: u32, + + /// sizeof(source_version_command) + cmdsize: u32, + + /// A.B.C.D.E packed as a24.b10.c10.d10.e10 + version: u64, +}; + /// After MacOS X 10.1 when a new load command is added that is required to be /// understood by the dynamic linker for the image to execute properly the /// LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 611f3af54107..5fa1dbbf4d71 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -34,6 +34,7 @@ const LoadCommand = union(enum) { Dylib: macho.dylib_command, EntryPoint: macho.entry_point_command, MinVersion: macho.version_min_command, + SourceVersion: macho.source_version_command, pub fn cmdsize(self: LoadCommand) u32 { return switch (self) { @@ -46,6 +47,7 @@ const LoadCommand = union(enum) { .Dylib => |x| x.cmdsize, .EntryPoint => |x| x.cmdsize, .MinVersion => |x| x.cmdsize, + .SourceVersion => |x| x.cmdsize, }; } @@ -60,6 +62,7 @@ const LoadCommand = union(enum) { .Dylib => |cmd| writeGeneric(cmd, file, offset), .EntryPoint => |cmd| writeGeneric(cmd, file, offset), .MinVersion => |cmd| writeGeneric(cmd, file, offset), + .SourceVersion => |cmd| writeGeneric(cmd, file, offset), }; } @@ -101,6 +104,8 @@ function_starts_cmd_index: ?u16 = null, main_cmd_index: ?u16 = null, /// Minimum OS version version_min_cmd_index: ?u16 = null, +/// Source version +source_version_cmd_index: ?u16 = null, /// Table of all sections sections: std.ArrayListUnmanaged(macho.section_64) = .{}, @@ -1377,6 +1382,16 @@ pub fn populateMissingMetadata(self: *MachO) !void { }, }); } + if (self.source_version_cmd_index == null) { + self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .SourceVersion = .{ + .cmd = macho.LC_SOURCE_VERSION, + .cmdsize = @sizeOf(macho.source_version_command), + .version = 0x0, + }, + }); + } { const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; From 7dd5ce25270ddd3a3ba3570e89654e305dbc2e76 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 13 Nov 2020 12:39:35 +0100 Subject: [PATCH 03/37] stage2 macOS: make exe flagged as pie --- src/link/MachO.zig | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 5fa1dbbf4d71..982bc2e09425 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -388,6 +388,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; symtab.nsyms = nlocals + nglobals + nundefs; symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); + + // Extend dynamic linker info until start of symbol table + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + dyld_info.export_size = symtab.symoff - dyld_info.export_off; } try self.writeStringTable(); @@ -1243,7 +1247,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const initprot = macho.VM_PROT_READ; try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, @@ -1253,8 +1258,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .vmsize = 0, .fileoff = 0, .filesize = 0, - .maxprot = prot, - .initprot = prot, + .maxprot = maxprot, + .initprot = initprot, .nsects = 0, .flags = 0, }, @@ -1840,8 +1845,15 @@ fn writeMachOHeader(self: *MachO) !void { hdr.sizeofcmds = sizeofcmds; - // TODO should these be set to something else? - hdr.flags = 0; + switch (self.base.options.output_mode) { + .Exe => { + hdr.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE; + }, + else => { + hdr.flags = 0; + }, + } + hdr.reserved = 0; hdr.reserved = 0; log.debug("writing Mach-O header {}\n", .{hdr}); From b3fdfe5ca055245e0184f09684329736fd020409 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 13 Nov 2020 22:20:07 +0100 Subject: [PATCH 04/37] stage2 MachO: clean up segment protection flags --- src/link/MachO.zig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 982bc2e09425..0b95dd8f5264 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1142,7 +1142,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.text_segment_cmd_index == null) { self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); - const prot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; + const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, @@ -1152,8 +1153,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .vmsize = 0, .fileoff = 0, .filesize = 0, - .maxprot = prot, - .initprot = prot, + .maxprot = maxprot, + .initprot = initprot, .nsects = 0, .flags = 0, }, @@ -1194,7 +1195,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.data_segment_cmd_index == null) { self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; + const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, @@ -1204,8 +1206,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .vmsize = 0, .fileoff = 0, .filesize = 0, - .maxprot = prot, - .initprot = prot, + .maxprot = maxprot, + .initprot = initprot, .nsects = 0, .flags = 0, }, From 6ac7e99dadfe14a0f4fb8a4fd19e9324747d9545 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 16 Nov 2020 12:04:55 +0100 Subject: [PATCH 05/37] Write local symbols when flushing --- src/link/MachO.zig | 167 +++++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 88 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 0b95dd8f5264..a185d44c109e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -322,33 +322,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - // Unfortunately these have to be buffered and done at the end because MachO does not allow - // mixing local, global and undefined symbols within a symbol table. - try self.writeAllGlobalSymbols(); - try self.writeAllUndefSymbols(); switch (self.base.options.output_mode) { .Exe => { - // Write export trie. - try self.writeExportTrie(); if (self.entry_addr) |addr| { // Update LC_MAIN with entry offset. const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint; main_cmd.entryoff = addr - text_segment.vmaddr; } - { - // Update dynamic symbol table. - const nlocals = @intCast(u32, self.local_symbols.items.len); - const nglobals = @intCast(u32, self.global_symbols.items.len); - const nundefs = @intCast(u32, self.undef_symbols.items.len); - const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - dysymtab.nlocalsym = nlocals; - dysymtab.iextdefsym = nlocals; - dysymtab.nextdefsym = nglobals; - dysymtab.iundefsym = nlocals + nglobals; - dysymtab.nundefsym = nundefs; - } if (self.dylinker_cmd_dirty) { // Write path to dyld loader. var off: usize = @sizeOf(macho.mach_header_64); @@ -375,27 +357,34 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } + + // Write export trie. + try self.writeExportTrie(); + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + linkedit.fileoff = dyld_info.export_off; + symtab.symoff = dyld_info.export_off + dyld_info.export_size; }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, } + // Unfortunately these have to be buffered and done at the end because MachO does not allow + // mixing local, global and undefined symbols within a symbol table. + try self.writeSymbolTable(); + try self.writeStringTable(); + { - // Update symbol table. - const nlocals = @intCast(u32, self.local_symbols.items.len); - const nglobals = @intCast(u32, self.global_symbols.items.len); - const nundefs = @intCast(u32, self.undef_symbols.items.len); + // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of + // doing dynamic updates like this. + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - symtab.nsyms = nlocals + nglobals + nundefs; - symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); - - // Extend dynamic linker info until start of symbol table - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - dyld_info.export_size = symtab.symoff - dyld_info.export_off; + const file_size = symtab.stroff + symtab.strsize - linkedit.fileoff; + linkedit.filesize = file_size; + linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, 0x1000); } - try self.writeStringTable(); - if (self.cmd_table_dirty) { try self.writeCmdHeaders(); try self.writeMachOHeader(); @@ -984,7 +973,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { symbol.n_sect = @intCast(u8, self.text_section_index.?) + 1; symbol.n_desc = 0; // TODO this write could be avoided if no fields of the symbol were changed. - try self.writeSymbol(decl.link.macho.local_sym_index); + // try self.writeSymbol(decl.link.macho.local_sym_index); } else { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); @@ -1001,7 +990,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }; self.offset_table.items[decl.link.macho.offset_table_index] = addr; - try self.writeSymbol(decl.link.macho.local_sym_index); + // try self.writeSymbol(decl.link.macho.local_sym_index); try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1399,48 +1388,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { }, }); } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - if (dyld_info.export_off == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(u64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); - log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - dyld_info.export_off = off; - dyld_info.export_size = @intCast(u32, file_size); - - const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); - linkedit.vmsize += segment_size; - linkedit.fileoff = off; - } - } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - if (symtab.symoff == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(macho.nlist_64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); - log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.symoff = off; - symtab.nsyms = @intCast(u32, nsyms); - - const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); - linkedit.vmsize += segment_size; - } - if (symtab.stroff == 0) { - try self.string_table.append(self.base.allocator, 0); - const file_size = @intCast(u32, self.string_table.items.len); - const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); - log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.stroff = off; - symtab.strsize = file_size; - - const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); - linkedit.vmsize += segment_size; - } - } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); const name = try self.makeString("dyld_stub_binder"); @@ -1687,6 +1634,53 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&buf, off); } +fn writeSymbolTable(self: *MachO) !void { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const locals_off = symtab.symoff; + + var locals = try self.base.allocator.alloc(macho.nlist_64, self.local_symbols.items.len - 1); + defer self.base.allocator.free(locals); + + for (locals) |*sym, i| { + sym.* = .{ + .n_strx = self.local_symbols.items[i + 1].n_strx, + .n_type = self.local_symbols.items[i + 1].n_type, + .n_sect = self.local_symbols.items[i + 1].n_sect, + .n_desc = self.local_symbols.items[i + 1].n_desc, + .n_value = self.local_symbols.items[i + 1].n_value, + }; + } + + const locals_size = locals.len * @sizeOf(macho.nlist_64); + log.debug("writing local symbols from 0x{x} to 0x{x}\n", .{ locals_off, locals_size + locals_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(locals), locals_off); + + const globals_off = locals_off + locals_size; + const globals_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ globals_off, globals_size + globals_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); + + const undefs_off = globals_off + globals_size; + const undefs_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ undefs_off, undefs_size + undefs_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off); + + // Update symbol table. + const nlocals = @intCast(u32, locals.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); + symtab.nsyms = nlocals + nglobals + nundefs; + symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = nlocals; + dysymtab.iextdefsym = nlocals; + dysymtab.nextdefsym = nglobals; + dysymtab.iundefsym = nlocals + nglobals; + dysymtab.nundefsym = nundefs; +} + fn writeAllGlobalSymbols(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64); @@ -1728,29 +1722,26 @@ fn writeExportTrie(self: *MachO) !void { try trie.writeULEB128Mem(self.base.allocator, &buffer); + const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + dyld_info.export_off = @intCast(u32, data.fileoff + data.filesize); + dyld_info.export_size = mem.alignForwardGeneric(u32, @intCast(u32, buffer.items.len), @sizeOf(u64)); try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off); } fn writeStringTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const allocated_size = self.allocatedSize(symtab.stroff); + // const allocated_size = self.allocatedSize(symtab.stroff); const needed_size = self.string_table.items.len; - if (needed_size > allocated_size) { - symtab.strsize = 0; - symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); - } - symtab.strsize = @intCast(u32, needed_size); - - log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); - + // if (needed_size > allocated_size) { + // symtab.strsize = 0; + // symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); + // } + symtab.strsize = mem.alignForwardGeneric(u32, @intCast(u32, needed_size), @sizeOf(u64)); + try self.base.file.?.pwriteAll(&[_]u8{ 0 }, symtab.stroff + symtab.strsize - 1); + log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + needed_size }); try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); - - // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of - // doing dynamic updates like this. - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; } fn writeCmdHeaders(self: *MachO) !void { From 7a407246ed503d93218bb0292150ffbf12dfb735 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 16 Nov 2020 16:13:44 +0100 Subject: [PATCH 06/37] stage2 MachO: remove discontinuities between segments --- src/link/MachO.zig | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a185d44c109e..4492054b577c 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1,5 +1,4 @@ const MachO = @This(); - const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -375,6 +374,12 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.writeSymbolTable(); try self.writeStringTable(); + { + // Seal __DATA,__got section size + const got = &self.sections.items[self.got_section_index.?]; + got.size = @intCast(u32, self.offset_table.items.len * @sizeOf(u64)); + } + { // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of // doing dynamic updates like this. @@ -947,7 +952,8 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }, }; - const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); + // const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); + const required_alignment = 4; assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes() const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; @@ -997,7 +1003,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const text_section = self.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; const file_offset = text_section.offset + section_offset; - try self.base.file.?.pwriteAll(code, file_offset); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. @@ -1168,7 +1173,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .addr = text_segment.vmaddr + off, .size = file_size, .offset = off, - .@"align" = 12, // 2^12 = 4096 + .@"align" = 2, // 2^12 = 4096 .reloff = 0, .nreloc = 0, .flags = flags, @@ -1193,7 +1198,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .segname = makeStaticString("__DATA"), .vmaddr = text_segment.vmaddr + text_segment.vmsize, .vmsize = 0, - .fileoff = 0, + .fileoff = text_segment.fileoff + text_segment.filesize, .filesize = 0, .maxprot = maxprot, .initprot = initprot, @@ -1210,7 +1215,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { data_segment.nsects += 1; const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); + // const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); + const off = @intCast(u32, data_segment.fileoff); log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); From 4a3d757f3e57d115ef2f6889f52a06e013bcfc06 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 16 Nov 2020 16:22:20 +0100 Subject: [PATCH 07/37] stage2 MachO: reduce size of __TEXT segment --- src/link/MachO.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4492054b577c..ffa409fbe1b1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1161,7 +1161,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { text_segment.cmdsize += @sizeOf(macho.section_64); text_segment.nsects += 1; - const file_size = mem.alignForwardGeneric(u64, self.base.options.program_code_size_hint, 0x1000); + // const program_code_size_hint = self.base.options.program_code_size_hint; + const program_code_size_hint = 128; + const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, 0x1000); const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; From 8450e6f156e365dd14756f74e748f755fc0cc5ab Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 16 Nov 2020 20:16:34 +0100 Subject: [PATCH 08/37] stage2 macho: pages need to be 16kb aligned! --- src/link/MachO.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index ffa409fbe1b1..5f37b0ae7d7b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -387,7 +387,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const file_size = symtab.stroff + symtab.strsize - linkedit.fileoff; linkedit.filesize = file_size; - linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, 0x1000); + linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, 0x4000); } if (self.cmd_table_dirty) { @@ -1163,8 +1163,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { // const program_code_size_hint = self.base.options.program_code_size_hint; const program_code_size_hint = 128; - const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, 0x1000); - const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); // TODO maybe findFreeSpace should return u32 directly? + const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, 0x4000); + const off = @intCast(u32, self.findFreeSpace(file_size, 0x4000)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -1237,7 +1237,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved3 = 0, }); - const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); + const segment_size = mem.alignForwardGeneric(u64, file_size, 0x4000); data_segment.vmsize = segment_size; data_segment.filesize = segment_size; data_segment.fileoff = off; From 4e3520a2f750474471308c520af072631dc31a9d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 09:57:35 +0100 Subject: [PATCH 09/37] stage2 macho: make page size target cpu arch dependent --- src/link/MachO.zig | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 5f37b0ae7d7b..18ddc68f9929 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -73,6 +73,10 @@ const LoadCommand = union(enum) { base: File, +/// Page size is dependent on the target cpu architecture. +/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB. +page_size: u16, + /// Table of all load commands load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, /// __PAGEZERO segment @@ -301,6 +305,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*MachO { .allocator = gpa, .file = null, }, + .page_size = if (options.target.cpu.arch == .aarch64) 0x4000 else 0x1000, }; return self; } @@ -387,7 +392,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const file_size = symtab.stroff + symtab.strsize - linkedit.fileoff; linkedit.filesize = file_size; - linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, 0x4000); + linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, self.page_size); } if (self.cmd_table_dirty) { @@ -1163,8 +1168,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { // const program_code_size_hint = self.base.options.program_code_size_hint; const program_code_size_hint = 128; - const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, 0x4000); - const off = @intCast(u32, self.findFreeSpace(file_size, 0x4000)); // TODO maybe findFreeSpace should return u32 directly? + const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, self.page_size); + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -1237,7 +1242,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved3 = 0, }); - const segment_size = mem.alignForwardGeneric(u64, file_size, 0x4000); + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); data_segment.vmsize = segment_size; data_segment.filesize = segment_size; data_segment.fileoff = off; From 2972f630a4ff6dc8da556c68a7f5c77de75f2514 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 10:13:23 +0100 Subject: [PATCH 10/37] stage2 macho: start reverting some tweaks --- src/link/MachO.zig | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 18ddc68f9929..689bf388f6f4 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -326,7 +326,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - switch (self.base.options.output_mode) { .Exe => { if (self.entry_addr) |addr| { @@ -361,7 +360,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } - + // Write export trie. try self.writeExportTrie(); const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; @@ -1166,8 +1165,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { text_segment.cmdsize += @sizeOf(macho.section_64); text_segment.nsects += 1; - // const program_code_size_hint = self.base.options.program_code_size_hint; - const program_code_size_hint = 128; + const program_code_size_hint = self.base.options.program_code_size_hint; + // const program_code_size_hint = 128; const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, self.page_size); const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; @@ -1180,7 +1179,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .addr = text_segment.vmaddr + off, .size = file_size, .offset = off, - .@"align" = 2, // 2^12 = 4096 + .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 1, // 2^2 for aarch64, 2^1 for x86_64 .reloff = 0, .nreloc = 0, .flags = flags, @@ -1650,7 +1649,7 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { fn writeSymbolTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const locals_off = symtab.symoff; - + var locals = try self.base.allocator.alloc(macho.nlist_64, self.local_symbols.items.len - 1); defer self.base.allocator.free(locals); @@ -1752,7 +1751,7 @@ fn writeStringTable(self: *MachO) !void { // symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); // } symtab.strsize = mem.alignForwardGeneric(u32, @intCast(u32, needed_size), @sizeOf(u64)); - try self.base.file.?.pwriteAll(&[_]u8{ 0 }, symtab.stroff + symtab.strsize - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, symtab.stroff + symtab.strsize - 1); log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + needed_size }); try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); } From e8dd62accd1c0f62b0d6193f35f653a464e15347 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 10:16:09 +0100 Subject: [PATCH 11/37] stage2 macho: fix incorrect rebase --- lib/std/macho.zig | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index a4a8bbdd108a..4e4e64881246 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -705,36 +705,6 @@ pub const relocation_info = packed struct { r_type: u4, }; -/// The version_min_command contains the min OS version on which this -/// binary was built to run. -pub const version_min_command = extern struct { - /// LC_VERSION_MIN_MACOSX or LC_VERSION_MIN_IPHONEOS or LC_VERSION_MIN_WATCHOS - /// or LC_VERSION_MIN_TVOS - cmd: u32, - - /// sizeof(struct min_version_command) - cmdsize: u32, - - /// X.Y.Z is encoded in nibbles xxxx.yy.zz - version: u32, - - /// X.Y.Z is encoded in nibbles xxxx.yy.zz - sdk: u32, -}; - -/// The source_version_command is an optional load command containing -/// the version of the sources used to build the binary. -pub const source_version_command = extern struct { - /// LC_SOURCE_VERSION - cmd: u32, - - /// sizeof(source_version_command) - cmdsize: u32, - - /// A.B.C.D.E packed as a24.b10.c10.d10.e10 - version: u64, -}; - /// After MacOS X 10.1 when a new load command is added that is required to be /// understood by the dynamic linker for the image to execute properly the /// LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic From be0d5571d2c183ea7504faac13802f0e271f180b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 10:25:37 +0100 Subject: [PATCH 12/37] stage2 macho: revert required alignment always at 4 --- src/link/MachO.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 689bf388f6f4..bfc8537342a5 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -956,8 +956,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }, }; - // const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); - const required_alignment = 4; + const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes() const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; From e1b65ff8d211066d0856ad3535b96dcdc88e76dc Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 13:04:25 +0100 Subject: [PATCH 13/37] stage2 macho: cleanup minimum version command --- src/link/MachO.zig | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index bfc8537342a5..ff90c5a6c169 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1379,13 +1379,21 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.version_min_cmd_index == null) { self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmd: u32 = switch (self.base.options.target.os.tag) { + .macos => macho.LC_VERSION_MIN_MACOSX, + .ios => macho.LC_VERSION_MIN_IPHONEOS, + .tvos => macho.LC_VERSION_MIN_TVOS, + .watchos => macho.LC_VERSION_MIN_WATCHOS, + else => unreachable, // wrong OS + }; + const ver = self.base.options.target.os.version_range.semver.min; + const version = ver.major << 16 | ver.minor << 8 | ver.patch; try self.load_commands.append(self.base.allocator, .{ - // TODO allow for different targets and different versions .MinVersion = .{ - .cmd = macho.LC_VERSION_MIN_MACOSX, + .cmd = cmd, .cmdsize = @sizeOf(macho.version_min_command), - .version = 0xB0001, // 11.0.1 BigSur - .sdk = 0xB0001, // 11.0.1 BigSur + .version = version, + .sdk = version, }, }); } From a2e0e33249c786e766e185223875b3cef6c946fb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 14:57:47 +0100 Subject: [PATCH 14/37] stage2 macho: bring back incremental symbol commits --- src/link/MachO.zig | 177 +++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 86 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index ff90c5a6c169..f78e6d2d173f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -326,14 +326,36 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); + // Unfortunately these have to be buffered and done at the end because MachO does not allow + // mixing local, global and undefined symbols within a symbol table. + try self.writeAllGlobalSymbols(); + try self.writeAllUndefSymbols(); + + // TODO uncomment when we add our own codesigning mechanim + // try self.writeStringTable(); + switch (self.base.options.output_mode) { .Exe => { + // Write export trie. + try self.writeExportTrie(); if (self.entry_addr) |addr| { // Update LC_MAIN with entry offset. const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint; main_cmd.entryoff = addr - text_segment.vmaddr; } + { + // Update dynamic symbol table. + const nlocals = @intCast(u32, self.local_symbols.items.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = nlocals; + dysymtab.iextdefsym = nlocals; + dysymtab.nextdefsym = nglobals; + dysymtab.iundefsym = nlocals + nglobals; + dysymtab.nundefsym = nundefs; + } if (self.dylinker_cmd_dirty) { // Write path to dyld loader. var off: usize = @sizeOf(macho.mach_header_64); @@ -360,40 +382,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } - - // Write export trie. - try self.writeExportTrie(); - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - linkedit.fileoff = dyld_info.export_off; - symtab.symoff = dyld_info.export_off + dyld_info.export_size; }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, } - // Unfortunately these have to be buffered and done at the end because MachO does not allow - // mixing local, global and undefined symbols within a symbol table. - try self.writeSymbolTable(); - try self.writeStringTable(); - - { - // Seal __DATA,__got section size - const got = &self.sections.items[self.got_section_index.?]; - got.size = @intCast(u32, self.offset_table.items.len * @sizeOf(u64)); - } - { - // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of - // doing dynamic updates like this. - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + // Update symbol table. + const nlocals = @intCast(u32, self.local_symbols.items.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const file_size = symtab.stroff + symtab.strsize - linkedit.fileoff; - linkedit.filesize = file_size; - linkedit.vmsize = mem.alignForwardGeneric(u64, file_size, self.page_size); + symtab.nsyms = nlocals + nglobals + nundefs; + // TODO could we drop this when we add our own codesigning mechanims? + symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); } + // TODO remove when we add our own codesigning mechanism + try self.writeStringTable(); + if (self.cmd_table_dirty) { try self.writeCmdHeaders(); try self.writeMachOHeader(); @@ -982,7 +989,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { symbol.n_sect = @intCast(u8, self.text_section_index.?) + 1; symbol.n_desc = 0; // TODO this write could be avoided if no fields of the symbol were changed. - // try self.writeSymbol(decl.link.macho.local_sym_index); + try self.writeSymbol(decl.link.macho.local_sym_index); } else { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); @@ -999,7 +1006,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }; self.offset_table.items[decl.link.macho.offset_table_index] = addr; - // try self.writeSymbol(decl.link.macho.local_sym_index); + try self.writeSymbol(decl.link.macho.local_sym_index); try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1220,8 +1227,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { data_segment.nsects += 1; const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - // const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); - const off = @intCast(u32, data_segment.fileoff); + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -1288,6 +1294,22 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + if (dyld_info.export_off == 0) { + const nsyms = self.base.options.symbol_count_hint; + const file_size = @sizeOf(u64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + dyld_info.export_off = off; + dyld_info.export_size = @intCast(u32, file_size); + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + linkedit.fileoff = off; + } + } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ @@ -1330,6 +1352,35 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + if (symtab.symoff == 0) { + const nsyms = self.base.options.symbol_count_hint; + const file_size = @sizeOf(macho.nlist_64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.symoff = off; + symtab.nsyms = @intCast(u32, nsyms); + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + // TODO this is needed to please codesign_allocate + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + dyld_info.export_size = off - dyld_info.export_off; + } + if (symtab.stroff == 0) { + try self.string_table.append(self.base.allocator, 0); + const file_size = @intCast(u32, self.string_table.items.len); + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.stroff = off; + symtab.strsize = file_size; + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + } + } if (self.dylinker_cmd_index == null) { self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), @sizeOf(u64)); @@ -1653,53 +1704,6 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&buf, off); } -fn writeSymbolTable(self: *MachO) !void { - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const locals_off = symtab.symoff; - - var locals = try self.base.allocator.alloc(macho.nlist_64, self.local_symbols.items.len - 1); - defer self.base.allocator.free(locals); - - for (locals) |*sym, i| { - sym.* = .{ - .n_strx = self.local_symbols.items[i + 1].n_strx, - .n_type = self.local_symbols.items[i + 1].n_type, - .n_sect = self.local_symbols.items[i + 1].n_sect, - .n_desc = self.local_symbols.items[i + 1].n_desc, - .n_value = self.local_symbols.items[i + 1].n_value, - }; - } - - const locals_size = locals.len * @sizeOf(macho.nlist_64); - log.debug("writing local symbols from 0x{x} to 0x{x}\n", .{ locals_off, locals_size + locals_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(locals), locals_off); - - const globals_off = locals_off + locals_size; - const globals_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); - log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ globals_off, globals_size + globals_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); - - const undefs_off = globals_off + globals_size; - const undefs_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64); - log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ undefs_off, undefs_size + undefs_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off); - - // Update symbol table. - const nlocals = @intCast(u32, locals.len); - const nglobals = @intCast(u32, self.global_symbols.items.len); - const nundefs = @intCast(u32, self.undef_symbols.items.len); - symtab.nsyms = nlocals + nglobals + nundefs; - symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); - - // Update dynamic symbol table. - const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - dysymtab.nlocalsym = nlocals; - dysymtab.iextdefsym = nlocals; - dysymtab.nextdefsym = nglobals; - dysymtab.iundefsym = nlocals + nglobals; - dysymtab.nundefsym = nundefs; -} - fn writeAllGlobalSymbols(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64); @@ -1741,26 +1745,27 @@ fn writeExportTrie(self: *MachO) !void { try trie.writeULEB128Mem(self.base.allocator, &buffer); - const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - dyld_info.export_off = @intCast(u32, data.fileoff + data.filesize); - dyld_info.export_size = mem.alignForwardGeneric(u32, @intCast(u32, buffer.items.len), @sizeOf(u64)); try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off); } fn writeStringTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - // const allocated_size = self.allocatedSize(symtab.stroff); + const allocated_size = self.allocatedSize(symtab.stroff); const needed_size = self.string_table.items.len; - // if (needed_size > allocated_size) { - // symtab.strsize = 0; - // symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); - // } - symtab.strsize = mem.alignForwardGeneric(u32, @intCast(u32, needed_size), @sizeOf(u64)); - try self.base.file.?.pwriteAll(&[_]u8{0}, symtab.stroff + symtab.strsize - 1); + if (needed_size > allocated_size) { + symtab.strsize = 0; + symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } + symtab.strsize = @intCast(u32, needed_size); log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + needed_size }); try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); + + // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of + // doing dynamic updates like this. + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; } fn writeCmdHeaders(self: *MachO) !void { From bbc4ee3f175d8b6c3bf8f6bdf03a7ce05b2935ea Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 17 Nov 2020 14:59:31 +0100 Subject: [PATCH 15/37] stage2 macho: refactor --- src/link/MachO.zig | 97 +++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index f78e6d2d173f..474795ea5792 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1,4 +1,5 @@ const MachO = @This(); + const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -1172,7 +1173,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { text_segment.nsects += 1; const program_code_size_hint = self.base.options.program_code_size_hint; - // const program_code_size_hint = 128; const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, self.page_size); const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; @@ -1294,22 +1294,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - if (dyld_info.export_off == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(u64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - dyld_info.export_off = off; - dyld_info.export_size = @intCast(u32, file_size); - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - linkedit.fileoff = off; - } - } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ @@ -1352,35 +1336,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - if (symtab.symoff == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(macho.nlist_64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.symoff = off; - symtab.nsyms = @intCast(u32, nsyms); - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - // TODO this is needed to please codesign_allocate - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - dyld_info.export_size = off - dyld_info.export_off; - } - if (symtab.stroff == 0) { - try self.string_table.append(self.base.allocator, 0); - const file_size = @intCast(u32, self.string_table.items.len); - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.stroff = off; - symtab.strsize = file_size; - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - } - } if (self.dylinker_cmd_index == null) { self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), @sizeOf(u64)); @@ -1458,6 +1413,51 @@ pub fn populateMissingMetadata(self: *MachO) !void { }, }); } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + if (dyld_info.export_off == 0) { + const nsyms = self.base.options.symbol_count_hint; + const file_size = @sizeOf(u64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + dyld_info.export_off = off; + dyld_info.export_size = @intCast(u32, file_size); + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + linkedit.fileoff = off; + } + } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + if (symtab.symoff == 0) { + const nsyms = self.base.options.symbol_count_hint; + const file_size = @sizeOf(macho.nlist_64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.symoff = off; + symtab.nsyms = @intCast(u32, nsyms); + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + // TODO this is needed to please codesign_allocate + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + dyld_info.export_size = off - dyld_info.export_off; + } + if (symtab.stroff == 0) { + try self.string_table.append(self.base.allocator, 0); + const file_size = @intCast(u32, self.string_table.items.len); + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.stroff = off; + symtab.strsize = file_size; + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; + } + } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); const name = try self.makeString("dyld_stub_binder"); @@ -1759,7 +1759,9 @@ fn writeStringTable(self: *MachO) !void { symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); } symtab.strsize = @intCast(u32, needed_size); - log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + needed_size }); + + log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of @@ -1871,7 +1873,6 @@ fn writeMachOHeader(self: *MachO) !void { }, } hdr.reserved = 0; - hdr.reserved = 0; log.debug("writing Mach-O header {}\n", .{hdr}); From 403dc50ff77a319d8d1e4ceb60bcb254e4f17f06 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 19 Nov 2020 18:52:48 +0100 Subject: [PATCH 16/37] stage2 macho: preallocate empty code sig space --- src/link/MachO.zig | 48 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 474795ea5792..afc1f4289ca4 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -110,6 +110,8 @@ main_cmd_index: ?u16 = null, version_min_cmd_index: ?u16 = null, /// Source version source_version_cmd_index: ?u16 = null, +/// Code signature +code_signature_cmd_index: ?u16 = null, /// Table of all sections sections: std.ArrayListUnmanaged(macho.section_64) = .{}, @@ -401,6 +403,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { // TODO remove when we add our own codesigning mechanism try self.writeStringTable(); + try self.codeSign(); + + { + // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of + // doing dynamic updates like this. + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const code_sig = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + linkedit.filesize = code_sig.dataoff + code_sig.datasize - linkedit.fileoff; + } if (self.cmd_table_dirty) { try self.writeCmdHeaders(); @@ -1413,6 +1424,17 @@ pub fn populateMissingMetadata(self: *MachO) !void { }, }); } + if (self.code_signature_cmd_index == null) { + self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .LinkeditData = .{ + .cmd = macho.LC_CODE_SIGNATURE, + .cmdsize = @sizeOf(macho.linkedit_data_command), + .dataoff = 0, + .datasize = 0, + }, + }); + } { const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; @@ -1458,6 +1480,17 @@ pub fn populateMissingMetadata(self: *MachO) !void { linkedit.vmsize += segment_size; } } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const code_sig = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + if (code_sig.dataoff == 0) { + const file_size = 0x1000; // TODO what is a good guesstimate for initial code signature? + const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + code_sig.dataoff = off; + code_sig.datasize = file_size; + } + } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); const name = try self.makeString("dyld_stub_binder"); @@ -1722,6 +1755,16 @@ fn writeAllUndefSymbols(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); } +fn codeSign(self: *MachO) !void { + const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const off = mem.alignForwardGeneric(u32, symtab.stroff + symtab.strsize, @sizeOf(u64)); + symtab.strsize = off - symtab.stroff; + code_sig_cmd.dataoff = off; + // TODO add actual code signing mechanism + try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); +} + fn writeExportTrie(self: *MachO) !void { if (self.global_symbols.items.len == 0) return; // No exports, nothing to do. @@ -1763,11 +1806,6 @@ fn writeStringTable(self: *MachO) !void { log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); - - // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of - // doing dynamic updates like this. - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; } fn writeCmdHeaders(self: *MachO) !void { From 2bd963ad6ac83ef5b1c7499f4980a6573401ae4e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 19 Nov 2020 21:55:30 +0100 Subject: [PATCH 17/37] stage2 macho: don't pad out holes between sections --- src/link/MachO.zig | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index afc1f4289ca4..43d84bd9e35b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -334,8 +334,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.writeAllGlobalSymbols(); try self.writeAllUndefSymbols(); - // TODO uncomment when we add our own codesigning mechanim - // try self.writeStringTable(); + try self.writeStringTable(); switch (self.base.options.output_mode) { .Exe => { @@ -385,6 +384,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } + + try self.codeSign(); }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, @@ -397,20 +398,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const nundefs = @intCast(u32, self.undef_symbols.items.len); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; symtab.nsyms = nlocals + nglobals + nundefs; - // TODO could we drop this when we add our own codesigning mechanims? - symtab.stroff = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); } - // TODO remove when we add our own codesigning mechanism - try self.writeStringTable(); - try self.codeSign(); - - { - // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of - // doing dynamic updates like this. + // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of + // doing dynamic updates like this. + if (self.code_signature_cmd_index) |i| { const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const code_sig = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; linkedit.filesize = code_sig.dataoff + code_sig.datasize - linkedit.fileoff; + } else { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; } if (self.cmd_table_dirty) { @@ -1464,9 +1463,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); linkedit.vmsize += segment_size; - // TODO this is needed to please codesign_allocate - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - dyld_info.export_size = off - dyld_info.export_off; } if (symtab.stroff == 0) { try self.string_table.append(self.base.allocator, 0); @@ -1757,10 +1753,6 @@ fn writeAllUndefSymbols(self: *MachO) !void { fn codeSign(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const off = mem.alignForwardGeneric(u32, symtab.stroff + symtab.strsize, @sizeOf(u64)); - symtab.strsize = off - symtab.stroff; - code_sig_cmd.dataoff = off; // TODO add actual code signing mechanism try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } From 3ac804628f26ad889701e746d44b21d369cff98d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 00:35:46 +0100 Subject: [PATCH 18/37] stage2 macho: write out constants in CS --- src/link/MachO.zig | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 43d84bd9e35b..8dc724dfcb26 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1754,6 +1754,57 @@ fn writeAllUndefSymbols(self: *MachO) !void { fn codeSign(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; // TODO add actual code signing mechanism + var buffer: std.ArrayListUnmanaged(u8) = .{}; + defer buffer.deinit(self.base.allocator); + // var super_blob = macho.SuperBlob{ + // .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, + // .length = 0, + // .count = 2, + // }; + const length: u32 = @sizeOf(u32) * 12; + try buffer.ensureCapacity(self.base.allocator, length); + var buf: [@sizeOf(u32)]u8 = undefined; + mem.writeIntBig(u32, &buf, macho.CSMAGIC_EMBEDDED_SIGNATURE); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, length); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, 2); + buffer.appendSliceAssumeCapacity(buf[0..]); + // macho.BlobIndex{ + // .type = macho.CSSLOT_REQUIREMENTS, + // .offset = offset, + // }; + mem.writeIntBig(u32, &buf, macho.CSSLOT_REQUIREMENTS); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, 28); + buffer.appendSliceAssumeCapacity(buf[0..]); + // macho.BlobIndex{ + // .type = macho.CSSLOT_SIGNATURESLOT, + // .offset = offset, + // }; + mem.writeIntBig(u32, &buf, macho.CSSLOT_SIGNATURESLOT); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, 40); + buffer.appendSliceAssumeCapacity(buf[0..]); + // const requirements_blob = macho.GenericBlob{ + // .magic = macho.CSMAGIC_REQUIREMENTS, + // .length = 0, + // }; + mem.writeIntBig(u32, &buf, macho.CSMAGIC_REQUIREMENTS); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, @sizeOf(u32) * 3); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, 0); + buffer.appendSliceAssumeCapacity(buf[0..]); + // const signature_blob = macho.GenericBlob{ + // .magic = macho.CSSLOT_SIGNATURESLOT, + // .length = 0, + // }; + mem.writeIntBig(u32, &buf, macho.CSMAGIC_BLOBWRAPPER); + buffer.appendSliceAssumeCapacity(buf[0..]); + mem.writeIntBig(u32, &buf, @sizeOf(u32) * 2); + buffer.appendSliceAssumeCapacity(buf[0..]); + try self.base.file.?.pwriteAll(buffer.items[0..], code_sig_cmd.dataoff); try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } From d14cd59ef0d9f03114ea6467a8446c17bf33ab9a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 10:11:16 +0100 Subject: [PATCH 19/37] stage2 macho: move code signature logic into struct --- src/link/MachO.zig | 69 +++++++------------------------- src/link/MachO/CodeSignature.zig | 59 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 54 deletions(-) create mode 100644 src/link/MachO/CodeSignature.zig diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8dc724dfcb26..431dcf714c0b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -21,6 +21,7 @@ const Cache = @import("../Cache.zig"); const target_util = @import("../target.zig"); const Trie = @import("MachO/Trie.zig"); +const CodeSignature = @import("MachO/CodeSignature.zig"); pub const base_tag: File.Tag = File.Tag.macho; @@ -385,7 +386,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { self.libsystem_cmd_dirty = false; } - try self.codeSign(); + try self.writecodeSignature(); }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, @@ -1751,60 +1752,20 @@ fn writeAllUndefSymbols(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); } -fn codeSign(self: *MachO) !void { +fn writecodeSignature(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; - // TODO add actual code signing mechanism - var buffer: std.ArrayListUnmanaged(u8) = .{}; - defer buffer.deinit(self.base.allocator); - // var super_blob = macho.SuperBlob{ - // .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, - // .length = 0, - // .count = 2, - // }; - const length: u32 = @sizeOf(u32) * 12; - try buffer.ensureCapacity(self.base.allocator, length); - var buf: [@sizeOf(u32)]u8 = undefined; - mem.writeIntBig(u32, &buf, macho.CSMAGIC_EMBEDDED_SIGNATURE); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, length); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, 2); - buffer.appendSliceAssumeCapacity(buf[0..]); - // macho.BlobIndex{ - // .type = macho.CSSLOT_REQUIREMENTS, - // .offset = offset, - // }; - mem.writeIntBig(u32, &buf, macho.CSSLOT_REQUIREMENTS); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, 28); - buffer.appendSliceAssumeCapacity(buf[0..]); - // macho.BlobIndex{ - // .type = macho.CSSLOT_SIGNATURESLOT, - // .offset = offset, - // }; - mem.writeIntBig(u32, &buf, macho.CSSLOT_SIGNATURESLOT); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, 40); - buffer.appendSliceAssumeCapacity(buf[0..]); - // const requirements_blob = macho.GenericBlob{ - // .magic = macho.CSMAGIC_REQUIREMENTS, - // .length = 0, - // }; - mem.writeIntBig(u32, &buf, macho.CSMAGIC_REQUIREMENTS); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, @sizeOf(u32) * 3); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, 0); - buffer.appendSliceAssumeCapacity(buf[0..]); - // const signature_blob = macho.GenericBlob{ - // .magic = macho.CSSLOT_SIGNATURESLOT, - // .length = 0, - // }; - mem.writeIntBig(u32, &buf, macho.CSMAGIC_BLOBWRAPPER); - buffer.appendSliceAssumeCapacity(buf[0..]); - mem.writeIntBig(u32, &buf, @sizeOf(u32) * 2); - buffer.appendSliceAssumeCapacity(buf[0..]); - try self.base.file.?.pwriteAll(buffer.items[0..], code_sig_cmd.dataoff); + + var code_sig = CodeSignature.init(self.base.allocator); + defer code_sig.deinit(); + + try code_sig.calcAdhocSignature(); + + var buffer = try self.base.allocator.alloc(u8, code_sig.size()); + defer self.base.allocator.free(buffer); + + code_sig.write(buffer); + + try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig new file mode 100644 index 000000000000..c03829daceb1 --- /dev/null +++ b/src/link/MachO/CodeSignature.zig @@ -0,0 +1,59 @@ +const CodeSignature = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); +const macho = std.macho; +const mem = std.mem; +const testing = std.testing; +const Allocator = mem.Allocator; + +// pub const Blob = union(enum) { +// Signature: struct{ +// inner: +// } +// }; + +alloc: *Allocator, +inner: macho.SuperBlob = .{ + .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, + .length = @sizeOf(macho.SuperBlob), + .count = 0, +}, +// blobs: std.ArrayList(Blob), + +pub fn init(alloc: *Allocator) CodeSignature { + return .{ + .alloc = alloc, + // .indices = std.ArrayList(Blob).init(alloc), + }; +} + +pub fn calcAdhocSignature(self: *CodeSignature) !void {} + +pub fn size(self: CodeSignature) u32 { + return self.inner.length; +} + +pub fn write(self: CodeSignature, buffer: []u8) void { + assert(buffer.len >= self.inner.length); + self.writeHeader(buffer); +} + +pub fn deinit(self: *CodeSignature) void {} + +fn writeHeader(self: CodeSignature, buffer: []u8) void { + assert(buffer.len >= @sizeOf(macho.SuperBlob)); + mem.writeIntBig(u32, buffer[0..4], self.inner.magic); + mem.writeIntBig(u32, buffer[4..8], self.inner.length); + mem.writeIntBig(u32, buffer[8..12], self.inner.count); +} + +test "CodeSignature header" { + var code_sig = CodeSignature.init(testing.allocator); + defer code_sig.deinit(); + var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined; + code_sig.writeHeader(buffer[0..]); + const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 }; + testing.expect(mem.eql(u8, expected[0..], buffer[0..])); +} From 79381dc4fb1f7eb92b5e01d21f866f550ca3ff20 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 11:05:16 +0100 Subject: [PATCH 20/37] stage2 macho: add empty CodeDirectory blob --- lib/std/macho.zig | 16 ++++-- src/link/MachO/CodeSignature.zig | 89 ++++++++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 4e4e64881246..4a54b6b9c502 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1446,6 +1446,18 @@ pub const CodeDirectory = extern struct { /// Unused (must be zero) spare2: u32, + /// + scatterOffset: u32, + + /// + teamOffset: u32, + + /// + spare3: u32, + + /// + codeLimit64: u64, + /// Offset of executable segment execSegBase: u64, @@ -1453,9 +1465,7 @@ pub const CodeDirectory = extern struct { execSegLimit: u64, /// Executable segment flags - execSegFlags, - - // end_withExecSeg: [*]u8, + execSegFlags: u64, }; /// Structure of an embedded-signature SuperBlob diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index c03829daceb1..fbc67dbabd8c 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -8,11 +8,39 @@ const mem = std.mem; const testing = std.testing; const Allocator = mem.Allocator; -// pub const Blob = union(enum) { -// Signature: struct{ -// inner: -// } -// }; +const Blob = struct { + inner: macho.CodeDirectory, + data: std.ArrayListUnmanaged(u8) = .{}, + + fn size(self: Blob) u32 { + return self.inner.length; + } + + fn write(self: Blob, buffer: []u8) void { + assert(buffer.len >= self.inner.length); + mem.writeIntBig(u32, buffer[0..4], self.inner.magic); + mem.writeIntBig(u32, buffer[4..8], self.inner.length); + mem.writeIntBig(u32, buffer[8..12], self.inner.version); + mem.writeIntBig(u32, buffer[12..16], self.inner.flags); + mem.writeIntBig(u32, buffer[16..20], self.inner.hashOffset); + mem.writeIntBig(u32, buffer[20..24], self.inner.identOffset); + mem.writeIntBig(u32, buffer[24..28], self.inner.nSpecialSlots); + mem.writeIntBig(u32, buffer[28..32], self.inner.nCodeSlots); + mem.writeIntBig(u32, buffer[32..36], self.inner.codeLimit); + mem.writeIntBig(u8, buffer[36..37], self.inner.hashSize); + mem.writeIntBig(u8, buffer[37..38], self.inner.hashType); + mem.writeIntBig(u8, buffer[38..39], self.inner.platform); + mem.writeIntBig(u8, buffer[39..40], self.inner.pageSize); + mem.writeIntBig(u32, buffer[40..44], self.inner.spare2); + mem.writeIntBig(u32, buffer[44..48], self.inner.scatterOffset); + mem.writeIntBig(u32, buffer[48..52], self.inner.teamOffset); + mem.writeIntBig(u32, buffer[52..56], self.inner.spare3); + mem.writeIntBig(u64, buffer[56..64], self.inner.codeLimit64); + mem.writeIntBig(u64, buffer[64..72], self.inner.execSegBase); + mem.writeIntBig(u64, buffer[72..80], self.inner.execSegLimit); + mem.writeIntBig(u64, buffer[80..88], self.inner.execSegFlags); + } +}; alloc: *Allocator, inner: macho.SuperBlob = .{ @@ -20,16 +48,44 @@ inner: macho.SuperBlob = .{ .length = @sizeOf(macho.SuperBlob), .count = 0, }, -// blobs: std.ArrayList(Blob), +blob: ?Blob = null, pub fn init(alloc: *Allocator) CodeSignature { return .{ .alloc = alloc, - // .indices = std.ArrayList(Blob).init(alloc), }; } -pub fn calcAdhocSignature(self: *CodeSignature) !void {} +pub fn calcAdhocSignature(self: *CodeSignature) !void { + var blob = Blob{ + .inner = .{ + .magic = macho.CSMAGIC_CODEDIRECTORY, + .length = @sizeOf(macho.CodeDirectory), + .version = 0x20400, + .flags = 0, + .hashOffset = 0, + .identOffset = 0, + .nSpecialSlots = 0, + .nCodeSlots = 0, + .codeLimit = 0, + .hashSize = 0, + .hashType = 0, + .platform = 0, + .pageSize = 0, + .spare2 = 0, + .scatterOffset = 0, + .teamOffset = 0, + .spare3 = 0, + .codeLimit64 = 0, + .execSegBase = 0, + .execSegLimit = 0, + .execSegFlags = 0, + }, + }; + self.inner.length += @sizeOf(macho.BlobIndex) + blob.size(); + self.inner.count = 1; + self.blob = blob; +} pub fn size(self: CodeSignature) u32 { return self.inner.length; @@ -38,9 +94,16 @@ pub fn size(self: CodeSignature) u32 { pub fn write(self: CodeSignature, buffer: []u8) void { assert(buffer.len >= self.inner.length); self.writeHeader(buffer); + const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex); + writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, buffer[@sizeOf(macho.SuperBlob)..]); + self.blob.?.write(buffer[offset..]); } -pub fn deinit(self: *CodeSignature) void {} +pub fn deinit(self: *CodeSignature) void { + if (self.blob) |*b| { + b.data.deinit(self.alloc); + } +} fn writeHeader(self: CodeSignature, buffer: []u8) void { assert(buffer.len >= @sizeOf(macho.SuperBlob)); @@ -49,11 +112,19 @@ fn writeHeader(self: CodeSignature, buffer: []u8) void { mem.writeIntBig(u32, buffer[8..12], self.inner.count); } +fn writeBlobIndex(tt: u32, offset: u32, buffer: []u8) void { + assert(buffer.len >= @sizeOf(macho.BlobIndex)); + mem.writeIntBig(u32, buffer[0..4], tt); + mem.writeIntBig(u32, buffer[4..8], offset); +} + test "CodeSignature header" { var code_sig = CodeSignature.init(testing.allocator); defer code_sig.deinit(); + var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined; code_sig.writeHeader(buffer[0..]); + const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 }; testing.expect(mem.eql(u8, expected[0..], buffer[0..])); } From 9dcf7ee9c904b25fbffd264cabeb6638e9d9e2d6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 11:50:44 +0100 Subject: [PATCH 21/37] stage2 macho: add info about __TEXT segment --- lib/std/macho.zig | 8 ++++---- src/link/MachO.zig | 2 +- src/link/MachO/CodeSignature.zig | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 4a54b6b9c502..79378ef5a487 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1402,6 +1402,10 @@ pub const CS_SIGNER_TYPE_UNKNOWN: u32 = 0; pub const CS_SIGNER_TYPE_LEGACYVPN: u32 = 5; pub const CS_SIGNER_TYPE_MAC_APP_STORE: u32 = 6; +pub const CS_ADHOC: u32 = 0x2; + +pub const CS_EXECSEG_MAIN_BINARY: u32 = 0x1; + /// This CodeDirectory is tailored specfically at version 0x20400. pub const CodeDirectory = extern struct { /// Magic number (CSMAGIC_CODEDIRECTORY) @@ -1488,8 +1492,6 @@ pub const SuperBlob = extern struct { /// Number of index BlobIndex entries following this struct count: u32, - - // index: []const BlobIndex, }; pub const GenericBlob = extern struct { @@ -1498,6 +1500,4 @@ pub const GenericBlob = extern struct { /// Total length of blob length: u32, - - // data: []const u8, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 431dcf714c0b..f77f05fb33a3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1758,7 +1758,7 @@ fn writecodeSignature(self: *MachO) !void { var code_sig = CodeSignature.init(self.base.allocator); defer code_sig.deinit(); - try code_sig.calcAdhocSignature(); + try code_sig.calcAdhocSignature(self); var buffer = try self.base.allocator.alloc(u8, code_sig.size()); defer self.base.allocator.free(buffer); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index fbc67dbabd8c..25b1e9e9ffd8 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -8,6 +8,8 @@ const mem = std.mem; const testing = std.testing; const Allocator = mem.Allocator; +const MachO = @import("../MachO.zig"); + const Blob = struct { inner: macho.CodeDirectory, data: std.ArrayListUnmanaged(u8) = .{}, @@ -56,13 +58,17 @@ pub fn init(alloc: *Allocator) CodeSignature { }; } -pub fn calcAdhocSignature(self: *CodeSignature) !void { +pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { + const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment; + const execSegBase: u64 = text_segment.fileoff; + const execSegLimit: u64 = text_segment.filesize; + const execSegFlags: u64 = text_segment.flags; var blob = Blob{ .inner = .{ .magic = macho.CSMAGIC_CODEDIRECTORY, .length = @sizeOf(macho.CodeDirectory), - .version = 0x20400, - .flags = 0, + .version = macho.CS_SUPPORTSEXECSEG, + .flags = macho.CS_ADHOC, .hashOffset = 0, .identOffset = 0, .nSpecialSlots = 0, @@ -77,9 +83,9 @@ pub fn calcAdhocSignature(self: *CodeSignature) !void { .teamOffset = 0, .spare3 = 0, .codeLimit64 = 0, - .execSegBase = 0, - .execSegLimit = 0, - .execSegFlags = 0, + .execSegBase = execSegBase, + .execSegLimit = execSegLimit, + .execSegFlags = execSegFlags, }, }; self.inner.length += @sizeOf(macho.BlobIndex) + blob.size(); From a6e93da717334b6ee354d162107e00080807ce7c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 15:48:44 +0100 Subject: [PATCH 22/37] stage2 macho: generate a code sig (not valid yet) --- lib/std/macho.zig | 8 +-- src/link/MachO.zig | 14 ++++-- src/link/MachO/CodeSignature.zig | 86 +++++++++++++++++++++++++------- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 79378ef5a487..6bd22359f734 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1384,10 +1384,10 @@ pub const CSTYPE_INDEX_REQUIREMENTS: u32 = 0x00000002; /// Compat with amfi pub const CSTYPE_INDEX_ENTITLEMENTS: u32 = 0x00000005; -pub const CS_HASHTYPE_SHA1: u32 = 1; -pub const CS_HASHTYPE_SHA256: u32 = 2; -pub const CS_HASHTYPE_SHA256_TRUNCATED: u32 = 3; -pub const CS_HASHTYPE_SHA384: u32 = 4; +pub const CS_HASHTYPE_SHA1: u8 = 1; +pub const CS_HASHTYPE_SHA256: u8 = 2; +pub const CS_HASHTYPE_SHA256_TRUNCATED: u8 = 3; +pub const CS_HASHTYPE_SHA384: u8 = 4; pub const CS_SHA1_LEN: u32 = 20; pub const CS_SHA256_LEN: u32 = 32; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index f77f05fb33a3..ca5dc894df97 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -385,8 +385,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } - - try self.writecodeSignature(); }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, @@ -430,6 +428,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { assert(!self.cmd_table_dirty); assert(!self.dylinker_cmd_dirty); assert(!self.libsystem_cmd_dirty); + + switch (self.base.options.output_mode) { + .Exe, .Lib => try self.writeCodeSignature(), // code signing always comes last + else => {}, + } } fn linkWithLLD(self: *MachO, comp: *Compilation) !void { @@ -1486,6 +1489,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); code_sig.dataoff = off; code_sig.datasize = file_size; + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; } } if (self.dyld_stub_binder_index == null) { @@ -1752,7 +1758,7 @@ fn writeAllUndefSymbols(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); } -fn writecodeSignature(self: *MachO) !void { +fn writeCodeSignature(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; var code_sig = CodeSignature.init(self.base.allocator); @@ -1766,7 +1772,7 @@ fn writecodeSignature(self: *MachO) !void { code_sig.write(buffer); try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); - try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } fn writeExportTrie(self: *MachO) !void { diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 25b1e9e9ffd8..813993d02c62 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -7,19 +7,24 @@ const macho = std.macho; const mem = std.mem; const testing = std.testing; const Allocator = mem.Allocator; +const Sha256 = std.crypto.hash.sha2.Sha256; const MachO = @import("../MachO.zig"); -const Blob = struct { +const hash_size: u8 = 32; +const page_size: u16 = 0x1000; + +const CodeDirectory = struct { inner: macho.CodeDirectory, data: std.ArrayListUnmanaged(u8) = .{}, - fn size(self: Blob) u32 { + fn size(self: CodeDirectory) u32 { return self.inner.length; } - fn write(self: Blob, buffer: []u8) void { + fn write(self: CodeDirectory, buffer: []u8) void { assert(buffer.len >= self.inner.length); + mem.writeIntBig(u32, buffer[0..4], self.inner.magic); mem.writeIntBig(u32, buffer[4..8], self.inner.length); mem.writeIntBig(u32, buffer[8..12], self.inner.version); @@ -29,10 +34,10 @@ const Blob = struct { mem.writeIntBig(u32, buffer[24..28], self.inner.nSpecialSlots); mem.writeIntBig(u32, buffer[28..32], self.inner.nCodeSlots); mem.writeIntBig(u32, buffer[32..36], self.inner.codeLimit); - mem.writeIntBig(u8, buffer[36..37], self.inner.hashSize); - mem.writeIntBig(u8, buffer[37..38], self.inner.hashType); - mem.writeIntBig(u8, buffer[38..39], self.inner.platform); - mem.writeIntBig(u8, buffer[39..40], self.inner.pageSize); + buffer[36] = self.inner.hashSize; + buffer[37] = self.inner.hashType; + buffer[38] = self.inner.platform; + buffer[39] = self.inner.pageSize; mem.writeIntBig(u32, buffer[40..44], self.inner.spare2); mem.writeIntBig(u32, buffer[44..48], self.inner.scatterOffset); mem.writeIntBig(u32, buffer[48..52], self.inner.teamOffset); @@ -41,6 +46,8 @@ const Blob = struct { mem.writeIntBig(u64, buffer[64..72], self.inner.execSegBase); mem.writeIntBig(u64, buffer[72..80], self.inner.execSegLimit); mem.writeIntBig(u64, buffer[80..88], self.inner.execSegFlags); + + mem.copy(u8, buffer[88..], self.data.items); } }; @@ -50,7 +57,7 @@ inner: macho.SuperBlob = .{ .length = @sizeOf(macho.SuperBlob), .count = 0, }, -blob: ?Blob = null, +cdir: ?CodeDirectory = null, pub fn init(alloc: *Allocator) CodeSignature { return .{ @@ -60,10 +67,14 @@ pub fn init(alloc: *Allocator) CodeSignature { pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment; + const data_segment = bin_file.load_commands.items[bin_file.data_segment_cmd_index.?].Segment; + const linkedit_segment = bin_file.load_commands.items[bin_file.linkedit_segment_cmd_index.?].Segment; + const symtab = bin_file.load_commands.items[bin_file.symtab_cmd_index.?].Symtab; + const execSegBase: u64 = text_segment.fileoff; const execSegLimit: u64 = text_segment.filesize; - const execSegFlags: u64 = text_segment.flags; - var blob = Blob{ + const execSegFlags: u64 = if (bin_file.base.options.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; + var cdir = CodeDirectory{ .inner = .{ .magic = macho.CSMAGIC_CODEDIRECTORY, .length = @sizeOf(macho.CodeDirectory), @@ -74,10 +85,10 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { .nSpecialSlots = 0, .nCodeSlots = 0, .codeLimit = 0, - .hashSize = 0, - .hashType = 0, + .hashSize = hash_size, + .hashType = macho.CS_HASHTYPE_SHA256, .platform = 0, - .pageSize = 0, + .pageSize = @truncate(u8, std.math.log2(page_size)), .spare2 = 0, .scatterOffset = 0, .teamOffset = 0, @@ -88,9 +99,48 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { .execSegFlags = execSegFlags, }, }; - self.inner.length += @sizeOf(macho.BlobIndex) + blob.size(); + + const file_size = symtab.stroff + symtab.strsize; + const total_pages = mem.alignForward(file_size, page_size) / page_size; + log.debug("Total file size: {}; total number of pages: {}\n", .{ file_size, total_pages }); + + var hash: [hash_size]u8 = undefined; + var buffer = try bin_file.base.allocator.alloc(u8, page_size); + defer bin_file.base.allocator.free(buffer); + const macho_file = bin_file.base.file.?; + + const id = bin_file.base.options.emit.?.sub_path; + try cdir.data.ensureCapacity(self.alloc, total_pages * hash_size + id.len + 1); + + // 1. Save the identifier and update offsets + cdir.inner.identOffset = cdir.inner.length; + cdir.data.appendSliceAssumeCapacity(id); + cdir.data.appendAssumeCapacity(0); + + // 2. Calculate hash for each page (in file) and write it to the buffer + // TODO figure out how we can cache several hashes since we won't update + // every page during incremental linking + cdir.inner.hashOffset = cdir.inner.identOffset + @intCast(u32, id.len) + 1; + var i: usize = 0; + while (i < total_pages) : (i += 1) { + const fstart = i * page_size; + const fsize = if (fstart + page_size > file_size) file_size - fstart else page_size; + const len = try macho_file.preadAll(buffer, fstart); + assert(fsize <= len); + + Sha256.hash(buffer[0..fsize], &hash, .{}); + log.debug("Calculated hash for page 0x{x}-0x{x}: 0x{x}\n", .{ fstart, fstart + fsize, hash[0..] }); + + cdir.data.appendSliceAssumeCapacity(hash[0..]); + cdir.inner.nCodeSlots += 1; + } + + // 3. Update CodeDirectory length + cdir.inner.length += @intCast(u32, cdir.data.items.len); + + self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size(); self.inner.count = 1; - self.blob = blob; + self.cdir = cdir; } pub fn size(self: CodeSignature) u32 { @@ -102,12 +152,12 @@ pub fn write(self: CodeSignature, buffer: []u8) void { self.writeHeader(buffer); const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex); writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, buffer[@sizeOf(macho.SuperBlob)..]); - self.blob.?.write(buffer[offset..]); + self.cdir.?.write(buffer[offset..]); } pub fn deinit(self: *CodeSignature) void { - if (self.blob) |*b| { - b.data.deinit(self.alloc); + if (self.cdir) |*cdir| { + cdir.data.deinit(self.alloc); } } From cd79c6dda1457ae9bf69fe900852209e31c28406 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 17:26:39 +0100 Subject: [PATCH 23/37] stage2 macho: fix issues with codesigning --- src/link/MachO.zig | 4 +++- src/link/MachO/CodeSignature.zig | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index ca5dc894df97..a498b7766be0 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1760,6 +1760,9 @@ fn writeAllUndefSymbols(self: *MachO) !void { fn writeCodeSignature(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + // Pad out the space. We need to do this to calculate valid hashes for everything in the file + // except for code signature data. + try self.base.file.?.pwriteAll(&[_]u8{0}, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); var code_sig = CodeSignature.init(self.base.allocator); defer code_sig.deinit(); @@ -1772,7 +1775,6 @@ fn writeCodeSignature(self: *MachO) !void { code_sig.write(buffer); try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); - try self.base.file.?.pwriteAll(&[_]u8{0}, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } fn writeExportTrie(self: *MachO) !void { diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 813993d02c62..72e49bb9767f 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -69,11 +69,12 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment; const data_segment = bin_file.load_commands.items[bin_file.data_segment_cmd_index.?].Segment; const linkedit_segment = bin_file.load_commands.items[bin_file.linkedit_segment_cmd_index.?].Segment; - const symtab = bin_file.load_commands.items[bin_file.symtab_cmd_index.?].Symtab; + const code_sig_cmd = bin_file.load_commands.items[bin_file.code_signature_cmd_index.?].LinkeditData; const execSegBase: u64 = text_segment.fileoff; const execSegLimit: u64 = text_segment.filesize; const execSegFlags: u64 = if (bin_file.base.options.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; + const file_size = code_sig_cmd.dataoff; var cdir = CodeDirectory{ .inner = .{ .magic = macho.CSMAGIC_CODEDIRECTORY, @@ -84,7 +85,7 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { .identOffset = 0, .nSpecialSlots = 0, .nCodeSlots = 0, - .codeLimit = 0, + .codeLimit = @intCast(u32, file_size), .hashSize = hash_size, .hashType = macho.CS_HASHTYPE_SHA256, .platform = 0, @@ -100,7 +101,6 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { }, }; - const file_size = symtab.stroff + symtab.strsize; const total_pages = mem.alignForward(file_size, page_size) / page_size; log.debug("Total file size: {}; total number of pages: {}\n", .{ file_size, total_pages }); From e7db37286459b30e5cb95666aac25d1f9406dd3e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Nov 2020 17:38:41 +0100 Subject: [PATCH 24/37] stage2 macho: cleanup logs --- src/link/MachO.zig | 2 ++ src/link/MachO/CodeSignature.zig | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a498b7766be0..95e2fe014ab1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1774,6 +1774,8 @@ fn writeCodeSignature(self: *MachO) !void { code_sig.write(buffer); + log.debug("writing code signature from 0x{x} to 0x{x}\n", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 72e49bb9767f..a7097d4cc080 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -102,7 +102,6 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { }; const total_pages = mem.alignForward(file_size, page_size) / page_size; - log.debug("Total file size: {}; total number of pages: {}\n", .{ file_size, total_pages }); var hash: [hash_size]u8 = undefined; var buffer = try bin_file.base.allocator.alloc(u8, page_size); @@ -129,7 +128,6 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { assert(fsize <= len); Sha256.hash(buffer[0..fsize], &hash, .{}); - log.debug("Calculated hash for page 0x{x}-0x{x}: 0x{x}\n", .{ fstart, fstart + fsize, hash[0..] }); cdir.data.appendSliceAssumeCapacity(hash[0..]); cdir.inner.nCodeSlots += 1; From 59fe3d447d8cdd5dda34d7a77459c2c6761857ea Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 22 Nov 2020 12:33:39 +0100 Subject: [PATCH 25/37] stage2 macho: make file structure compatible with codesign tool --- src/link/MachO.zig | 252 ++++++++++++++++++++------------------------- 1 file changed, 113 insertions(+), 139 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 95e2fe014ab1..95b165f27c59 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -125,6 +125,9 @@ got_section_index: ?u16 = null, entry_addr: ?u64 = null, +// TODO move this into each Segment aggregator +linkedit_segment_next_offset: ?u32 = null, + /// Table of all local symbols /// Internally references string table for names (which are optional). local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, @@ -330,35 +333,14 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - // Unfortunately these have to be buffered and done at the end because MachO does not allow - // mixing local, global and undefined symbols within a symbol table. - try self.writeAllGlobalSymbols(); - try self.writeAllUndefSymbols(); - - try self.writeStringTable(); - switch (self.base.options.output_mode) { .Exe => { - // Write export trie. - try self.writeExportTrie(); if (self.entry_addr) |addr| { // Update LC_MAIN with entry offset. const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint; main_cmd.entryoff = addr - text_segment.vmaddr; } - { - // Update dynamic symbol table. - const nlocals = @intCast(u32, self.local_symbols.items.len); - const nglobals = @intCast(u32, self.global_symbols.items.len); - const nundefs = @intCast(u32, self.undef_symbols.items.len); - const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - dysymtab.nlocalsym = nlocals; - dysymtab.iextdefsym = nlocals; - dysymtab.nextdefsym = nglobals; - dysymtab.iundefsym = nlocals + nglobals; - dysymtab.nundefsym = nundefs; - } if (self.dylinker_cmd_dirty) { // Write path to dyld loader. var off: usize = @sizeOf(macho.mach_header_64); @@ -385,32 +367,22 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } + + try self.writeExportTrie(); + try self.writeSymbolTable(); + try self.writeStringTable(); + + // Preallocate space for the code signature. + // We need to do this at this stage so that we have the load commands with proper values + // written out to the file. + // The most important here is to have the correct vm and filesize of the __LINKEDIT segment + // where the code signature goes into. + try self.writeCodeSignaturePadding(); }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, } - { - // Update symbol table. - const nlocals = @intCast(u32, self.local_symbols.items.len); - const nglobals = @intCast(u32, self.global_symbols.items.len); - const nundefs = @intCast(u32, self.undef_symbols.items.len); - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - symtab.nsyms = nlocals + nglobals + nundefs; - } - - // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of - // doing dynamic updates like this. - if (self.code_signature_cmd_index) |i| { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const code_sig = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; - linkedit.filesize = code_sig.dataoff + code_sig.datasize - linkedit.fileoff; - } else { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; - } - if (self.cmd_table_dirty) { try self.writeCmdHeaders(); try self.writeMachOHeader(); @@ -1003,8 +975,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { symbol.n_type = macho.N_SECT; symbol.n_sect = @intCast(u8, self.text_section_index.?) + 1; symbol.n_desc = 0; - // TODO this write could be avoided if no fields of the symbol were changed. - try self.writeSymbol(decl.link.macho.local_sym_index); } else { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); @@ -1020,8 +990,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .n_value = addr, }; self.offset_table.items[decl.link.macho.offset_table_index] = addr; - - try self.writeSymbol(decl.link.macho.local_sym_index); try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1241,7 +1209,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { data_segment.nsects += 1; const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + // TODO looking for free space should be done *within* a segment it belongs to + // const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); + const off = @intCast(u32, data_segment.fileoff); log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -1263,7 +1233,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); data_segment.vmsize = segment_size; data_segment.filesize = segment_size; - data_segment.fileoff = off; self.cmd_table_dirty = true; } if (self.linkedit_segment_cmd_index == null) { @@ -1271,6 +1240,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; const initprot = macho.VM_PROT_READ; + const off = data_segment.fileoff + data_segment.filesize; try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, @@ -1278,7 +1248,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .segname = makeStaticString("__LINKEDIT"), .vmaddr = data_segment.vmaddr + data_segment.vmsize, .vmsize = 0, - .fileoff = 0, + .fileoff = off, .filesize = 0, .maxprot = maxprot, .initprot = initprot, @@ -1286,6 +1256,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }, }); + self.linkedit_segment_next_offset = @intCast(u32, off); self.cmd_table_dirty = true; } if (self.dyld_info_cmd_index == null) { @@ -1438,62 +1409,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { }, }); } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - if (dyld_info.export_off == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(u64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - dyld_info.export_off = off; - dyld_info.export_size = @intCast(u32, file_size); - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - linkedit.fileoff = off; - } - } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - if (symtab.symoff == 0) { - const nsyms = self.base.options.symbol_count_hint; - const file_size = @sizeOf(macho.nlist_64) * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.symoff = off; - symtab.nsyms = @intCast(u32, nsyms); - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - } - if (symtab.stroff == 0) { - try self.string_table.append(self.base.allocator, 0); - const file_size = @intCast(u32, self.string_table.items.len); - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - symtab.stroff = off; - symtab.strsize = file_size; - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - } - } - { - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const code_sig = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; - if (code_sig.dataoff == 0) { - const file_size = 0x1000; // TODO what is a good guesstimate for initial code signature? - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - code_sig.dataoff = off; - code_sig.datasize = file_size; - - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - linkedit.vmsize += segment_size; - } - } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); const name = try self.makeString("dyld_stub_binder"); @@ -1719,17 +1634,6 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { return start; } -fn writeSymbol(self: *MachO, index: usize) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const sym = [1]macho.nlist_64{self.local_symbols.items[index]}; - const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; - log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); -} - fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const sect = &self.sections.items[self.got_section_index.?]; const endian = self.base.options.target.cpu.arch.endian(); @@ -1740,30 +1644,72 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&buf, off); } -fn writeAllGlobalSymbols(self: *MachO) !void { +fn writeSymbolTable(self: *MachO) !void { + // TODO workout how we can cache these so that we only overwrite symbols that were updated const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64); - const file_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); - log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), off); -} -fn writeAllUndefSymbols(self: *MachO) !void { - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.local_symbols.items.len; - const nglobals = self.global_symbols.items.len; - const off = symtab.symoff + (nlocals + nglobals) * @sizeOf(macho.nlist_64); - const file_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64); - log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); + const locals_off = self.linkedit_segment_next_offset.?; + const locals_size = self.local_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing local symbols from 0x{x} to 0x{x}\n", .{ locals_off, locals_size + locals_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.local_symbols.items), locals_off); + + const globals_off = locals_off + locals_size; + const globals_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ globals_off, globals_size + globals_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); + + const undefs_off = globals_off + globals_size; + const undefs_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ undefs_off, undefs_size + undefs_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off); + + // Update symbol table. + const nlocals = @intCast(u32, self.local_symbols.items.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); + symtab.symoff = self.linkedit_segment_next_offset.?; + symtab.nsyms = nlocals + nglobals + nundefs; + self.linkedit_segment_next_offset = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = nlocals; + dysymtab.iextdefsym = nlocals; + dysymtab.nextdefsym = nglobals; + dysymtab.iundefsym = nlocals + nglobals; + dysymtab.nundefsym = nundefs; + + // Advance size of __LINKEDIT segment + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize += symtab.nsyms * @sizeOf(macho.nlist_64); + if (linkedit.vmsize < linkedit.filesize) { + linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size); + } + self.cmd_table_dirty = true; } -fn writeCodeSignature(self: *MachO) !void { +fn writeCodeSignaturePadding(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + const fileoff = self.linkedit_segment_next_offset.?; + const datasize: u32 = 0x1000; // TODO Calculate the expected size of the signature. + code_sig_cmd.dataoff = fileoff; + code_sig_cmd.datasize = datasize; + + self.linkedit_segment_next_offset = fileoff + datasize; + // Advance size of __LINKEDIT segment + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize += datasize; + if (linkedit.vmsize < linkedit.filesize) { + linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size); + } + log.debug("writing code signature padding from 0x{x} to 0x{x}\n", .{ fileoff, fileoff + datasize }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. - try self.base.file.?.pwriteAll(&[_]u8{0}, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, fileoff + datasize - 1); +} +fn writeCodeSignature(self: *MachO) !void { + const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; var code_sig = CodeSignature.init(self.base.allocator); defer code_sig.deinit(); @@ -1780,7 +1726,7 @@ fn writeCodeSignature(self: *MachO) !void { } fn writeExportTrie(self: *MachO) !void { - if (self.global_symbols.items.len == 0) return; // No exports, nothing to do. + assert(self.global_symbols.items.len > 0); var trie: Trie = .{}; defer trie.deinit(self.base.allocator); @@ -1803,23 +1749,51 @@ fn writeExportTrie(self: *MachO) !void { try trie.writeULEB128Mem(self.base.allocator, &buffer); const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + const export_size = @intCast(u32, mem.alignForward(buffer.items.len, @sizeOf(u64))); + dyld_info.export_off = self.linkedit_segment_next_offset.?; + dyld_info.export_size = export_size; + + log.debug("writing export trie from 0x{x} to 0x{x}\n", .{ dyld_info.export_off, dyld_info.export_off + export_size }); + + if (export_size > buffer.items.len) { + // Pad out to align(8). + try self.base.file.?.pwriteAll(&[_]u8{ 0 }, dyld_info.export_off + export_size); + } try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off); + + self.linkedit_segment_next_offset = dyld_info.export_off + dyld_info.export_size; + // Advance size of __LINKEDIT segment + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize += dyld_info.export_size; + if (linkedit.vmsize < linkedit.filesize) { + linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size); + } + self.cmd_table_dirty = true; } fn writeStringTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const allocated_size = self.allocatedSize(symtab.stroff); const needed_size = self.string_table.items.len; - if (needed_size > allocated_size) { - symtab.strsize = 0; - symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); - } - symtab.strsize = @intCast(u32, needed_size); + symtab.stroff = self.linkedit_segment_next_offset.?; + symtab.strsize = @intCast(u32, mem.alignForward(needed_size, @sizeOf(u64))); log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + if (symtab.strsize > needed_size) { + // Pad out to align(8); + try self.base.file.?.pwriteAll(&[_]u8{ 0 }, symtab.stroff + symtab.strsize); + } try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); + + self.linkedit_segment_next_offset = symtab.stroff + symtab.strsize; + // Advance size of __LINKEDIT segment + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize += symtab.strsize; + if (linkedit.vmsize < linkedit.filesize) { + linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size); + } + self.cmd_table_dirty = true; } fn writeCmdHeaders(self: *MachO) !void { From 80b1041c21599f5d444373dd35eafe2dc68e3887 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 23 Nov 2020 13:49:43 +0100 Subject: [PATCH 26/37] stage2 macho: use RIP-relative for memory-set regs x86_64 --- src/codegen.zig | 27 +++++++++++++++++++++++---- src/link/MachO.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 48fda6f0d5f8..6cef7d8d0ae2 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1683,11 +1683,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); switch (arch) { .x86_64 => { - // Here, we store the got address in %rax, and then call %rax - // movabsq [addr], %rax try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr }); // callq *%rax - try self.code.ensureCapacity(self.code.items.len + 2); self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); }, .aarch64 => { @@ -2766,7 +2763,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); }, .memory => |x| { - if (x <= math.maxInt(u32)) { + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + // For MachO, the binary, with the exception of object files, has to be a PIE. + // Therefore, we cannot load an absolute address. + assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO. + // The plan here is to use RIP-relative addressing, but leaving the actual displacement + // information empty (0-padded) and fixing it up later in the linker. + try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ + .address = x, + .start = self.code.items.len, + .len = 7, + }); + try self.code.ensureCapacity(self.code.items.len + 9); + // leaq %r, [rip + disp] + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x48, + 0x8d, + 0x05 | (@as(u8, reg.id() & 0b111) << 3), // R + 0x0, + 0x0, + 0x0, + 0x0, + }); + } else if (x <= math.maxInt(u32)) { // Moving from memory to a register is a variant of `8B /r`. // Since we're using 64-bit moves, we require a REX. // This variant also requires a SIB, as it would otherwise be RIP-relative. diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 95b165f27c59..dc6acfca487a 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -214,10 +214,21 @@ pub const TextBlock = struct { /// Unlike in Elf, we need to store the size of this symbol as part of /// the TextBlock since macho.nlist_64 lacks this information. size: u64, + /// List of RIP-relative positions in the code + /// This is a table of all RIP-relative positions that will need fixups + /// after codegen when linker assigns addresses to GOT entries. + /// TODO handle freeing, shrinking and re-allocs + rip_positions: std.ArrayListUnmanaged(RipPosition) = .{}, /// Points to the previous and next neighbours prev: ?*TextBlock, next: ?*TextBlock, + pub const RipPosition = struct { + address: u64, + start: usize, + len: usize, + }; + pub const empty = TextBlock{ .local_sym_index = 0, .offset_table_index = undefined, @@ -226,6 +237,15 @@ pub const TextBlock = struct { .next = null, }; + pub fn addRipPosition(self: *TextBlock, alloc: *Allocator, rip: RipPosition) !void { + std.debug.print("text_block={}, rip={}\n", .{ self.local_sym_index, rip }); + return self.rip_positions.append(alloc, rip); + } + + fn deinit(self: *TextBlock, alloc: *Allocator) void { + self.rip_positions.deinit(alloc); + } + /// Returns how much room there is to grow in virtual address space. /// File offset relocation happens transparently, so it is not included in /// this calculation. @@ -993,6 +1013,20 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } + // Perform RIP-relative fixups (if any) + const got_section = self.sections.items[self.got_section_index.?]; + for (decl.link.macho.rip_positions.items) |rip| { + std.debug.print("rip={}\n", .{rip}); + const target_addr = rip.address; + // const got_addr = got_section.addr + decl.link.macho.offset_table_index * @sizeOf(u64); + const this_addr = symbol.n_value + rip.start; + std.debug.print("target_addr=0x{x},this_addr=0x{x}\n", .{target_addr, this_addr}); + const displacement = @intCast(u32, target_addr - this_addr + rip.len); + std.debug.print("displacement=0x{x}\n", .{displacement}); + var placeholder = code_buffer.items[rip.start + rip.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; + mem.writeIntSliceLittle(u32, placeholder, displacement); + } + const text_section = self.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; const file_offset = text_section.offset + section_offset; From ef5132c508f89ed8392143f6e8d03e8a2f121ba9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 24 Nov 2020 18:46:43 +0100 Subject: [PATCH 27/37] stage2 macho: first, rough draft at trampolining --- src/codegen.zig | 63 +++++++++++++----- src/link/MachO.zig | 109 ++++++++++++------------------- src/link/MachO/CodeSignature.zig | 2 - 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 6cef7d8d0ae2..85b039069af8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2767,24 +2767,51 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // For MachO, the binary, with the exception of object files, has to be a PIE. // Therefore, we cannot load an absolute address. assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO. - // The plan here is to use RIP-relative addressing, but leaving the actual displacement - // information empty (0-padded) and fixing it up later in the linker. - try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ - .address = x, - .start = self.code.items.len, - .len = 7, - }); - try self.code.ensureCapacity(self.code.items.len + 9); - // leaq %r, [rip + disp] - self.code.appendSliceAssumeCapacity(&[_]u8{ - 0x48, - 0x8d, - 0x05 | (@as(u8, reg.id() & 0b111) << 3), // R - 0x0, - 0x0, - 0x0, - 0x0, - }); + // The plan here is to use unconditional relative jump to GOT entry, where we store + // pre-calculated and stored effective address to load into the target register. + // We leave the actual displacement information empty (0-padded) and fixing it up + // later in the linker. + if (reg.id() == 0) { // %rax is special-cased + try self.code.ensureCapacity(self.code.items.len + 5); + try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ + .address = x, + .start = self.code.items.len, + .len = 5, + }); + // call [label] + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0xE8, + 0x0, + 0x0, + 0x0, + 0x0, + }); + } else { + try self.code.ensureCapacity(self.code.items.len + 10); + // push %rax + self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); + try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ + .address = x, + .start = self.code.items.len, + .len = 5, + }); + // call [label] + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0xE8, + 0x0, + 0x0, + 0x0, + 0x0, + }); + // mov %r, %rax + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x48, + 0x89, + 0xC0 | @as(u8, reg.id()), + }); + // pop %rax + self.code.appendSliceAssumeCapacity(&[_]u8{0x58}); + } } else if (x <= math.maxInt(u32)) { // Moving from memory to a register is a variant of `8B /r`. // Since we're using 64-bit moves, we require a REX. diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dc6acfca487a..bb4813a9df0f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1020,8 +1020,8 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const target_addr = rip.address; // const got_addr = got_section.addr + decl.link.macho.offset_table_index * @sizeOf(u64); const this_addr = symbol.n_value + rip.start; - std.debug.print("target_addr=0x{x},this_addr=0x{x}\n", .{target_addr, this_addr}); - const displacement = @intCast(u32, target_addr - this_addr + rip.len); + std.debug.print("target_addr=0x{x},this_addr=0x{x}\n", .{ target_addr, this_addr }); + const displacement = @intCast(u32, target_addr - this_addr - rip.len); std.debug.print("displacement=0x{x}\n", .{displacement}); var placeholder = code_buffer.items[rip.start + rip.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; mem.writeIntSliceLittle(u32, placeholder, displacement); @@ -1201,7 +1201,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .addr = text_segment.vmaddr + off, .size = file_size, .offset = off, - .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 1, // 2^2 for aarch64, 2^1 for x86_64 + .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0, // 2^2 for aarch64, 2^0 for x86_64 .reloff = 0, .nreloc = 0, .flags = flags, @@ -1214,48 +1214,23 @@ pub fn populateMissingMetadata(self: *MachO) !void { text_segment.filesize = file_size + off; self.cmd_table_dirty = true; } - if (self.data_segment_cmd_index == null) { - self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; - const initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; - try self.load_commands.append(self.base.allocator, .{ - .Segment = .{ - .cmd = macho.LC_SEGMENT_64, - .cmdsize = @sizeOf(macho.segment_command_64), - .segname = makeStaticString("__DATA"), - .vmaddr = text_segment.vmaddr + text_segment.vmsize, - .vmsize = 0, - .fileoff = text_segment.fileoff + text_segment.filesize, - .filesize = 0, - .maxprot = maxprot, - .initprot = initprot, - .nsects = 0, - .flags = 0, - }, - }); - self.cmd_table_dirty = true; - } if (self.got_section_index == null) { + const text_section = &self.sections.items[self.text_section_index.?]; self.got_section_index = @intCast(u16, self.sections.items.len); - const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - data_segment.cmdsize += @sizeOf(macho.section_64); - data_segment.nsects += 1; const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; // TODO looking for free space should be done *within* a segment it belongs to - // const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); - const off = @intCast(u32, data_segment.fileoff); + const off = @intCast(u32, text_section.offset + text_section.size); log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.sections.append(self.base.allocator, .{ - .sectname = makeStaticString("__got"), - .segname = makeStaticString("__DATA"), - .addr = data_segment.vmaddr, + .sectname = makeStaticString("__ziggot"), + .segname = makeStaticString("__TEXT"), + .addr = text_section.addr + text_section.size, .size = file_size, .offset = off, - .@"align" = 3, // 2^3 = 8 + .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, @@ -1264,23 +1239,26 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved3 = 0, }); - const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - data_segment.vmsize = segment_size; - data_segment.filesize = segment_size; + const added_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + text_segment.vmsize += added_size; + text_segment.filesize += added_size; + text_segment.cmdsize += @sizeOf(macho.section_64); + text_segment.nsects += 1; self.cmd_table_dirty = true; } if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); - const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; const initprot = macho.VM_PROT_READ; - const off = data_segment.fileoff + data_segment.filesize; + const off = text_segment.fileoff + text_segment.filesize; try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString("__LINKEDIT"), - .vmaddr = data_segment.vmaddr + data_segment.vmsize, + .vmaddr = text_segment.vmaddr + text_segment.vmsize, .vmsize = 0, .fileoff = off, .filesize = 0, @@ -1671,11 +1649,24 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const sect = &self.sections.items[self.got_section_index.?]; const endian = self.base.options.target.cpu.arch.endian(); - var buf: [@sizeOf(u64)]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + const off = sect.offset + @sizeOf(u64) * index; + const vmaddr = sect.addr + @sizeOf(u64) * index; + const pos_symbol_off = @truncate(u31, vmaddr - self.offset_table.items[index] + 7); + const symbol_off = @intCast(i32, pos_symbol_off) * -1; + std.debug.print("vmaddr=0x{x},item=0x{x}\n", .{vmaddr, self.offset_table.items[index]}); + std.debug.print("posSymbolOff=0x{x},symbolOff=0x{x}\n", .{pos_symbol_off, @bitCast(u32, symbol_off)}); + + var code: [8]u8 = undefined; + // lea %rax, [rip - disp] + code[0] = 0x48; + code[1] = 0x8D; + code[2] = 0x5; + mem.writeInt(u32, code[3..7], @bitCast(u32, symbol_off), endian); + // ret + code[7] = 0xC3; log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); - try self.base.file.?.pwriteAll(&buf, off); + try self.base.file.?.pwriteAll(&code, off); } fn writeSymbolTable(self: *MachO) !void { @@ -1791,7 +1782,7 @@ fn writeExportTrie(self: *MachO) !void { if (export_size > buffer.items.len) { // Pad out to align(8). - try self.base.file.?.pwriteAll(&[_]u8{ 0 }, dyld_info.export_off + export_size); + try self.base.file.?.pwriteAll(&[_]u8{0}, dyld_info.export_off + export_size); } try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off); @@ -1816,7 +1807,7 @@ fn writeStringTable(self: *MachO) !void { if (symtab.strsize > needed_size) { // Pad out to align(8); - try self.base.file.?.pwriteAll(&[_]u8{ 0 }, symtab.stroff + symtab.strsize); + try self.base.file.?.pwriteAll(&[_]u8{0}, symtab.stroff + symtab.strsize); } try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); @@ -1843,7 +1834,6 @@ fn writeCmdHeaders(self: *MachO) !void { last_cmd_offset += cmd.cmdsize(); } { - // write __text section header const off = if (self.text_segment_cmd_index) |text_segment_index| blk: { var i: usize = 0; var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); @@ -1856,27 +1846,14 @@ fn writeCmdHeaders(self: *MachO) !void { // only one, noname segment to append this section header to. return error.TODOImplementWritingObjFiles; }; - const idx = self.text_section_index.?; + // write __text section header + const id1 = self.text_section_index.?; log.debug("writing text section header at 0x{x}\n", .{off}); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off); - } - { - // write __got section header - const off = if (self.data_segment_cmd_index) |data_segment_index| blk: { - var i: usize = 0; - var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); - while (i < data_segment_index) : (i += 1) { - cmdsize += self.load_commands.items[i].cmdsize(); - } - break :blk cmdsize; - } else { - // If we've landed in here, we are building a MachO object file, so we have - // only one, noname segment to append this section header to. - return error.TODOImplementWritingObjFiles; - }; - const idx = self.got_section_index.?; - log.debug("writing got section header at 0x{x}\n", .{off}); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id1 .. id1 + 1]), off); + // write __ziggot section header + const id2 = self.got_section_index.?; + log.debug("writing got section header at 0x{x}\n", .{off + @sizeOf(macho.section_64)}); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id2 .. id2 + 1]), off + @sizeOf(macho.section_64)); } } diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index a7097d4cc080..6262315eb8b6 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -67,8 +67,6 @@ pub fn init(alloc: *Allocator) CodeSignature { pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment; - const data_segment = bin_file.load_commands.items[bin_file.data_segment_cmd_index.?].Segment; - const linkedit_segment = bin_file.load_commands.items[bin_file.linkedit_segment_cmd_index.?].Segment; const code_sig_cmd = bin_file.load_commands.items[bin_file.code_signature_cmd_index.?].LinkeditData; const execSegBase: u64 = text_segment.fileoff; From 2cd84b1b3f0a6e3f030da723ec51f0be33b7a1ed Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 24 Nov 2020 20:32:09 +0100 Subject: [PATCH 28/37] stage2 macho: refactor PIE generation on x86_64 --- src/codegen.zig | 4 +-- src/link/MachO.zig | 70 +++++++++++++++++++++------------------------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 85b039069af8..f75ad079ae87 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2773,7 +2773,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // later in the linker. if (reg.id() == 0) { // %rax is special-cased try self.code.ensureCapacity(self.code.items.len + 5); - try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ + try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ .address = x, .start = self.code.items.len, .len = 5, @@ -2790,7 +2790,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.code.ensureCapacity(self.code.items.len + 10); // push %rax self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); - try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{ + try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ .address = x, .start = self.code.items.len, .len = 5, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index bb4813a9df0f..20d9c3e78e78 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -214,16 +214,15 @@ pub const TextBlock = struct { /// Unlike in Elf, we need to store the size of this symbol as part of /// the TextBlock since macho.nlist_64 lacks this information. size: u64, - /// List of RIP-relative positions in the code - /// This is a table of all RIP-relative positions that will need fixups + /// List of PIE fixups in the code. + /// This is a table of all position-relative positions that will need fixups /// after codegen when linker assigns addresses to GOT entries. - /// TODO handle freeing, shrinking and re-allocs - rip_positions: std.ArrayListUnmanaged(RipPosition) = .{}, + pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, /// Points to the previous and next neighbours prev: ?*TextBlock, next: ?*TextBlock, - pub const RipPosition = struct { + pub const PieFixup = struct { address: u64, start: usize, len: usize, @@ -237,13 +236,12 @@ pub const TextBlock = struct { .next = null, }; - pub fn addRipPosition(self: *TextBlock, alloc: *Allocator, rip: RipPosition) !void { - std.debug.print("text_block={}, rip={}\n", .{ self.local_sym_index, rip }); - return self.rip_positions.append(alloc, rip); + pub fn addPieFixup(self: *TextBlock, alloc: *Allocator, fixup: PieFixup) !void { + return self.pie_fixups.append(alloc, fixup); } fn deinit(self: *TextBlock, alloc: *Allocator) void { - self.rip_positions.deinit(alloc); + self.pie_fixups.deinit(alloc); } /// Returns how much room there is to grow in virtual address space. @@ -850,6 +848,9 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 { } pub fn deinit(self: *MachO) void { + for (self.text_block_free_list.items) |tb| { + tb.deinit(self.base.allocator); + } self.text_block_free_list.deinit(self.base.allocator); self.offset_table.deinit(self.base.allocator); self.offset_table_free_list.deinit(self.base.allocator); @@ -892,7 +893,9 @@ fn freeTextBlock(self: *MachO, text_block: *TextBlock) void { if (!already_have_free_list_node and prev.freeListEligible(self.*)) { // The free list is heuristics, it doesn't have to be perfect, so we can ignore // the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch {}; + self.text_block_free_list.append(self.base.allocator, prev) catch { + prev.deinit(self.base.allocator); + }; } } else { text_block.prev = null; @@ -982,7 +985,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, symbol.n_value, vaddr }); if (vaddr != symbol.n_value) { symbol.n_value = vaddr; - log.debug(" (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.macho.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); @@ -1013,17 +1015,13 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } - // Perform RIP-relative fixups (if any) + // Perform PIE fixups (if any) const got_section = self.sections.items[self.got_section_index.?]; - for (decl.link.macho.rip_positions.items) |rip| { - std.debug.print("rip={}\n", .{rip}); - const target_addr = rip.address; - // const got_addr = got_section.addr + decl.link.macho.offset_table_index * @sizeOf(u64); - const this_addr = symbol.n_value + rip.start; - std.debug.print("target_addr=0x{x},this_addr=0x{x}\n", .{ target_addr, this_addr }); - const displacement = @intCast(u32, target_addr - this_addr - rip.len); - std.debug.print("displacement=0x{x}\n", .{displacement}); - var placeholder = code_buffer.items[rip.start + rip.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; + while (decl.link.macho.pie_fixups.popOrNull()) |fixup| { + const target_addr = fixup.address; + const this_addr = symbol.n_value + fixup.start; + const displacement = @intCast(u32, target_addr - this_addr - fixup.len); + var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; mem.writeIntSliceLittle(u32, placeholder, displacement); } @@ -1185,8 +1183,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.text_section_index == null) { self.text_section_index = @intCast(u16, self.sections.items.len); const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - text_segment.cmdsize += @sizeOf(macho.section_64); - text_segment.nsects += 1; const program_code_size_hint = self.base.options.program_code_size_hint; const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, self.page_size); @@ -1212,11 +1208,13 @@ pub fn populateMissingMetadata(self: *MachO) !void { text_segment.vmsize = file_size + off; // We add off here since __TEXT segment includes everything prior to __text section. text_segment.filesize = file_size + off; + text_segment.cmdsize += @sizeOf(macho.section_64); + text_segment.nsects += 1; self.cmd_table_dirty = true; } if (self.got_section_index == null) { - const text_section = &self.sections.items[self.text_section_index.?]; self.got_section_index = @intCast(u16, self.sections.items.len); + const text_section = &self.sections.items[self.text_section_index.?]; const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; // TODO looking for free space should be done *within* a segment it belongs to @@ -1225,7 +1223,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.sections.append(self.base.allocator, .{ - .sectname = makeStaticString("__ziggot"), + .sectname = makeStaticString("__got"), .segname = makeStaticString("__TEXT"), .addr = text_section.addr + text_section.size, .size = file_size, @@ -1239,8 +1237,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved3 = 0, }); - const added_size = mem.alignForwardGeneric(u64, file_size, self.page_size); const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const added_size = mem.alignForwardGeneric(u64, file_size, self.page_size); text_segment.vmsize += added_size; text_segment.filesize += added_size; text_segment.cmdsize += @sizeOf(macho.section_64); @@ -1653,18 +1651,17 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const off = sect.offset + @sizeOf(u64) * index; const vmaddr = sect.addr + @sizeOf(u64) * index; const pos_symbol_off = @truncate(u31, vmaddr - self.offset_table.items[index] + 7); - const symbol_off = @intCast(i32, pos_symbol_off) * -1; - std.debug.print("vmaddr=0x{x},item=0x{x}\n", .{vmaddr, self.offset_table.items[index]}); - std.debug.print("posSymbolOff=0x{x},symbolOff=0x{x}\n", .{pos_symbol_off, @bitCast(u32, symbol_off)}); + const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1); var code: [8]u8 = undefined; // lea %rax, [rip - disp] code[0] = 0x48; code[1] = 0x8D; code[2] = 0x5; - mem.writeInt(u32, code[3..7], @bitCast(u32, symbol_off), endian); + mem.writeInt(u32, code[3..7], symbol_off, endian); // ret code[7] = 0xC3; + log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off); } @@ -1846,14 +1843,11 @@ fn writeCmdHeaders(self: *MachO) !void { // only one, noname segment to append this section header to. return error.TODOImplementWritingObjFiles; }; - // write __text section header - const id1 = self.text_section_index.?; - log.debug("writing text section header at 0x{x}\n", .{off}); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id1 .. id1 + 1]), off); - // write __ziggot section header - const id2 = self.got_section_index.?; - log.debug("writing got section header at 0x{x}\n", .{off + @sizeOf(macho.section_64)}); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id2 .. id2 + 1]), off + @sizeOf(macho.section_64)); + // write sections belonging to __TEXT segment + // TODO section indices should belong to each Segment, and we should iterate dynamically. + const id = self.text_section_index.?; + log.debug("writing __TEXT section headers at 0x{x}\n", .{off}); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id .. id + 2]), off); } } From 10942e3f86c03d30833cc221371f30b78a4bd710 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 25 Nov 2020 11:10:17 +0100 Subject: [PATCH 29/37] stage2 macho: Hello, Silicon! --- src/codegen.zig | 26 ++++++++++++++++++++++---- src/link/MachO.zig | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index f75ad079ae87..1482e8de7e1d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1689,6 +1689,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .aarch64 => { try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr }); + // blr x30 writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32()); }, else => unreachable, // unsupported architecture on MachO @@ -2583,10 +2584,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), .memory => |addr| { - // The value is in memory at a hard-coded address. - // If the type is a pointer, it means the pointer address is at this memory location. - try self.genSetReg(src, reg, .{ .immediate = addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32()); + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + // For MachO, the binary, with the exception of object files, has to be a PIE. + // Therefore we cannot load an absolute address. + // Instead, we need to make use of PC-relative addressing. + // if (reg.id() == 0) { // x0 is special-cased + try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + .address = addr, + .start = self.code.items.len, + .len = 4, + }); + // bl [label] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32()); + // } else { + // unreachable; // TODO + // } + } else { + // The value is in memory at a hard-coded address. + // If the type is a pointer, it means the pointer address is at this memory location. + try self.genSetReg(src, reg, .{ .immediate = addr }); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32()); + } }, else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), }, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 20d9c3e78e78..b6f5cb9ce8f5 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -7,6 +7,7 @@ const fs = std.fs; const log = std.log.scoped(.link); const macho = std.macho; const codegen = @import("../codegen.zig"); +const aarch64 = @import("../codegen/aarch64.zig"); const math = std.math; const mem = std.mem; @@ -1020,9 +1021,15 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { while (decl.link.macho.pie_fixups.popOrNull()) |fixup| { const target_addr = fixup.address; const this_addr = symbol.n_value + fixup.start; - const displacement = @intCast(u32, target_addr - this_addr - fixup.len); - var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; - mem.writeIntSliceLittle(u32, placeholder, displacement); + if (self.base.options.target.cpu.arch == .x86_64) { + const displacement = @intCast(u32, target_addr - this_addr - fixup.len); + var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; + mem.writeIntSliceLittle(u32, placeholder, displacement); + } else { + const displacement = @intCast(u27, target_addr - this_addr); + var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + } } const text_section = self.sections.items[self.text_section_index.?]; @@ -1646,22 +1653,28 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const sect = &self.sections.items[self.got_section_index.?]; - const endian = self.base.options.target.cpu.arch.endian(); - const off = sect.offset + @sizeOf(u64) * index; const vmaddr = sect.addr + @sizeOf(u64) * index; - const pos_symbol_off = @truncate(u31, vmaddr - self.offset_table.items[index] + 7); - const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1); var code: [8]u8 = undefined; - // lea %rax, [rip - disp] - code[0] = 0x48; - code[1] = 0x8D; - code[2] = 0x5; - mem.writeInt(u32, code[3..7], symbol_off, endian); - // ret - code[7] = 0xC3; - + if (self.base.options.target.cpu.arch == .x86_64) { + const pos_symbol_off = @intCast(u31, vmaddr - self.offset_table.items[index] + 7); + const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1); + // lea %rax, [rip - disp] + code[0] = 0x48; + code[1] = 0x8D; + code[2] = 0x5; + mem.writeIntLittle(u32, code[3..7], symbol_off); + // ret + code[7] = 0xC3; + } else { + const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]); + const symbol_off = @intCast(i21, pos_symbol_off) * -1; + // adr .x0 [-disp] + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x1, symbol_off).toU32()); + // ret + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(null).toU32()); + } log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off); } From c749b78df50160bedae40f90765442dd1f49de3a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 25 Nov 2020 19:58:15 +0100 Subject: [PATCH 30/37] stage2 macho: add orr and orn instructions --- src/codegen.zig | 59 ++++++++++++++++++++++++--- src/codegen/aarch64.zig | 89 +++++++++++++++++++++++++++++++++++++++++ src/link/MachO.zig | 10 ++--- 3 files changed, 147 insertions(+), 11 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 1482e8de7e1d..35443ba0b136 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2588,17 +2588,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // For MachO, the binary, with the exception of object files, has to be a PIE. // Therefore we cannot load an absolute address. // Instead, we need to make use of PC-relative addressing. - // if (reg.id() == 0) { // x0 is special-cased + // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack + // like described here: + // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop) + // TODO As far as branching is concerned, instead of saving the return address + // in a register, I'm thinking here of immitating x86_64, and having the address + // passed on the stack. + if (reg.id() == 0) { // x0 is special-cased + // str x28, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // adr x28, #8 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ .address = addr, .start = self.code.items.len, .len = 4, }); - // bl [label] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32()); - // } else { - // unreachable; // TODO - // } + // b [label] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); + // mov r, x0 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32()); + // ldr x28, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + } else { + // str x28, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // str x0, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x0, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // adr x28, #8 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); + try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + .address = addr, + .start = self.code.items.len, + .len = 4, + }); + // b [label] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); + // mov r, x0 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32()); + // ldr x0, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x0, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + // ldr x28, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + } } else { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 18fccb823ef1..33b4a14eda6c 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -19,6 +19,8 @@ pub const Register = enum(u6) { w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30, wzr, + pub const sp = .xzr; + pub fn id(self: Register) u5 { return @truncate(u5, @enumToInt(self)); } @@ -195,6 +197,17 @@ test "FloatingPointRegister.toX" { /// Represents an instruction in the AArch64 instruction set pub const Instruction = union(enum) { + OrShiftedRegister: packed struct { + rd: u5, + rn: u5, + imm6: u6, + rm: u5, + n: u1, + shift: u2, + fixed: u5 = 0b01010, + opc: u2 = 0b01, + sf: u1, + }, MoveWideImmediate: packed struct { rd: u5, imm16: u16, @@ -251,6 +264,7 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { + .OrShiftedRegister => |v| @bitCast(u32, v), .MoveWideImmediate => |v| @bitCast(u32, v), .PCRelativeAddress => |v| @bitCast(u32, v), .LoadStoreRegister => |v| @bitCast(u32, v), @@ -379,8 +393,65 @@ pub const Instruction = union(enum) { } }; + pub const RegisterShift = struct { + rn: u5, + imm6: u6, + shift: enum(u2) { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + }, + + pub fn none() RegisterShift { + return .{ + .rn = 0b11111, + .imm6 = 0, + .shift = .Lsl, + }; + } + }; + // Helper functions for assembly syntax functions + fn orShiftedRegister( + rd: Register, + rm: Register, + shift: RegisterShift, + invert: bool, + ) Instruction { + const n: u1 = if (invert) 1 else 0; + switch (rd.size()) { + 32 => { + return Instruction{ + .OrShiftedRegister = .{ + .rd = rd.id(), + .rn = shift.rn, + .imm6 = shift.imm6, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .sf = 0, + }, + }; + }, + 64 => { + return Instruction{ + .OrShiftedRegister = .{ + .rd = rd.id(), + .rn = shift.rn, + .imm6 = shift.imm6, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .sf = 1, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + fn moveWideImmediate( opc: u2, rd: Register, @@ -543,6 +614,16 @@ pub const Instruction = union(enum) { }; } + // Bitwise (inclusive) OR of a register value + + pub fn orr(rd: Register, rm: Register, shift: RegisterShift) Instruction { + return orShiftedRegister(rd, rm, shift, false); + } + + pub fn orn(rd: Register, rm: Register, shift: RegisterShift) Instruction { + return orShiftedRegister(rd, rm, shift, true); + } + // Move wide (immediate) pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction { @@ -653,6 +734,14 @@ test "serialize instructions" { }; const testcases = [_]Testcase{ + .{ // orr x0 x1 + .inst = Instruction.orr(.x0, .x1, Instruction.RegisterShift.none()), + .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, + }, + .{ // orn x0 x1 + .inst = Instruction.orn(.x0, .x1, Instruction.RegisterShift.none()), + .expected = 0b1_01_01010_00_1_00001_000000_11111_00000, + }, .{ // movz x1 #4 .inst = Instruction.movz(.x1, 4, 0), .expected = 0b1_10_100101_00_0000000000000100_00001, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b6f5cb9ce8f5..e0d5cc0d3c8c 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1028,7 +1028,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } else { const displacement = @intCast(u27, target_addr - this_addr); var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); } } @@ -1670,10 +1670,10 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { } else { const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]); const symbol_off = @intCast(i21, pos_symbol_off) * -1; - // adr .x0 [-disp] - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x1, symbol_off).toU32()); - // ret - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(null).toU32()); + // adr x0, #-disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); + // ret x28 + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); } log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off); From 64eae8f39240109ce23c21e975c1d29191b4692a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 25 Nov 2020 22:02:59 +0100 Subject: [PATCH 31/37] stage2 macho: move PIE fixups to link file; fix tests --- src/codegen.zig | 8 ++++---- src/link/MachO.zig | 46 ++++++++++++++++++++-------------------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 35443ba0b136..af91e7e909dd 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2601,7 +2601,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }).toU32()); // adr x28, #8 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ .address = addr, .start = self.code.items.len, .len = 4, @@ -2626,7 +2626,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }).toU32()); // adr x28, #8 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ .address = addr, .start = self.code.items.len, .len = 4, @@ -2838,7 +2838,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // later in the linker. if (reg.id() == 0) { // %rax is special-cased try self.code.ensureCapacity(self.code.items.len + 5); - try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ .address = x, .start = self.code.items.len, .len = 5, @@ -2855,7 +2855,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.code.ensureCapacity(self.code.items.len + 10); // push %rax self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); - try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ .address = x, .start = self.code.items.len, .len = 5, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e0d5cc0d3c8c..e66b6ab413df 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -175,6 +175,22 @@ libsystem_cmd_dirty: bool = false, text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, /// Pointer to the last allocated text block last_text_block: ?*TextBlock = null, +/// A list of all PIE fixups required for this run of the linker. +/// Warning, this is currently NOT thread-safe. See the TODO below. +/// TODO Move this list inside `updateDecl` where it should be allocated +/// prior to calling `generateSymbol`, and then immediately deallocated +/// rather than sitting in the global scope. +pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, + +pub const PieFixup = struct { + /// Target address we wanted to address in absolute terms. + address: u64, + /// Where in the byte stream we should perform the fixup. + start: usize, + /// The length of the byte stream. For x86_64, this will be + /// variable. For aarch64, it will be fixed at 4 bytes. + len: usize, +}; /// `alloc_num / alloc_den` is the factor of padding when allocating. const alloc_num = 4; @@ -215,20 +231,10 @@ pub const TextBlock = struct { /// Unlike in Elf, we need to store the size of this symbol as part of /// the TextBlock since macho.nlist_64 lacks this information. size: u64, - /// List of PIE fixups in the code. - /// This is a table of all position-relative positions that will need fixups - /// after codegen when linker assigns addresses to GOT entries. - pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, /// Points to the previous and next neighbours prev: ?*TextBlock, next: ?*TextBlock, - pub const PieFixup = struct { - address: u64, - start: usize, - len: usize, - }; - pub const empty = TextBlock{ .local_sym_index = 0, .offset_table_index = undefined, @@ -237,14 +243,6 @@ pub const TextBlock = struct { .next = null, }; - pub fn addPieFixup(self: *TextBlock, alloc: *Allocator, fixup: PieFixup) !void { - return self.pie_fixups.append(alloc, fixup); - } - - fn deinit(self: *TextBlock, alloc: *Allocator) void { - self.pie_fixups.deinit(alloc); - } - /// Returns how much room there is to grow in virtual address space. /// File offset relocation happens transparently, so it is not included in /// this calculation. @@ -849,9 +847,7 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 { } pub fn deinit(self: *MachO) void { - for (self.text_block_free_list.items) |tb| { - tb.deinit(self.base.allocator); - } + self.pie_fixups.deinit(self.base.allocator); self.text_block_free_list.deinit(self.base.allocator); self.offset_table.deinit(self.base.allocator); self.offset_table_free_list.deinit(self.base.allocator); @@ -894,9 +890,7 @@ fn freeTextBlock(self: *MachO, text_block: *TextBlock) void { if (!already_have_free_list_node and prev.freeListEligible(self.*)) { // The free list is heuristics, it doesn't have to be perfect, so we can ignore // the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch { - prev.deinit(self.base.allocator); - }; + self.text_block_free_list.append(self.base.allocator, prev) catch {}; } } else { text_block.prev = null; @@ -1018,7 +1012,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { // Perform PIE fixups (if any) const got_section = self.sections.items[self.got_section_index.?]; - while (decl.link.macho.pie_fixups.popOrNull()) |fixup| { + while (self.pie_fixups.popOrNull()) |fixup| { const target_addr = fixup.address; const this_addr = symbol.n_value + fixup.start; if (self.base.options.target.cpu.arch == .x86_64) { @@ -1761,7 +1755,7 @@ fn writeCodeSignature(self: *MachO) !void { } fn writeExportTrie(self: *MachO) !void { - assert(self.global_symbols.items.len > 0); + if (self.global_symbols.items.len == 0) return; var trie: Trie = .{}; defer trie.deinit(self.base.allocator); From 7e8f7da3ec0a841d7d009f9f55dad22235b418d3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 26 Nov 2020 11:21:50 +0100 Subject: [PATCH 32/37] stage2 macho: rename inodes to prevent SIGKILL --- src/link.zig | 36 +++++++++++++++++++++++++++++++++++- src/main.zig | 3 +++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/link.zig b/src/link.zig index 064c178d661c..2b53942ff73d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -238,7 +238,41 @@ pub const File = struct { pub fn makeExecutable(base: *File) !void { switch (base.tag) { - .coff, .elf, .macho => if (base.file) |f| { + .macho => if (base.file) |f| { + if (base.intermediary_basename != null) { + // The file we have open is not the final file that we want to + // make executable, so we don't have to close it. + return; + } + if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) { + if (base.options.target.cpu.arch != .aarch64) return; // If we're not targeting aarch64, nothing to do. + // XNU starting with Big Sur running on arm64 is caching inodes of running binaries. + // Any change to the binary will effectively invalidate the kernel's cache + // resulting in a SIGKILL on each subsequent run. Since when doing incremental + // linking we're modifying a binary in-place, this will end up with the kernel + // killing it on every subsequent run. To circumvent it, we will copy the file + // into a new inode, remove the original file, and rename the copy to match + // the original file. This is super messy, but there doesn't seem any other + // way to please the XNU. + const random_bytes_len = 12; + comptime const random_sub_path_len = std.base64.Base64Encoder.calcSize(random_bytes_len); + const emit = base.options.emit orelse return; + var random_bytes: [random_bytes_len]u8 = undefined; + try std.crypto.randomBytes(&random_bytes); + var random_sub_path: [random_sub_path_len]u8 = undefined; + fs.base64_encoder.encode(&random_sub_path, &random_bytes); + const tmp_file_name = try mem.join(base.allocator, "_", &[_][]const u8{ emit.sub_path, random_sub_path[0..] }); + defer base.allocator.free(tmp_file_name); + var tmp_file = try emit.directory.handle.createFile(tmp_file_name, .{ .mode = determineMode(base.options) }); + defer tmp_file.close(); + const stat = try f.stat(); + _ = try f.copyRangeAll(0, tmp_file, 0, stat.size); + try emit.directory.handle.rename(tmp_file_name, emit.sub_path); + } + f.close(); + base.file = null; + }, + .coff, .elf => if (base.file) |f| { if (base.intermediary_basename != null) { // The file we have open is not the final file that we want to // make executable, so we don't have to close it. diff --git a/src/main.zig b/src/main.zig index 595faed88f77..8de94e1abd24 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1727,6 +1727,9 @@ fn buildOutputType( error.SemanticAnalyzeFail => process.exit(1), else => |e| return e, }; + if (output_mode == .Exe) { + try comp.makeBinFileExecutable(); + } if (build_options.is_stage1 and comp.stage1_lock != null and watch) { warn("--watch is not recommended with the stage1 backend; it leaks memory and is not capable of incremental compilation", .{}); From f125c4f5c795485e3c927c85198828793c1be434 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 26 Nov 2020 11:25:36 +0100 Subject: [PATCH 33/37] stage2 macho: enable end-to-end incremental linking tests on aarch64 --- test/stage2/aarch64.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig index 323d63b9feb1..b71362cd3d0e 100644 --- a/test/stage2/aarch64.zig +++ b/test/stage2/aarch64.zig @@ -12,9 +12,7 @@ const linux_aarch64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - // TODO enable when we add codesigning to the self-hosted linker - // related to #6971 - if (false) { + { var case = ctx.exe("hello world with updates", macos_aarch64); // Regular old hello world From a8f8d8221595a422b61ceca8835c677f6830799d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 26 Nov 2020 20:21:53 +0100 Subject: [PATCH 34/37] stage2 macho: use Dir.copyFile instead of manual create+copy --- src/link.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/link.zig b/src/link.zig index 2b53942ff73d..98082570b79c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -263,10 +263,7 @@ pub const File = struct { fs.base64_encoder.encode(&random_sub_path, &random_bytes); const tmp_file_name = try mem.join(base.allocator, "_", &[_][]const u8{ emit.sub_path, random_sub_path[0..] }); defer base.allocator.free(tmp_file_name); - var tmp_file = try emit.directory.handle.createFile(tmp_file_name, .{ .mode = determineMode(base.options) }); - defer tmp_file.close(); - const stat = try f.stat(); - _ = try f.copyRangeAll(0, tmp_file, 0, stat.size); + try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_file_name, .{}); try emit.directory.handle.rename(tmp_file_name, emit.sub_path); } f.close(); From ebb2f208159c46fed3aadfcefd2a5524bc46011b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 26 Nov 2020 21:12:20 +0100 Subject: [PATCH 35/37] stage2 macho: Dir.copyFile does the rename for us! --- src/link.zig | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/link.zig b/src/link.zig index 98082570b79c..e8a9f3d7308f 100644 --- a/src/link.zig +++ b/src/link.zig @@ -254,17 +254,8 @@ pub const File = struct { // into a new inode, remove the original file, and rename the copy to match // the original file. This is super messy, but there doesn't seem any other // way to please the XNU. - const random_bytes_len = 12; - comptime const random_sub_path_len = std.base64.Base64Encoder.calcSize(random_bytes_len); const emit = base.options.emit orelse return; - var random_bytes: [random_bytes_len]u8 = undefined; - try std.crypto.randomBytes(&random_bytes); - var random_sub_path: [random_sub_path_len]u8 = undefined; - fs.base64_encoder.encode(&random_sub_path, &random_bytes); - const tmp_file_name = try mem.join(base.allocator, "_", &[_][]const u8{ emit.sub_path, random_sub_path[0..] }); - defer base.allocator.free(tmp_file_name); - try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_file_name, .{}); - try emit.directory.handle.rename(tmp_file_name, emit.sub_path); + try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, emit.sub_path, .{}); } f.close(); base.file = null; From 02baaac506d0d64d1c946219335b7492222c4b64 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 27 Nov 2020 20:31:26 +0100 Subject: [PATCH 36/37] Update src/codegen.zig Co-authored-by: Andrew Kelley --- src/codegen.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen.zig b/src/codegen.zig index af91e7e909dd..8bd08af15ce1 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2584,7 +2584,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), .memory => |addr| { - if (self.bin_file.cast(link.File.MachO)) |macho_file| { + if (self.bin_file.options.pie) { // For MachO, the binary, with the exception of object files, has to be a PIE. // Therefore we cannot load an absolute address. // Instead, we need to make use of PC-relative addressing. From 5ed76268c9f0ecca8c5d62cb7e56da05aa8f1f7c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 27 Nov 2020 20:55:34 +0100 Subject: [PATCH 37/37] stage2 macho: apply more review comments --- src/codegen.zig | 58 +++++++++++++++++++++++++++++----------------- src/link/MachO.zig | 58 ++++++++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 8bd08af15ce1..74800aa22e0c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2601,11 +2601,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }).toU32()); // adr x28, #8 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = addr, - .start = self.code.items.len, - .len = 4, - }); + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .address = addr, + .start = self.code.items.len, + .len = 4, + }); + } else { + return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); + } // b [label] mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); // mov r, x0 @@ -2626,11 +2630,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }).toU32()); // adr x28, #8 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = addr, - .start = self.code.items.len, - .len = 4, - }); + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .address = addr, + .start = self.code.items.len, + .len = 4, + }); + } else { + return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); + } // b [label] mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); // mov r, x0 @@ -2828,7 +2836,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); }, .memory => |x| { - if (self.bin_file.cast(link.File.MachO)) |macho_file| { + if (self.bin_file.options.pie) { // For MachO, the binary, with the exception of object files, has to be a PIE. // Therefore, we cannot load an absolute address. assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO. @@ -2838,11 +2846,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // later in the linker. if (reg.id() == 0) { // %rax is special-cased try self.code.ensureCapacity(self.code.items.len + 5); - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = x, - .start = self.code.items.len, - .len = 5, - }); + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .address = x, + .start = self.code.items.len, + .len = 5, + }); + } else { + return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); + } // call [label] self.code.appendSliceAssumeCapacity(&[_]u8{ 0xE8, @@ -2855,11 +2867,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.code.ensureCapacity(self.code.items.len + 10); // push %rax self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = x, - .start = self.code.items.len, - .len = 5, - }); + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .address = x, + .start = self.code.items.len, + .len = 5, + }); + } else { + return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); + } // call [label] self.code.appendSliceAssumeCapacity(&[_]u8{ 0xE8, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e66b6ab413df..d2a03cae18d8 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1015,14 +1015,18 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { while (self.pie_fixups.popOrNull()) |fixup| { const target_addr = fixup.address; const this_addr = symbol.n_value + fixup.start; - if (self.base.options.target.cpu.arch == .x86_64) { - const displacement = @intCast(u32, target_addr - this_addr - fixup.len); - var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; - mem.writeIntSliceLittle(u32, placeholder, displacement); - } else { - const displacement = @intCast(u27, target_addr - this_addr); - var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); + switch (self.base.options.target.cpu.arch) { + .x86_64 => { + const displacement = @intCast(u32, target_addr - this_addr - fixup.len); + var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; + mem.writeIntSliceLittle(u32, placeholder, displacement); + }, + .aarch64 => { + const displacement = @intCast(u27, target_addr - this_addr); + var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); + }, + else => unreachable, // unsupported target architecture } } @@ -1651,23 +1655,27 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const vmaddr = sect.addr + @sizeOf(u64) * index; var code: [8]u8 = undefined; - if (self.base.options.target.cpu.arch == .x86_64) { - const pos_symbol_off = @intCast(u31, vmaddr - self.offset_table.items[index] + 7); - const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1); - // lea %rax, [rip - disp] - code[0] = 0x48; - code[1] = 0x8D; - code[2] = 0x5; - mem.writeIntLittle(u32, code[3..7], symbol_off); - // ret - code[7] = 0xC3; - } else { - const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]); - const symbol_off = @intCast(i21, pos_symbol_off) * -1; - // adr x0, #-disp - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); - // ret x28 - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); + switch (self.base.options.target.cpu.arch) { + .x86_64 => { + const pos_symbol_off = @intCast(u31, vmaddr - self.offset_table.items[index] + 7); + const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1); + // lea %rax, [rip - disp] + code[0] = 0x48; + code[1] = 0x8D; + code[2] = 0x5; + mem.writeIntLittle(u32, code[3..7], symbol_off); + // ret + code[7] = 0xC3; + }, + .aarch64 => { + const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]); + const symbol_off = @intCast(i21, pos_symbol_off) * -1; + // adr x0, #-disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); + // ret x28 + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); + }, + else => unreachable, // unsupported target architecture } log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off);