From f33b644c4ff776f9e0947d592ac7a5df452710b6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 18 Dec 2020 11:03:26 +0100 Subject: [PATCH 01/13] macho: redo how we allocate within a segment Firstly, we preallocate segments offset and sizes, and then when adding sections, we find the free space within each segment. Currently, this applies to any segment that is not __LINKEDIT segment since this requires special treatment. --- src/link/MachO.zig | 214 +++++++++++++----------------------- src/link/MachO/commands.zig | 40 ++++++- 2 files changed, 118 insertions(+), 136 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 13704d8839..597b957f98 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -37,6 +37,10 @@ page_size: u16, /// Mach-O header header: ?macho.mach_header_64 = null, +/// We commit 0x1000 = 4096 bytes of space to the header and +/// the table of load commands. This should be plenty for any +/// potential future extensions. +header_pad: u16 = 0x1000, /// Table of all load commands load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -75,7 +79,7 @@ code_signature_cmd_index: ?u16 = null, /// Index into __TEXT,__text section. text_section_index: ?u16 = null, -/// Index into __TEXT,__got section. +/// Index into __TEXT,__ziggot section. got_section_index: ?u16 = null, /// The absolute address of the entry point. entry_addr: ?u64 = null, @@ -150,8 +154,8 @@ pub const PieFixup = struct { }; /// `alloc_num / alloc_den` is the factor of padding when allocating. -const alloc_num = 4; -const alloc_den = 3; +pub const alloc_num = 4; +pub const alloc_den = 3; /// Default path to dyld /// TODO instead of hardcoding it, we should probably look through some env vars and search paths @@ -1295,15 +1299,23 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; const initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; + + const program_code_size_hint = self.base.options.program_code_size_hint; + const offset_table_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint; + const ideal_size = self.header_pad + program_code_size_hint + offset_table_size_hint; + const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); + + log.debug("found __TEXT segment free space 0x{x} to 0x{x}\n", .{ 0, needed_size }); + try self.load_commands.append(self.base.allocator, .{ .Segment = SegmentCommand.empty(.{ .cmd = macho.LC_SEGMENT_64, .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString("__TEXT"), .vmaddr = 0x100000000, // always starts at 4GB - .vmsize = 0, + .vmsize = needed_size, .fileoff = 0, - .filesize = 0, + .filesize = needed_size, .maxprot = maxprot, .initprot = initprot, .nsects = 0, @@ -1316,31 +1328,31 @@ pub fn populateMissingMetadata(self: *MachO) !void { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; self.text_section_index = @intCast(u16, text_segment.sections.items.len); - 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); - const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); // TODO maybe findFreeSpace should return u32 directly? + const alignment: u32 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; + const needed_size = self.base.options.program_code_size_hint; + const off = self.header_pad; - log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); - try text_segment.sections.append(self.base.allocator, .{ + try text_segment.addSection(self.base.allocator, .{ .sectname = makeStaticString("__text"), .segname = makeStaticString("__TEXT"), .addr = text_segment.inner.vmaddr + off, - .size = file_size, + .size = @intCast(u32, needed_size), .offset = off, - .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0, // 2^2 for aarch64, 2^0 for x86_64 + .@"align" = alignment, .reloff = 0, .nreloc = 0, - .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .flags = flags, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); - - text_segment.inner.vmsize = file_size + off; // We add off here since __TEXT segment includes everything prior to __text section. - text_segment.inner.filesize = file_size + off; - text_segment.inner.cmdsize += @sizeOf(macho.section_64); - text_segment.inner.nsects += 1; self.cmd_table_dirty = true; } if (self.got_section_index == null) { @@ -1348,48 +1360,48 @@ pub fn populateMissingMetadata(self: *MachO) !void { const text_section = &text_segment.sections.items[self.text_section_index.?]; self.got_section_index = @intCast(u16, text_segment.sections.items.len); - 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, text_section.offset + text_section.size); + const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = text_segment.findFreeSpace(needed_size, @sizeOf(u64)); - log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + // TODO Audit this. Is it possible to not find free space? We need to grow __TEXT then. + assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); - try text_segment.sections.append(self.base.allocator, .{ - .sectname = makeStaticString("__got"), + log.debug("found __ziggot section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); + + try text_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__ziggot"), .segname = makeStaticString("__TEXT"), - .addr = text_section.addr + text_section.size, - .size = file_size, + .addr = text_segment.inner.vmaddr + off, + .size = needed_size, .offset = off, - .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0, + .@"align" = @sizeOf(u64), .reloff = 0, .nreloc = 0, - .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .flags = flags, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); - - const added_size = mem.alignForwardGeneric(u64, file_size, self.page_size); - text_segment.inner.vmsize += added_size; - text_segment.inner.filesize += added_size; - text_segment.inner.cmdsize += @sizeOf(macho.section_64); - text_segment.inner.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 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 = text_segment.inner.fileoff + text_segment.inner.filesize; + const address_and_offset = self.nextSegmentAddressAndOffset(); + + log.debug("found __LINKEDIT segment free space at 0x{x}\n", .{address_and_offset.offset}); + try self.load_commands.append(self.base.allocator, .{ .Segment = SegmentCommand.empty(.{ .cmd = macho.LC_SEGMENT_64, .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString("__LINKEDIT"), - .vmaddr = text_segment.inner.vmaddr + text_segment.inner.vmsize, + .vmaddr = address_and_offset.address, .vmsize = 0, - .fileoff = off, + .fileoff = address_and_offset.offset, .filesize = 0, .maxprot = maxprot, .initprot = initprot, @@ -1397,7 +1409,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }), }); - self.linkedit_segment_next_offset = @intCast(u32, off); + self.linkedit_segment_next_offset = @intCast(u32, address_and_offset.offset); self.cmd_table_dirty = true; } if (self.dyld_info_cmd_index == null) { @@ -1631,9 +1643,8 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const expand_text_section = block_placement == null or block_placement.?.next == null; if (expand_text_section) { - const text_capacity = self.allocatedSize(text_section.offset); const needed_size = (vaddr + new_block_size) - text_section.addr; - assert(needed_size <= text_capacity); // TODO must move the entire text section. + assert(needed_size <= text_segment.inner.filesize); // TODO must move the entire text section. self.last_text_block = text_block; text_section.size = needed_size; @@ -1692,95 +1703,28 @@ fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 { return self.makeString(new_name); } -fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { - const hdr_size: u64 = @sizeOf(macho.mach_header_64); - if (start < hdr_size) return hdr_size; - const end = start + satMul(size, alloc_num) / alloc_den; - { - const off = @sizeOf(macho.mach_header_64); - var tight_size: u64 = 0; - for (self.load_commands.items) |cmd| { - tight_size += cmd.cmdsize(); - } - const increased_size = satMul(tight_size, alloc_num) / alloc_den; - const test_end = off + increased_size; - if (end > off and start < test_end) { - return test_end; - } - } - if (self.text_segment_cmd_index) |text_index| { - const text_segment = self.load_commands.items[text_index].Segment; - for (text_segment.sections.items) |section| { - const increased_size = satMul(section.size, alloc_num) / alloc_den; - const test_end = section.offset + increased_size; - if (end > section.offset and start < test_end) { - return test_end; - } - } - } - if (self.dyld_info_cmd_index) |dyld_info_index| { - const dyld_info = self.load_commands.items[dyld_info_index].DyldInfoOnly; - const tight_size = dyld_info.export_size; - const increased_size = satMul(tight_size, alloc_num) / alloc_den; - const test_end = dyld_info.export_off + increased_size; - if (end > dyld_info.export_off and start < test_end) { - return test_end; - } - } - if (self.symtab_cmd_index) |symtab_index| { - const symtab = self.load_commands.items[symtab_index].Symtab; - { - const tight_size = @sizeOf(macho.nlist_64) * symtab.nsyms; - const increased_size = satMul(tight_size, alloc_num) / alloc_den; - const test_end = symtab.symoff + increased_size; - if (end > symtab.symoff and start < test_end) { - return test_end; - } - } - { - const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; - const test_end = symtab.stroff + increased_size; - if (end > symtab.stroff and start < test_end) { - return test_end; - } - } - } - return null; -} +const NextSegmentAddressAndOffset = struct { + address: u64, + offset: u64, +}; -fn allocatedSize(self: *MachO, start: u64) u64 { - if (start == 0) - return 0; - var min_pos: u64 = std.math.maxInt(u64); - { - const off = @sizeOf(macho.mach_header_64); - if (off > start and off < min_pos) min_pos = off; - } - if (self.text_segment_cmd_index) |text_index| { - const text_segment = self.load_commands.items[text_index].Segment; - for (text_segment.sections.items) |section| { - if (section.offset <= start) continue; - if (section.offset < min_pos) min_pos = section.offset; +fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset { + const prev_segment_idx = blk: { + if (self.data_segment_cmd_index) |idx| { + break :blk idx; + } else if (self.text_segment_cmd_index) |idx| { + break :blk idx; + } else { + unreachable; // unhandled LC_SEGMENT_64 load command before __TEXT } - } - if (self.dyld_info_cmd_index) |dyld_info_index| { - const dyld_info = self.load_commands.items[dyld_info_index].DyldInfoOnly; - if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off; - } - if (self.symtab_cmd_index) |symtab_index| { - const symtab = self.load_commands.items[symtab_index].Symtab; - if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff; - if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff; - } - return min_pos - start; -} - -fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { - var start: u64 = 0; - while (self.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForwardGeneric(u64, item_end, min_alignment); - } - return start; + }; + const prev_segment = self.load_commands.items[prev_segment_idx].Segment; + const address = prev_segment.inner.vmaddr + prev_segment.inner.vmsize; + const offset = prev_segment.inner.fileoff + prev_segment.inner.filesize; + return .{ + .address = address, + .offset = offset, + }; } fn writeOffsetTableEntry(self: *MachO, index: usize) !void { @@ -2068,12 +2012,6 @@ fn writeHeader(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(slice[0..1]), 0); } -/// Saturating multiplication -fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { - const T = @TypeOf(a, b); - return std.math.mul(T, a, b) catch std.math.maxInt(T); -} - /// Parse MachO contents from existing binary file. /// TODO This method is incomplete and currently parses only the header /// plus the load commands. @@ -2148,7 +2086,7 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { self.header = header; } -fn parseAndCmpName(name: []const u8, needle: []const u8) bool { +pub fn parseAndCmpName(name: []const u8, needle: []const u8) bool { const len = mem.indexOfScalar(u8, name[0..], @as(u8, 0)) orelse name.len; return mem.eql(u8, name[0..len], needle); } @@ -2204,3 +2142,9 @@ fn parseLazyBindingInfoTable(self: *MachO) !void { var stream = std.io.fixedBufferStream(buffer); try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator); } + +/// Saturating multiplication +pub fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { + const T = @TypeOf(a, b); + return std.math.mul(T, a, b) catch std.math.maxInt(T); +} diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index bc1e5fab5f..8ae767d3e0 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -7,7 +7,12 @@ const macho = std.macho; const testing = std.testing; const Allocator = std.mem.Allocator; -const makeName = @import("../MachO.zig").makeStaticString; +const MachO = @import("../MachO.zig"); +const makeName = MachO.makeStaticString; +const alloc_num = MachO.alloc_num; +const alloc_den = MachO.alloc_den; +const parseAndCmpName = MachO.parseAndCmpName; +const satMul = MachO.satMul; pub const LoadCommand = union(enum) { Segment: SegmentCommand, @@ -155,6 +160,39 @@ pub const SegmentCommand = struct { return .{ .inner = inner }; } + pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void { + try self.sections.append(alloc, section); + self.inner.cmdsize += @sizeOf(macho.section_64); + self.inner.nsects += 1; + } + + fn detectAllocCollision(self: SegmentCommand, start: u64, size: u64) ?u64 { + const end = start + satMul(size, alloc_num) / alloc_den; + for (self.sections.items) |section| { + const increased_size = satMul(section.size, alloc_num) / alloc_den; + const test_end = section.offset + increased_size; + if (end > section.offset and start < test_end) { + return test_end; + } + } + return null; + } + + pub fn findFreeSpace(self: SegmentCommand, object_size: u64, min_alignment: u16) u32 { + var start: u64 = blk: { + if (parseAndCmpName(self.inner.segname[0..], "__TEXT")) { + if (self.sections.items.len > 0) { + break :blk self.sections.items[0].offset; + } + } + break :blk 0; + }; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return @intCast(u32, start); + } + pub fn read(alloc: *Allocator, reader: anytype) !SegmentCommand { const inner = try reader.readStruct(macho.segment_command_64); var segment = SegmentCommand{ From 6a021d845a78241415c1f8537cb8ae63a8367b89 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 18 Dec 2020 11:18:59 +0100 Subject: [PATCH 02/13] macho: find free space even for __text section --- src/link/MachO.zig | 38 ++++++++++++++++++------------------- src/link/MachO/commands.zig | 11 ++--------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 597b957f98..91a1e422fd 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -104,9 +104,7 @@ dyld_stub_binder_index: ?u16 = null, /// Table of symbol names aka the string table. string_table: std.ArrayListUnmanaged(u8) = .{}, -/// Table of symbol vaddr values. The values is the absolute vaddr value. -/// If the vaddr of the executable __TEXT segment vaddr changes, the entire offset -/// table needs to be rewritten. +/// Table of trampolines to the actual symbols in __text section. offset_table: std.ArrayListUnmanaged(u64) = .{}, /// Table of binding info entries. @@ -1307,35 +1305,35 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found __TEXT segment free space 0x{x} to 0x{x}\n", .{ 0, needed_size }); - try self.load_commands.append(self.base.allocator, .{ - .Segment = SegmentCommand.empty(.{ - .cmd = macho.LC_SEGMENT_64, - .cmdsize = @sizeOf(macho.segment_command_64), - .segname = makeStaticString("__TEXT"), - .vmaddr = 0x100000000, // always starts at 4GB - .vmsize = needed_size, - .fileoff = 0, - .filesize = needed_size, - .maxprot = maxprot, - .initprot = initprot, - .nsects = 0, - .flags = 0, - }), + var segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__TEXT"), + .vmaddr = 0x100000000, // always starts at 4GB + .vmsize = needed_size, + .fileoff = 0, + .filesize = needed_size, + .maxprot = maxprot, + .initprot = initprot, + .nsects = 0, + .flags = 0, }); + segment.header_pad = self.header_pad; + try self.load_commands.append(self.base.allocator, .{ .Segment = segment }); self.cmd_table_dirty = true; } if (self.text_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; self.text_section_index = @intCast(u16, text_segment.sections.items.len); - const alignment: u32 = switch (self.base.options.target.cpu.arch) { + const alignment: u2 = switch (self.base.options.target.cpu.arch) { .x86_64 => 0, .aarch64 => 2, else => unreachable, // unhandled architecture type }; const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = self.base.options.program_code_size_hint; - const off = self.header_pad; + const off = text_segment.findFreeSpace(needed_size, @as(u16, 1) << alignment); log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); @@ -2086,7 +2084,7 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { self.header = header; } -pub fn parseAndCmpName(name: []const u8, needle: []const u8) bool { +fn parseAndCmpName(name: []const u8, needle: []const u8) bool { const len = mem.indexOfScalar(u8, name[0..], @as(u8, 0)) orelse name.len; return mem.eql(u8, name[0..len], needle); } diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index 8ae767d3e0..cb7487f3d0 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -11,7 +11,6 @@ const MachO = @import("../MachO.zig"); const makeName = MachO.makeStaticString; const alloc_num = MachO.alloc_num; const alloc_den = MachO.alloc_den; -const parseAndCmpName = MachO.parseAndCmpName; const satMul = MachO.satMul; pub const LoadCommand = union(enum) { @@ -154,6 +153,7 @@ pub const LoadCommand = union(enum) { pub const SegmentCommand = struct { inner: macho.segment_command_64, + header_pad: ?u64 = null, sections: std.ArrayListUnmanaged(macho.section_64) = .{}, pub fn empty(inner: macho.segment_command_64) SegmentCommand { @@ -179,14 +179,7 @@ pub const SegmentCommand = struct { } pub fn findFreeSpace(self: SegmentCommand, object_size: u64, min_alignment: u16) u32 { - var start: u64 = blk: { - if (parseAndCmpName(self.inner.segname[0..], "__TEXT")) { - if (self.sections.items.len > 0) { - break :blk self.sections.items[0].offset; - } - } - break :blk 0; - }; + var start: u64 = if (self.header_pad) |pad| pad else 0; while (self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForwardGeneric(u64, item_end, min_alignment); } From 99578e828b84ba7b308e7bbfac3e99650379edc0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 18 Dec 2020 17:22:12 +0100 Subject: [PATCH 03/13] macho: move findFreeSpace back to MachO struct However, adding a twist where `findFreeSpace` accepts a `SegmentCommand` as argument meaning we want to look for free space specifically within that segment and nowhere else. --- src/link/MachO.zig | 76 +++++++++++++++++++++++-------------- src/link/MachO/commands.zig | 33 ++-------------- 2 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 91a1e422fd..8687e44ec5 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -152,8 +152,8 @@ pub const PieFixup = struct { }; /// `alloc_num / alloc_den` is the factor of padding when allocating. -pub const alloc_num = 4; -pub const alloc_den = 3; +const alloc_num = 4; +const alloc_den = 3; /// Default path to dyld /// TODO instead of hardcoding it, we should probably look through some env vars and search paths @@ -1305,21 +1305,21 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found __TEXT segment free space 0x{x} to 0x{x}\n", .{ 0, needed_size }); - var segment = SegmentCommand.empty(.{ - .cmd = macho.LC_SEGMENT_64, - .cmdsize = @sizeOf(macho.segment_command_64), - .segname = makeStaticString("__TEXT"), - .vmaddr = 0x100000000, // always starts at 4GB - .vmsize = needed_size, - .fileoff = 0, - .filesize = needed_size, - .maxprot = maxprot, - .initprot = initprot, - .nsects = 0, - .flags = 0, + try self.load_commands.append(self.base.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__TEXT"), + .vmaddr = 0x100000000, // always starts at 4GB + .vmsize = needed_size, + .fileoff = 0, + .filesize = needed_size, + .maxprot = maxprot, + .initprot = initprot, + .nsects = 0, + .flags = 0, + }), }); - segment.header_pad = self.header_pad; - try self.load_commands.append(self.base.allocator, .{ .Segment = segment }); self.cmd_table_dirty = true; } if (self.text_section_index == null) { @@ -1333,7 +1333,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { }; const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = self.base.options.program_code_size_hint; - const off = text_segment.findFreeSpace(needed_size, @as(u16, 1) << alignment); + const off = self.findFreeSpace(text_segment, needed_size, @as(u16, 1) << alignment); log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); @@ -1342,7 +1342,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .segname = makeStaticString("__TEXT"), .addr = text_segment.inner.vmaddr + off, .size = @intCast(u32, needed_size), - .offset = off, + .offset = @intCast(u32, off), .@"align" = alignment, .reloff = 0, .nreloc = 0, @@ -1360,10 +1360,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = text_segment.findFreeSpace(needed_size, @sizeOf(u64)); - - // TODO Audit this. Is it possible to not find free space? We need to grow __TEXT then. - assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); + const off = self.findFreeSpace(text_segment, needed_size, @sizeOf(u64)); + assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. log.debug("found __ziggot section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); @@ -1372,7 +1370,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .segname = makeStaticString("__TEXT"), .addr = text_segment.inner.vmaddr + off, .size = needed_size, - .offset = off, + .offset = @intCast(u32, off), .@"align" = @sizeOf(u64), .reloff = 0, .nreloc = 0, @@ -1725,6 +1723,32 @@ fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset { }; } +fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64, size: u64) ?u64 { + const end = start + satMul(size, alloc_num) / alloc_den; + for (segment.sections.items) |section| { + const increased_size = satMul(section.size, alloc_num) / alloc_den; + const test_end = section.offset + increased_size; + if (end > section.offset and start < test_end) { + return test_end; + } + } + return null; +} + +fn findFreeSpace(self: *MachO, segment: *const SegmentCommand, object_size: u64, min_alignment: u16) u64 { + var start: u64 = if (parseAndCmpName(&segment.inner.segname, "__TEXT")) self.header_pad else 0; + while (self.detectAllocCollision(segment, start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; +} + +/// Saturating multiplication +fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { + const T = @TypeOf(a, b); + return std.math.mul(T, a, b) catch std.math.maxInt(T); +} + fn writeOffsetTableEntry(self: *MachO, index: usize) !void { const text_semgent = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const sect = &text_semgent.sections.items[self.got_section_index.?]; @@ -2140,9 +2164,3 @@ fn parseLazyBindingInfoTable(self: *MachO) !void { var stream = std.io.fixedBufferStream(buffer); try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator); } - -/// Saturating multiplication -pub fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { - const T = @TypeOf(a, b); - return std.math.mul(T, a, b) catch std.math.maxInt(T); -} diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index cb7487f3d0..bf63722ece 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -7,11 +7,7 @@ const macho = std.macho; const testing = std.testing; const Allocator = std.mem.Allocator; -const MachO = @import("../MachO.zig"); -const makeName = MachO.makeStaticString; -const alloc_num = MachO.alloc_num; -const alloc_den = MachO.alloc_den; -const satMul = MachO.satMul; +const makeStaticString = @import("../MachO.zig").makeStaticString; pub const LoadCommand = union(enum) { Segment: SegmentCommand, @@ -153,7 +149,6 @@ pub const LoadCommand = union(enum) { pub const SegmentCommand = struct { inner: macho.segment_command_64, - header_pad: ?u64 = null, sections: std.ArrayListUnmanaged(macho.section_64) = .{}, pub fn empty(inner: macho.segment_command_64) SegmentCommand { @@ -166,26 +161,6 @@ pub const SegmentCommand = struct { self.inner.nsects += 1; } - fn detectAllocCollision(self: SegmentCommand, start: u64, size: u64) ?u64 { - const end = start + satMul(size, alloc_num) / alloc_den; - for (self.sections.items) |section| { - const increased_size = satMul(section.size, alloc_num) / alloc_den; - const test_end = section.offset + increased_size; - if (end > section.offset and start < test_end) { - return test_end; - } - } - return null; - } - - pub fn findFreeSpace(self: SegmentCommand, object_size: u64, min_alignment: u16) u32 { - var start: u64 = if (self.header_pad) |pad| pad else 0; - while (self.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForwardGeneric(u64, item_end, min_alignment); - } - return @intCast(u32, start); - } - pub fn read(alloc: *Allocator, reader: anytype) !SegmentCommand { const inner = try reader.readStruct(macho.segment_command_64); var segment = SegmentCommand{ @@ -308,7 +283,7 @@ test "read-write segment command" { .inner = .{ .cmd = macho.LC_SEGMENT_64, .cmdsize = 152, - .segname = makeName("__TEXT"), + .segname = makeStaticString("__TEXT"), .vmaddr = 4294967296, .vmsize = 294912, .fileoff = 0, @@ -320,8 +295,8 @@ test "read-write segment command" { }, }; try cmd.sections.append(gpa, .{ - .sectname = makeName("__text"), - .segname = makeName("__TEXT"), + .sectname = makeStaticString("__text"), + .segname = makeStaticString("__TEXT"), .addr = 4294983680, .size = 448, .offset = 16384, From f32681033d7e57628db13d5d50fc0ff3e1cce20e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 18 Dec 2020 17:47:03 +0100 Subject: [PATCH 04/13] macho: auto-coerce to slice --- src/link/MachO.zig | 14 +++++++------- src/link/MachO/CodeSignature.zig | 6 +++--- src/link/MachO/Trie.zig | 8 ++++---- src/link/MachO/commands.zig | 18 +++++++++--------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8687e44ec5..ea07bed27b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1674,7 +1674,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, pub fn makeStaticString(comptime bytes: []const u8) [16]u8 { var buf = [_]u8{0} ** 16; if (bytes.len > buf.len) @compileError("string too long; max 16 bytes"); - mem.copy(u8, buf[0..], bytes); + mem.copy(u8, &buf, bytes); return buf; } @@ -2048,18 +2048,18 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { switch (cmd.cmd()) { macho.LC_SEGMENT_64 => { const x = cmd.Segment; - if (parseAndCmpName(x.inner.segname[0..], "__PAGEZERO")) { + if (parseAndCmpName(&x.inner.segname, "__PAGEZERO")) { self.pagezero_segment_cmd_index = i; - } else if (parseAndCmpName(x.inner.segname[0..], "__LINKEDIT")) { + } else if (parseAndCmpName(&x.inner.segname, "__LINKEDIT")) { self.linkedit_segment_cmd_index = i; - } else if (parseAndCmpName(x.inner.segname[0..], "__TEXT")) { + } else if (parseAndCmpName(&x.inner.segname, "__TEXT")) { self.text_segment_cmd_index = i; for (x.sections.items) |sect, j| { - if (parseAndCmpName(sect.sectname[0..], "__text")) { + if (parseAndCmpName(§.sectname, "__text")) { self.text_section_index = @intCast(u16, j); } } - } else if (parseAndCmpName(x.inner.segname[0..], "__DATA")) { + } else if (parseAndCmpName(&x.inner.segname, "__DATA")) { self.data_segment_cmd_index = i; } }, @@ -2109,7 +2109,7 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { } fn parseAndCmpName(name: []const u8, needle: []const u8) bool { - const len = mem.indexOfScalar(u8, name[0..], @as(u8, 0)) orelse name.len; + const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; return mem.eql(u8, name[0..len], needle); } diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 442132dac1..0d8f307a4e 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -126,7 +126,7 @@ pub fn calcAdhocSignature( Sha256.hash(buffer[0..fsize], &hash, .{}); - cdir.data.appendSliceAssumeCapacity(hash[0..]); + cdir.data.appendSliceAssumeCapacity(&hash); cdir.inner.nCodeSlots += 1; } @@ -174,10 +174,10 @@ test "CodeSignature header" { defer code_sig.deinit(); var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined; - code_sig.writeHeader(buffer[0..]); + code_sig.writeHeader(&buffer); const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 }; - testing.expect(mem.eql(u8, expected[0..], buffer[0..])); + testing.expect(mem.eql(u8, expected, &buffer)); } pub fn calcCodeSignaturePadding(id: []const u8, file_size: u64) u32 { diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/Trie.zig index e445ea4fef..858b6aad60 100644 --- a/src/link/MachO/Trie.zig +++ b/src/link/MachO/Trie.zig @@ -531,14 +531,14 @@ test "write Trie to a byte stream" { { const nwritten = try trie.write(stream.writer()); testing.expect(nwritten == trie.size); - testing.expect(mem.eql(u8, buffer, exp_buffer[0..])); + testing.expect(mem.eql(u8, buffer, &exp_buffer)); } { // Writing finalized trie again should yield the same result. try stream.seekTo(0); const nwritten = try trie.write(stream.writer()); testing.expect(nwritten == trie.size); - testing.expect(mem.eql(u8, buffer, exp_buffer[0..])); + testing.expect(mem.eql(u8, buffer, &exp_buffer)); } } @@ -556,7 +556,7 @@ test "parse Trie from byte stream" { 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node }; - var in_stream = std.io.fixedBufferStream(in_buffer[0..]); + var in_stream = std.io.fixedBufferStream(&in_buffer); var trie = Trie.init(gpa); defer trie.deinit(); const nread = try trie.read(in_stream.reader()); @@ -571,5 +571,5 @@ test "parse Trie from byte stream" { const nwritten = try trie.write(out_stream.writer()); testing.expect(nwritten == trie.size); - testing.expect(mem.eql(u8, in_buffer[0..], out_buffer)); + testing.expect(mem.eql(u8, &in_buffer, out_buffer)); } diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index bf63722ece..2fa1f867f5 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -26,9 +26,9 @@ pub const LoadCommand = union(enum) { const header = try reader.readStruct(macho.load_command); var buffer = try allocator.alloc(u8, header.cmdsize); defer allocator.free(buffer); - mem.copy(u8, buffer[0..], mem.asBytes(&header)); + mem.copy(u8, buffer, mem.asBytes(&header)); try reader.readNoEof(buffer[@sizeOf(macho.load_command)..]); - var stream = io.fixedBufferStream(buffer[0..]); + var stream = io.fixedBufferStream(buffer); return switch (header.cmd) { macho.LC_SEGMENT_64 => LoadCommand{ @@ -216,7 +216,7 @@ pub fn GenericCommandWithData(comptime Cmd: type) type { const inner = try reader.readStruct(Cmd); var data = try allocator.alloc(u8, inner.cmdsize - @sizeOf(Cmd)); errdefer allocator.free(data); - try reader.readNoEof(data[0..]); + try reader.readNoEof(data); return Self{ .inner = inner, .data = data, @@ -309,10 +309,10 @@ test "read-write segment command" { .reserved3 = 0, }); defer cmd.deinit(gpa); - try testRead(gpa, in_buffer[0..], LoadCommand{ .Segment = cmd }); + try testRead(gpa, in_buffer, LoadCommand{ .Segment = cmd }); var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(out_buffer[0..], LoadCommand{ .Segment = cmd }, in_buffer[0..]); + try testWrite(&out_buffer, LoadCommand{ .Segment = cmd }, in_buffer); } test "read-write generic command with data" { @@ -348,10 +348,10 @@ test "read-write generic command with data" { cmd.data[5] = 0x0; cmd.data[6] = 0x0; cmd.data[7] = 0x0; - try testRead(gpa, in_buffer[0..], LoadCommand{ .Dylib = cmd }); + try testRead(gpa, in_buffer, LoadCommand{ .Dylib = cmd }); var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(out_buffer[0..], LoadCommand{ .Dylib = cmd }, in_buffer[0..]); + try testWrite(&out_buffer, LoadCommand{ .Dylib = cmd }, in_buffer); } test "read-write C struct command" { @@ -368,8 +368,8 @@ test "read-write C struct command" { .entryoff = 16644, .stacksize = 0, }; - try testRead(gpa, in_buffer[0..], LoadCommand{ .Main = cmd }); + try testRead(gpa, in_buffer, LoadCommand{ .Main = cmd }); var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(out_buffer[0..], LoadCommand{ .Main = cmd }, in_buffer[0..]); + try testWrite(&out_buffer, LoadCommand{ .Main = cmd }, in_buffer); } From c89f4781bd4f162e566ec5e9c0b782984fe33657 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 18 Dec 2020 19:41:10 +0100 Subject: [PATCH 05/13] macho: add missing dirty markers --- src/link/MachO.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index ea07bed27b..653811a557 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1537,6 +1537,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .sdk = version, }, }); + self.cmd_table_dirty = true; } if (self.source_version_cmd_index == null) { self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1547,6 +1548,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .version = 0x0, }, }); + self.cmd_table_dirty = true; } if (self.code_signature_cmd_index == null) { self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1558,6 +1560,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .datasize = 0, }, }); + self.cmd_table_dirty = true; } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); From ca1d03fe77b8f173870804580b868568c1d61fbd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 19 Dec 2020 19:44:13 +0100 Subject: [PATCH 06/13] macho: fix findFreeSpace start value --- src/link/MachO.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 653811a557..7bff0e88a1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1739,7 +1739,10 @@ fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64 } fn findFreeSpace(self: *MachO, segment: *const SegmentCommand, object_size: u64, min_alignment: u16) u64 { - var start: u64 = if (parseAndCmpName(&segment.inner.segname, "__TEXT")) self.header_pad else 0; + var start: u64 = if (parseAndCmpName(&segment.inner.segname, "__TEXT")) + self.header_pad + else + segment.inner.fileoff; while (self.detectAllocCollision(segment, start, object_size)) |item_end| { start = mem.alignForwardGeneric(u64, item_end, min_alignment); } From 6712575e044ff56e17d0cf8329d8edcd462453f9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 19 Dec 2020 23:37:21 +0100 Subject: [PATCH 07/13] macho: preallocate space for linkedit hidden sections; --- src/link/MachO.zig | 127 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 7bff0e88a1..6d34b90c93 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1273,6 +1273,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { } header.reserved = 0; self.header = header; + self.cmd_table_dirty = true; } if (self.pagezero_segment_cmd_index == null) { self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1410,6 +1411,14 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.dyld_info_cmd_index == null) { self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + + // TODO Preallocate rebase, binding, and lazy binding info. + const export_size = 2; + const export_off = self.findFreeSpace(&linkedit_segment, export_size, 1); + + log.debug("found export info free space 0x{x} to 0x{x}\n", .{ export_off, export_off + export_size }); + try self.load_commands.append(self.base.allocator, .{ .DyldInfoOnly = .{ .cmd = macho.LC_DYLD_INFO_ONLY, @@ -1430,6 +1439,18 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + + const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64); + const symtab_off = self.findFreeSpace(&linkedit_segment, symtab_size, @sizeOf(macho.nlist_64)); + + log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ symtab_off, symtab_off + symtab_size }); + + const strtab_size = 1; + const strtab_off = self.findFreeSpace(&linkedit_segment, strtab_size, 1); + + log.debug("found string table free space 0x{x} to 0x{x}\n", .{ strtab_off, strtab_off + strtab_size }); + try self.load_commands.append(self.base.allocator, .{ .Symtab = .{ .cmd = macho.LC_SYMTAB, @@ -1444,6 +1465,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.dysymtab_cmd_index == null) { self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); + + // TODO Preallocate space for indirect symbol table. + try self.load_commands.append(self.base.allocator, .{ .Dysymtab = .{ .cmd = macho.LC_DYSYMTAB, @@ -1552,6 +1576,13 @@ 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); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + + const codesig_size = 1; + const codesig_off = self.findFreeSpace(&linkedit_segment, codesig_size, @sizeOf(u64)); + + log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ codesig_off, codesig_off + codesig_size }); + try self.load_commands.append(self.base.allocator, .{ .LinkeditData = .{ .cmd = macho.LC_CODE_SIGNATURE, @@ -1726,15 +1757,101 @@ fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset { }; } +inline fn checkForCollision(start: u64, end: u64, off: u64, size: u64) ?u64 { + const increased_size = satMul(size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + return null; +} + fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64, size: u64) ?u64 { const end = start + satMul(size, alloc_num) / alloc_den; - for (segment.sections.items) |section| { - const increased_size = satMul(section.size, alloc_num) / alloc_den; - const test_end = section.offset + increased_size; - if (end > section.offset and start < test_end) { - return test_end; + + if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) { + // __LINKEDIT is a weird segment where sections get their own load commands so we + // special-case it. + if (self.dyld_info_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const dyld_info = self.load_commands.items[idx].DyldInfoOnly; + if (checkForCollision(start, end, dyld_info.rebase_off, dyld_info.rebase_size)) |pos| { + return pos; + } + // Binding info + if (checkForCollision(start, end, dyld_info.bind_off, dyld_info.bind_size)) |pos| { + return pos; + } + // Weak binding info + if (checkForCollision(start, end, dyld_info.weak_bind_off, dyld_info.weak_bind_size)) |pos| { + return pos; + } + // Lazy binding info + if (checkForCollision(start, end, dyld_info.lazy_bind_off, dyld_info.lazy_bind_size)) |pos| { + return pos; + } + // Export info + if (checkForCollision(start, end, dyld_info.export_off, dyld_info.export_size)) |pos| { + return pos; + } + } + + if (self.function_starts_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const fstart = self.load_commands.items[idx].LinkeditData; + if (checkForCollision(start, end, fstart.dataoff, fstart.datasize)) |pos| { + return pos; + } + } + + if (self.data_in_code_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const dic = self.load_commands.items[idx].LinkeditData; + if (checkForCollision(start, end, dic.dataoff, dic.datasize)) |pos| { + return pos; + } + } + + if (self.dysymtab_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const dysymtab = self.load_commands.items[idx].Dysymtab; + // Indirect symbol table + const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32); + if (checkForCollision(start, end, dysymtab.indirectsymoff, nindirectsize)) |pos| { + return pos; + } + // TODO Handle more dynamic symbol table sections. + } + + if (self.symtab_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const symtab = self.load_commands.items[idx].Symtab; + // Symbol table + const symsize = symtab.nsyms * @sizeOf(macho.nlist_64); + if (checkForCollision(start, end, symtab.symoff, symsize)) |pos| { + return pos; + } + // String table + if (checkForCollision(start, end, symtab.stroff, symtab.strsize)) |pos| { + return pos; + } + } + + if (self.code_signature_cmd_index) |idx| outer: { + if (self.load_commands.items.len == idx) break :outer; + const codesig = self.load_commands.items[idx].LinkeditData; + if (checkForCollision(start, end, codesig.dataoff, codesig.datasize)) |pos| { + return pos; + } + } + } else { + for (segment.sections.items) |section| { + if (checkForCollision(start, end, section.offset, section.size)) |pos| { + return pos; + } } } + return null; } From 3f7dbde92af9dbcc657b0f3dffb68adf90d68989 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 19 Dec 2020 23:51:39 +0100 Subject: [PATCH 08/13] macho: bring back allocatedSize function --- src/link/MachO.zig | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6d34b90c93..10190a5996 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1757,6 +1757,58 @@ fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset { }; } +fn allocatedSize(self: *MachO, segment: *const SegmentCommand, start: u64) u64 { + assert(start > 0); + var min_pos: u64 = std.math.maxInt(u64); + + if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) { + assert(segment.sections.items.len == 0); + // __LINKEDIT is a weird segment where sections get their own load commands so we + // special-case it. + if (self.dyld_info_cmd_index) |idx| { + const dyld_info = self.load_commands.items[idx].DyldInfoOnly; + if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = off; + if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = off; + if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = off; + if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = off; + if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = off; + } + + if (self.function_starts_cmd_index) |idx| { + const fstart = self.load_commands.items[idx].LinkeditData; + if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = off; + } + + if (self.data_in_code_cmd_index) |idx| { + const dic = self.load_commands.items[idx].LinkeditData; + if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = off; + } + + if (self.dysymtab_cmd_index) |idx| { + const dysymtab = self.load_commands.items[idx].Dysymtab; + if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = off; + // TODO Handle more dynamic symbol table sections. + } + + if (self.symtab_cmd_index) |idx| { + const symtab = self.load_commands.items[idx].Symtab; + if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = off; + if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = off; + } + + if (self.code_signature_cmd_index) |idx| { + const codesig = self.load_commands.items[idx].LinkeditData; + if (codesig.dataoff > start and codesig.dataoff < min_pos) min_pos = off; + } + } else { + for (segment.sections.items) |section| { + if (section.offset > start and sections.offset < min_pos) min_pos = off; + } + } + + return min_pos - start; +} + inline fn checkForCollision(start: u64, end: u64, off: u64, size: u64) ?u64 { const increased_size = satMul(size, alloc_num) / alloc_den; const test_end = off + increased_size; @@ -1770,6 +1822,7 @@ fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64 const end = start + satMul(size, alloc_num) / alloc_den; if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) { + assert(segment.sections.items.len == 0); // __LINKEDIT is a weird segment where sections get their own load commands so we // special-case it. if (self.dyld_info_cmd_index) |idx| outer: { From a1b3606f0e53755778e408188a08a98b950e62ac Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 21 Dec 2020 15:13:04 +0100 Subject: [PATCH 09/13] macho: dynamically preallocate space for LINKEDIT sections as well --- src/link/MachO.zig | 293 ++++++++++++++++++++++++++++----------------- 1 file changed, 182 insertions(+), 111 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 10190a5996..730117c43d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -84,9 +84,6 @@ got_section_index: ?u16 = null, /// The absolute address of the entry point. 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) = .{}, @@ -319,10 +316,12 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].Main; main_cmd.entryoff = addr - text_segment.inner.vmaddr; + self.cmd_table_dirty = true; } try self.writeExportTrie(); - try self.writeSymbolTable(); + try self.writeAllGlobalAndUndefSymbols(); try self.writeStringTable(); + try self.updateLinkeditSegmentSizes(); if (target.cpu.arch == .aarch64) { // Preallocate space for the code signature. @@ -870,9 +869,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { return error.NotEnoughPadding; } - const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - // TODO This is clunky. - self.linkedit_segment_next_offset = @intCast(u32, mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, @sizeOf(u64))); // Add code signature load command self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ @@ -1073,6 +1069,8 @@ 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; + + try self.writeLocalSymbol(decl.link.macho.local_sym_index); } else { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); @@ -1088,6 +1086,8 @@ 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.writeLocalSymbol(decl.link.macho.local_sym_index); try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1361,7 +1361,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = self.findFreeSpace(text_segment, needed_size, @sizeOf(u64)); + const off = self.findFreeSpace(text_segment, needed_size, @alignOf(u64)); assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. log.debug("found __ziggot section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); @@ -1406,7 +1406,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }), }); - self.linkedit_segment_next_offset = @intCast(u32, address_and_offset.offset); self.cmd_table_dirty = true; } if (self.dyld_info_cmd_index == null) { @@ -1431,8 +1430,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .weak_bind_size = 0, .lazy_bind_off = 0, .lazy_bind_size = 0, - .export_off = 0, - .export_size = 0, + .export_off = @intCast(u32, export_off), + .export_size = export_size, }, }); self.cmd_table_dirty = true; @@ -1446,7 +1445,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ symtab_off, symtab_off + symtab_size }); - const strtab_size = 1; + try self.string_table.append(self.base.allocator, 0); // Need a null at position 0. + const strtab_size = self.string_table.items.len; const strtab_off = self.findFreeSpace(&linkedit_segment, strtab_size, 1); log.debug("found string table free space 0x{x} to 0x{x}\n", .{ strtab_off, strtab_off + strtab_size }); @@ -1455,13 +1455,14 @@ pub fn populateMissingMetadata(self: *MachO) !void { .Symtab = .{ .cmd = macho.LC_SYMTAB, .cmdsize = @sizeOf(macho.symtab_command), - .symoff = 0, - .nsyms = 0, - .stroff = 0, - .strsize = 0, + .symoff = @intCast(u32, symtab_off), + .nsyms = @intCast(u32, self.base.options.symbol_count_hint), + .stroff = @intCast(u32, strtab_off), + .strsize = @intCast(u32, strtab_size), }, }); self.cmd_table_dirty = true; + try self.writeLocalSymbol(0); } if (self.dysymtab_cmd_index == null) { self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1577,12 +1578,6 @@ 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); const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - - const codesig_size = 1; - const codesig_off = self.findFreeSpace(&linkedit_segment, codesig_size, @sizeOf(u64)); - - log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ codesig_off, codesig_off + codesig_size }); - try self.load_commands.append(self.base.allocator, .{ .LinkeditData = .{ .cmd = macho.LC_CODE_SIGNATURE, @@ -1767,42 +1762,37 @@ fn allocatedSize(self: *MachO, segment: *const SegmentCommand, start: u64) u64 { // special-case it. if (self.dyld_info_cmd_index) |idx| { const dyld_info = self.load_commands.items[idx].DyldInfoOnly; - if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = off; - if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = off; - if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = off; - if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = off; - if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = off; + if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = dyld_info.rebase_off; + if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = dyld_info.bind_off; + if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = dyld_info.weak_bind_off; + if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = dyld_info.lazy_bind_off; + if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off; } if (self.function_starts_cmd_index) |idx| { const fstart = self.load_commands.items[idx].LinkeditData; - if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = off; + if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = fstart.dataoff; } if (self.data_in_code_cmd_index) |idx| { const dic = self.load_commands.items[idx].LinkeditData; - if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = off; + if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = dic.dataoff; } if (self.dysymtab_cmd_index) |idx| { const dysymtab = self.load_commands.items[idx].Dysymtab; - if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = off; + if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = dysymtab.indirectsymoff; // TODO Handle more dynamic symbol table sections. } if (self.symtab_cmd_index) |idx| { const symtab = self.load_commands.items[idx].Symtab; - if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = off; - if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = off; - } - - if (self.code_signature_cmd_index) |idx| { - const codesig = self.load_commands.items[idx].LinkeditData; - if (codesig.dataoff > start and codesig.dataoff < min_pos) min_pos = off; + if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff; + if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff; } } else { for (segment.sections.items) |section| { - if (section.offset > start and sections.offset < min_pos) min_pos = off; + if (section.offset > start and section.offset < min_pos) min_pos = section.offset; } } @@ -1889,14 +1879,6 @@ fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64 return pos; } } - - if (self.code_signature_cmd_index) |idx| outer: { - if (self.load_commands.items.len == idx) break :outer; - const codesig = self.load_commands.items[idx].LinkeditData; - if (checkForCollision(start, end, codesig.dataoff, codesig.datasize)) |pos| { - return pos; - } - } } else { for (segment.sections.items) |section| { if (checkForCollision(start, end, section.offset, section.size)) |pos| { @@ -1958,14 +1940,79 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&code, off); } -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; +fn writeLocalSymbol(self: *MachO, index: usize) !void { + const tracy = trace(@src()); + defer tracy.end(); - const locals_off = self.linkedit_segment_next_offset.?; + 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 nundefs = self.undef_symbols.items.len; + const nsyms = nlocals + nglobals + nundefs; + + if (symtab.nsyms < nsyms) { + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const needed_size = nsyms * @sizeOf(macho.nlist_64); + if (needed_size > self.allocatedSize(&linkedit_segment, symtab.symoff)) { + // Move the entire symbol table to a new location + const new_symoff = self.findFreeSpace(&linkedit_segment, needed_size, @alignOf(macho.nlist_64)); + const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64); + + log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ + symtab.symoff, + symtab.symoff + existing_size, + new_symoff, + new_symoff + existing_size, + }); + + const amt = try self.base.file.?.copyRangeAll(symtab.symoff, self.base.file.?, new_symoff, existing_size); + if (amt != existing_size) return error.InputOutput; + symtab.symoff = @intCast(u32, new_symoff); + } + symtab.nsyms = @intCast(u32, nsyms); + self.cmd_table_dirty = true; + } + + const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; + log.debug("writing local symbol {} at 0x{x}", .{ index, off }); + try self.base.file.?.pwriteAll(mem.asBytes(&self.local_symbols.items[index]), off); +} + +fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + 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 nundefs = self.undef_symbols.items.len; + const nsyms = nlocals + nglobals + nundefs; + + if (symtab.nsyms < nsyms) { + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const needed_size = nsyms * @sizeOf(macho.nlist_64); + if (needed_size > self.allocatedSize(&linkedit_segment, symtab.symoff)) { + // Move the entire symbol table to a new location + const new_symoff = self.findFreeSpace(&linkedit_segment, needed_size, @alignOf(macho.nlist_64)); + const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64); + + log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ + symtab.symoff, + symtab.symoff + existing_size, + new_symoff, + new_symoff + existing_size, + }); + + const amt = try self.base.file.?.copyRangeAll(symtab.symoff, self.base.file.?, new_symoff, existing_size); + if (amt != existing_size) return error.InputOutput; + symtab.symoff = @intCast(u32, new_symoff); + } + symtab.nsyms = @intCast(u32, nsyms); + self.cmd_table_dirty = true; + } + + const locals_off = symtab.symoff; 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); @@ -1977,44 +2024,31 @@ fn writeSymbolTable(self: *MachO) !void { 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.inner.filesize += symtab.nsyms * @sizeOf(macho.nlist_64); - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); - } + dysymtab.nlocalsym = @intCast(u32, nlocals); + dysymtab.iextdefsym = @intCast(u32, nlocals); + dysymtab.nextdefsym = @intCast(u32, nglobals); + dysymtab.iundefsym = @intCast(u32, nlocals + nglobals); + dysymtab.nundefsym = @intCast(u32, nundefs); self.cmd_table_dirty = true; } fn writeCodeSignaturePadding(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; - const fileoff = self.linkedit_segment_next_offset.?; + const fileoff = linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize; const datasize = CodeSignature.calcCodeSignaturePadding(self.base.options.emit.?.sub_path, fileoff); - code_sig_cmd.dataoff = fileoff; + code_sig_cmd.dataoff = @intCast(u32, 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.inner.filesize += datasize; - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); + linkedit_segment.inner.filesize += datasize; + if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) { + linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.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 @@ -2023,6 +2057,9 @@ fn writeCodeSignaturePadding(self: *MachO) !void { } fn writeCodeSignature(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; @@ -2048,6 +2085,9 @@ fn writeCodeSignature(self: *MachO) !void { fn writeExportTrie(self: *MachO) !void { if (self.global_symbols.items.len == 0) return; + const tracy = trace(@src()); + defer tracy.end(); + var trie = Trie.init(self.base.allocator); defer trie.deinit(); @@ -2070,26 +2110,19 @@ fn writeExportTrie(self: *MachO) !void { const nwritten = try trie.write(stream.writer()); assert(nwritten == trie.size); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - const export_size = @intCast(u32, mem.alignForward(buffer.len, @sizeOf(u64))); - dyld_info.export_off = self.linkedit_segment_next_offset.?; - dyld_info.export_size = export_size; + const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.export_off); + const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); - 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.len) { - // Pad out to align(8). - try self.base.file.?.pwriteAll(&[_]u8{0}, dyld_info.export_off + export_size); + if (needed_size > allocated_size) { + dyld_info.export_off = 0; + dyld_info.export_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1)); } + dyld_info.export_size = @intCast(u32, needed_size); + log.debug("writing export trie from 0x{x} to 0x{x}\n", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + try self.base.file.?.pwriteAll(buffer, 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.inner.filesize += dyld_info.export_size; - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); - } self.cmd_table_dirty = true; } @@ -2156,30 +2189,68 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { } fn writeStringTable(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const needed_size = self.string_table.items.len; - - symtab.stroff = self.linkedit_segment_next_offset.?; - symtab.strsize = @intCast(u32, mem.alignForward(needed_size, @sizeOf(u64))); + const allocated_size = self.allocatedSize(&linkedit_segment, symtab.stroff); + const needed_size = mem.alignForwardGeneric(u64, self.string_table.items.len, @alignOf(u64)); + if (needed_size > allocated_size) { + symtab.strsize = 0; + symtab.stroff = @intCast(u32, self.findFreeSpace(&linkedit_segment, 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 (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.inner.filesize += symtab.strsize; - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); - } self.cmd_table_dirty = true; } +fn updateLinkeditSegmentSizes(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // Now, we are in position to update __LINKEDIT segment sizes. + // TODO Add checkpointing so that we don't have to do this every single time. + const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + var final_offset = linkedit_segment.inner.fileoff; + + if (self.dyld_info_cmd_index) |idx| { + const dyld_info = self.load_commands.items[idx].DyldInfoOnly; + final_offset = std.math.max(final_offset, dyld_info.rebase_off + dyld_info.rebase_size); + final_offset = std.math.max(final_offset, dyld_info.bind_off + dyld_info.bind_size); + final_offset = std.math.max(final_offset, dyld_info.weak_bind_off + dyld_info.weak_bind_size); + final_offset = std.math.max(final_offset, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size); + final_offset = std.math.max(final_offset, dyld_info.export_off + dyld_info.export_size); + } + if (self.function_starts_cmd_index) |idx| { + const fstart = self.load_commands.items[idx].LinkeditData; + final_offset = std.math.max(final_offset, fstart.dataoff + fstart.datasize); + } + if (self.data_in_code_cmd_index) |idx| { + const dic = self.load_commands.items[idx].LinkeditData; + final_offset = std.math.max(final_offset, dic.dataoff + dic.datasize); + } + if (self.dysymtab_cmd_index) |idx| { + const dysymtab = self.load_commands.items[idx].Dysymtab; + const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32); + final_offset = std.math.max(final_offset, dysymtab.indirectsymoff + nindirectsize); + // TODO Handle more dynamic symbol table sections. + } + if (self.symtab_cmd_index) |idx| { + const symtab = self.load_commands.items[idx].Symtab; + const symsize = symtab.nsyms * @sizeOf(macho.nlist_64); + final_offset = std.math.max(final_offset, symtab.symoff + symsize); + final_offset = std.math.max(final_offset, symtab.stroff + symtab.strsize); + } + + const filesize = final_offset - linkedit_segment.inner.fileoff; + linkedit_segment.inner.filesize = filesize; + linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, filesize, self.page_size); +} + /// Writes all load commands and section headers. fn writeLoadCommands(self: *MachO) !void { var sizeofcmds: usize = 0; From de5421a0a666000908527503af8f511b9b3095ed Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 21 Dec 2020 18:31:26 +0100 Subject: [PATCH 10/13] macho: deduplicate symbol table relocation codepath --- src/link/MachO.zig | 42 +++++++++++------------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 730117c43d..455bfcc922 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1940,10 +1940,7 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&code, off); } -fn writeLocalSymbol(self: *MachO, index: usize) !void { - const tracy = trace(@src()); - defer tracy.end(); - +fn relocateSymbolTable(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; @@ -1972,7 +1969,13 @@ fn writeLocalSymbol(self: *MachO, index: usize) !void { symtab.nsyms = @intCast(u32, nsyms); self.cmd_table_dirty = true; } +} +fn writeLocalSymbol(self: *MachO, index: usize) !void { + const tracy = trace(@src()); + defer tracy.end(); + try self.relocateSymbolTable(); + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing local symbol {} at 0x{x}", .{ index, off }); try self.base.file.?.pwriteAll(mem.asBytes(&self.local_symbols.items[index]), off); @@ -1982,45 +1985,22 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); + try self.relocateSymbolTable(); 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 nundefs = self.undef_symbols.items.len; - const nsyms = nlocals + nglobals + nundefs; - - if (symtab.nsyms < nsyms) { - const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const needed_size = nsyms * @sizeOf(macho.nlist_64); - if (needed_size > self.allocatedSize(&linkedit_segment, symtab.symoff)) { - // Move the entire symbol table to a new location - const new_symoff = self.findFreeSpace(&linkedit_segment, needed_size, @alignOf(macho.nlist_64)); - const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64); - - log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ - symtab.symoff, - symtab.symoff + existing_size, - new_symoff, - new_symoff + existing_size, - }); - - const amt = try self.base.file.?.copyRangeAll(symtab.symoff, self.base.file.?, new_symoff, existing_size); - if (amt != existing_size) return error.InputOutput; - symtab.symoff = @intCast(u32, new_symoff); - } - symtab.nsyms = @intCast(u32, nsyms); - self.cmd_table_dirty = true; - } const locals_off = symtab.symoff; - const locals_size = self.local_symbols.items.len * @sizeOf(macho.nlist_64); + const locals_size = nlocals * @sizeOf(macho.nlist_64); const globals_off = locals_off + locals_size; - const globals_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); + const globals_size = nglobals * @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); + const undefs_size = nundefs * @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); From 3f21f9155fe8e10e3990ee5738b190accbdffd3a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 21 Dec 2020 19:59:40 +0100 Subject: [PATCH 11/13] macho: write only bits that changed Refactor use of `log` to not include an additional newline char. --- src/link/MachO.zig | 200 ++++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 76 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 455bfcc922..a712f31608 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -111,7 +111,11 @@ lazy_binding_info_table: LazyBindingInfoTable = .{}, error_flags: File.ErrorFlags = File.ErrorFlags{}, -cmd_table_dirty: bool = false, +offset_table_count_dirty: bool = false, +header_dirty: bool = false, +load_commands_dirty: bool = false, +export_info_dirty: bool = false, +string_table_dirty: bool = false, /// A list of text blocks that have surplus capacity. This list can have false /// positives, as functions grow and shrink over time, only sometimes being added @@ -316,7 +320,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].Main; main_cmd.entryoff = addr - text_segment.inner.vmaddr; - self.cmd_table_dirty = true; + self.load_commands_dirty = true; } try self.writeExportTrie(); try self.writeAllGlobalAndUndefSymbols(); @@ -336,21 +340,22 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { .Lib => return error.TODOImplementWritingLibFiles, } - if (self.cmd_table_dirty) { - try self.writeLoadCommands(); - try self.writeHeader(); - self.cmd_table_dirty = false; - } + try self.writeLoadCommands(); + try self.writeHeader(); if (self.entry_addr == null and self.base.options.output_mode == .Exe) { - log.debug("flushing. no_entry_point_found = true\n", .{}); + log.debug("flushing. no_entry_point_found = true", .{}); self.error_flags.no_entry_point_found = true; } else { - log.debug("flushing. no_entry_point_found = false\n", .{}); + log.debug("flushing. no_entry_point_found = false", .{}); self.error_flags.no_entry_point_found = false; } - assert(!self.cmd_table_dirty); + assert(!self.offset_table_count_dirty); + assert(!self.header_dirty); + assert(!self.load_commands_dirty); + assert(!self.export_info_dirty); + assert(!self.string_table_dirty); if (target.cpu.arch == .aarch64) { switch (output_mode) { @@ -769,9 +774,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { const needed_size = @sizeOf(macho.linkedit_data_command) * alloc_num / alloc_den; if (needed_size + after_last_cmd_offset > text_section.offset) { - std.log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - std.log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - std.log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); + log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); + log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); + log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); return error.NotEnoughPadding; } @@ -807,10 +812,12 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { mem.set(u8, dylib_cmd.data, 0); mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); + self.header_dirty = true; + self.load_commands_dirty = true; if (self.symtab_cmd_index == null or self.dysymtab_cmd_index == null) { - std.log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{}); - std.log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{}); + log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{}); + log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{}); return error.NoSymbolTableFound; } @@ -863,9 +870,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { const needed_size = @sizeOf(macho.linkedit_data_command) * alloc_num / alloc_den; if (needed_size + after_last_cmd_offset > text_section.offset) { - std.log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - std.log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - std.log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); + log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); + log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); + log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); return error.NotEnoughPadding; } @@ -879,6 +886,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { .datasize = 0, }, }); + self.header_dirty = true; + self.load_commands_dirty = true; // Pad out space for code signature try self.writeCodeSignaturePadding(); @@ -1000,10 +1009,10 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); if (self.local_symbol_free_list.popOrNull()) |i| { - log.debug("reusing symbol index {} for {}\n", .{ i, decl.name }); + log.debug("reusing symbol index {} for {}", .{ i, decl.name }); decl.link.macho.local_sym_index = i; } else { - log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); + log.debug("allocating symbol index {} for {}", .{ self.local_symbols.items.len, decl.name }); decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1013,6 +1022,7 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { } else { decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); _ = self.offset_table.addOneAssumeCapacity(); + self.offset_table_count_dirty = true; } self.local_symbols.items[decl.link.macho.local_sym_index] = .{ @@ -1054,10 +1064,10 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment); - log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, symbol.n_value, vaddr }); + log.debug("growing {} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); if (vaddr != symbol.n_value) { symbol.n_value = vaddr; - log.debug(" (writing new offset table entry)\n", .{}); + log.debug(" (writing new offset table entry)", .{}); self.offset_table.items[decl.link.macho.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1075,7 +1085,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment); - log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, addr }); + log.debug("allocated text block for {} at 0x{x}", .{ decl_name, addr }); errdefer self.freeTextBlock(&decl.link.macho); symbol.* = .{ @@ -1153,7 +1163,6 @@ pub fn updateDeclExports( .Strong => blk: { if (mem.eql(u8, exp.options.name, "_start")) { self.entry_addr = decl_sym.n_value; - self.cmd_table_dirty = true; // TODO This should be handled more granularly instead of invalidating all commands. } break :blk macho.REFERENCE_FLAG_DEFINED; }, @@ -1181,6 +1190,7 @@ pub fn updateDeclExports( const name_str_index = try self.makeString(exp.options.name); const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { _ = self.global_symbols.addOneAssumeCapacity(); + self.export_info_dirty = true; break :blk self.global_symbols.items.len - 1; }; self.global_symbols.items[i] = .{ @@ -1273,7 +1283,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { } header.reserved = 0; self.header = header; - self.cmd_table_dirty = true; + self.header_dirty = true; } if (self.pagezero_segment_cmd_index == null) { self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1292,7 +1302,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }), }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.text_segment_cmd_index == null) { self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1304,7 +1315,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const ideal_size = self.header_pad + program_code_size_hint + offset_table_size_hint; const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); - log.debug("found __TEXT segment free space 0x{x} to 0x{x}\n", .{ 0, needed_size }); + log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size }); try self.load_commands.append(self.base.allocator, .{ .Segment = SegmentCommand.empty(.{ @@ -1321,7 +1332,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }), }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.text_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; @@ -1336,7 +1348,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const needed_size = self.base.options.program_code_size_hint; const off = self.findFreeSpace(text_segment, needed_size, @as(u16, 1) << alignment); - log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); + log.debug("found __text section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); try text_segment.addSection(self.base.allocator, .{ .sectname = makeStaticString("__text"), @@ -1352,7 +1364,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved2 = 0, .reserved3 = 0, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.got_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; @@ -1364,7 +1377,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const off = self.findFreeSpace(text_segment, needed_size, @alignOf(u64)); assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. - log.debug("found __ziggot section free space 0x{x} to 0x{x}\n", .{ off, off + needed_size }); + log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); try text_segment.addSection(self.base.allocator, .{ .sectname = makeStaticString("__ziggot"), @@ -1380,7 +1393,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved2 = 0, .reserved3 = 0, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1389,7 +1403,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const initprot = macho.VM_PROT_READ; const address_and_offset = self.nextSegmentAddressAndOffset(); - log.debug("found __LINKEDIT segment free space at 0x{x}\n", .{address_and_offset.offset}); + log.debug("found __LINKEDIT segment free space at 0x{x}", .{address_and_offset.offset}); try self.load_commands.append(self.base.allocator, .{ .Segment = SegmentCommand.empty(.{ @@ -1406,7 +1420,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .flags = 0, }), }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.dyld_info_cmd_index == null) { self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1416,7 +1431,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const export_size = 2; const export_off = self.findFreeSpace(&linkedit_segment, export_size, 1); - log.debug("found export info free space 0x{x} to 0x{x}\n", .{ export_off, export_off + export_size }); + log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + export_size }); try self.load_commands.append(self.base.allocator, .{ .DyldInfoOnly = .{ @@ -1434,7 +1449,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .export_size = export_size, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1443,13 +1459,13 @@ pub fn populateMissingMetadata(self: *MachO) !void { const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64); const symtab_off = self.findFreeSpace(&linkedit_segment, symtab_size, @sizeOf(macho.nlist_64)); - log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ symtab_off, symtab_off + symtab_size }); + log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size }); try self.string_table.append(self.base.allocator, 0); // Need a null at position 0. const strtab_size = self.string_table.items.len; const strtab_off = self.findFreeSpace(&linkedit_segment, strtab_size, 1); - log.debug("found string table free space 0x{x} to 0x{x}\n", .{ strtab_off, strtab_off + strtab_size }); + log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size }); try self.load_commands.append(self.base.allocator, .{ .Symtab = .{ @@ -1461,8 +1477,10 @@ pub fn populateMissingMetadata(self: *MachO) !void { .strsize = @intCast(u32, strtab_size), }, }); - self.cmd_table_dirty = true; try self.writeLocalSymbol(0); + self.header_dirty = true; + self.load_commands_dirty = true; + self.string_table_dirty = true; } if (self.dysymtab_cmd_index == null) { self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1493,7 +1511,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .nlocrel = 0, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.dylinker_cmd_index == null) { self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1507,7 +1526,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { mem.set(u8, dylinker_cmd.data, 0); mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH)); try self.load_commands.append(self.base.allocator, .{ .Dylinker = dylinker_cmd }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.libsystem_cmd_index == null) { self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1529,7 +1549,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { mem.set(u8, dylib_cmd.data, 0); mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.main_cmd_index == null) { self.main_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1541,7 +1562,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .stacksize = 0, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.version_min_cmd_index == null) { self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1562,7 +1584,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .sdk = version, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.source_version_cmd_index == null) { self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1573,7 +1596,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .version = 0x0, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.code_signature_cmd_index == null) { self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1586,7 +1610,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .datasize = 0, }, }); - self.cmd_table_dirty = true; + self.header_dirty = true; + self.load_commands_dirty = true; } if (self.dyld_stub_binder_index == null) { self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); @@ -1674,7 +1699,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, self.last_text_block = text_block; text_section.size = needed_size; - self.cmd_table_dirty = true; // TODO Make more granular. + self.load_commands_dirty = true; // TODO Make more granular. } text_block.size = new_block_size; @@ -1712,6 +1737,7 @@ fn makeString(self: *MachO, bytes: []const u8) !u32 { const result = self.string_table.items.len; self.string_table.appendSliceAssumeCapacity(bytes); self.string_table.appendAssumeCapacity(0); + self.string_table_dirty = true; return @intCast(u32, result); } @@ -1908,11 +1934,16 @@ fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { } fn writeOffsetTableEntry(self: *MachO, index: usize) !void { - const text_semgent = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const sect = &text_semgent.sections.items[self.got_section_index.?]; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const sect = &text_segment.sections.items[self.got_section_index.?]; const off = sect.offset + @sizeOf(u64) * index; const vmaddr = sect.addr + @sizeOf(u64) * index; + if (self.offset_table_count_dirty) { + // TODO relocate. + self.offset_table_count_dirty = false; + } + var code: [8]u8 = undefined; switch (self.base.options.target.cpu.arch) { .x86_64 => { @@ -1936,7 +1967,7 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { }, else => unreachable, // unsupported target architecture } - log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); + log.debug("writing offset table entry 0x{x} at 0x{x}", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off); } @@ -1967,7 +1998,7 @@ fn relocateSymbolTable(self: *MachO) !void { symtab.symoff = @intCast(u32, new_symoff); } symtab.nsyms = @intCast(u32, nsyms); - self.cmd_table_dirty = true; + self.load_commands_dirty = true; } } @@ -1996,12 +2027,12 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const globals_off = locals_off + locals_size; const globals_size = nglobals * @sizeOf(macho.nlist_64); - log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ globals_off, globals_size + globals_off }); + log.debug("writing global symbols from 0x{x} to 0x{x}", .{ 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 = nundefs * @sizeOf(macho.nlist_64); - log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ undefs_off, undefs_size + undefs_off }); + log.debug("writing undef symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off); // Update dynamic symbol table. @@ -2011,7 +2042,7 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { dysymtab.nextdefsym = @intCast(u32, nglobals); dysymtab.iundefsym = @intCast(u32, nlocals + nglobals); dysymtab.nundefsym = @intCast(u32, nundefs); - self.cmd_table_dirty = true; + self.load_commands_dirty = true; } fn writeCodeSignaturePadding(self: *MachO) !void { @@ -2021,19 +2052,23 @@ fn writeCodeSignaturePadding(self: *MachO) !void { const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; const fileoff = linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize; - const datasize = CodeSignature.calcCodeSignaturePadding(self.base.options.emit.?.sub_path, fileoff); - code_sig_cmd.dataoff = @intCast(u32, fileoff); - code_sig_cmd.datasize = datasize; + const needed_size = CodeSignature.calcCodeSignaturePadding(self.base.options.emit.?.sub_path, fileoff); - // Advance size of __LINKEDIT segment - linkedit_segment.inner.filesize += datasize; - if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) { - linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.filesize, self.page_size); + if (code_sig_cmd.datasize < needed_size) { + code_sig_cmd.dataoff = @intCast(u32, fileoff); + code_sig_cmd.datasize = needed_size; + + // Advance size of __LINKEDIT segment + linkedit_segment.inner.filesize += needed_size; + if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) { + linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.filesize, self.page_size); + } + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + // 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}, fileoff + needed_size - 1); + self.load_commands_dirty = true; } - 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}, fileoff + datasize - 1); } fn writeCodeSignature(self: *MachO) !void { @@ -2057,12 +2092,13 @@ fn writeCodeSignature(self: *MachO) !void { defer self.base.allocator.free(buffer); 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 }); + log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } fn writeExportTrie(self: *MachO) !void { + if (!self.export_info_dirty) return; if (self.global_symbols.items.len == 0) return; const tracy = trace(@src()); @@ -2100,10 +2136,11 @@ fn writeExportTrie(self: *MachO) !void { dyld_info.export_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1)); } dyld_info.export_size = @intCast(u32, needed_size); - log.debug("writing export trie from 0x{x} to 0x{x}\n", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.debug("writing export trie from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.base.file.?.pwriteAll(buffer, dyld_info.export_off); - self.cmd_table_dirty = true; + self.load_commands_dirty = true; + self.export_info_dirty = false; } fn writeBindingInfoTable(self: *MachO) !void { @@ -2119,7 +2156,7 @@ fn writeBindingInfoTable(self: *MachO) !void { dyld_info.bind_off = self.linkedit_segment_next_offset.?; dyld_info.bind_size = bind_size; - log.debug("writing binding info table from 0x{x} to 0x{x}\n", .{ dyld_info.bind_off, dyld_info.bind_off + bind_size }); + log.debug("writing binding info table from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + bind_size }); if (bind_size > buffer.len) { // Pad out to align(8). @@ -2150,7 +2187,7 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { dyld_info.lazy_bind_off = self.linkedit_segment_next_offset.?; dyld_info.lazy_bind_size = bind_size; - log.debug("writing lazy binding info table from 0x{x} to 0x{x}\n", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + bind_size }); + log.debug("writing lazy binding info table from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + bind_size }); if (bind_size > buffer.len) { // Pad out to align(8). @@ -2169,6 +2206,8 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { } fn writeStringTable(self: *MachO) !void { + if (!self.string_table_dirty) return; + const tracy = trace(@src()); defer tracy.end(); @@ -2182,13 +2221,16 @@ fn writeStringTable(self: *MachO) !void { symtab.stroff = @intCast(u32, self.findFreeSpace(&linkedit_segment, 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 }); + log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); - self.cmd_table_dirty = true; + self.load_commands_dirty = true; + self.string_table_dirty = false; } fn updateLinkeditSegmentSizes(self: *MachO) !void { + if (!self.load_commands_dirty) return; + const tracy = trace(@src()); defer tracy.end(); @@ -2229,10 +2271,13 @@ fn updateLinkeditSegmentSizes(self: *MachO) !void { const filesize = final_offset - linkedit_segment.inner.fileoff; linkedit_segment.inner.filesize = filesize; linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, filesize, self.page_size); + self.load_commands_dirty = true; } /// Writes all load commands and section headers. fn writeLoadCommands(self: *MachO) !void { + if (!self.load_commands_dirty) return; + var sizeofcmds: usize = 0; for (self.load_commands.items) |lc| { sizeofcmds += lc.cmdsize(); @@ -2246,19 +2291,22 @@ fn writeLoadCommands(self: *MachO) !void { } try self.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); + self.load_commands_dirty = false; } /// Writes Mach-O file header. fn writeHeader(self: *MachO) !void { + if (!self.header_dirty) return; + self.header.?.ncmds = @intCast(u32, self.load_commands.items.len); var sizeofcmds: u32 = 0; for (self.load_commands.items) |cmd| { sizeofcmds += cmd.cmdsize(); } self.header.?.sizeofcmds = sizeofcmds; - log.debug("writing Mach-O header {}\n", .{self.header.?}); - const slice = [1]macho.mach_header_64{self.header.?}; - try self.base.file.?.pwriteAll(mem.sliceAsBytes(slice[0..1]), 0); + log.debug("writing Mach-O header {}", .{self.header.?}); + try self.base.file.?.pwriteAll(mem.asBytes(&self.header.?), 0); + self.header_dirty = false; } /// Parse MachO contents from existing binary file. @@ -2327,7 +2375,7 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { self.code_signature_cmd_index = i; }, else => { - std.log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); + log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); }, } self.load_commands.appendAssumeCapacity(cmd); From f106a46fd293605fab7cb776d36bb22cc482e692 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 21 Dec 2020 23:15:55 +0100 Subject: [PATCH 12/13] macho: enable binding and lazy binding info writes --- src/link/MachO.zig | 85 +++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a712f31608..b51f094648 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -114,6 +114,8 @@ error_flags: File.ErrorFlags = File.ErrorFlags{}, offset_table_count_dirty: bool = false, header_dirty: bool = false, load_commands_dirty: bool = false, +binding_info_dirty: bool = false, +lazy_binding_info_dirty: bool = false, export_info_dirty: bool = false, string_table_dirty: bool = false, @@ -322,6 +324,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { main_cmd.entryoff = addr - text_segment.inner.vmaddr; self.load_commands_dirty = true; } + try self.writeBindingInfoTable(); + try self.writeLazyBindingInfoTable(); try self.writeExportTrie(); try self.writeAllGlobalAndUndefSymbols(); try self.writeStringTable(); @@ -354,6 +358,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { assert(!self.offset_table_count_dirty); assert(!self.header_dirty); assert(!self.load_commands_dirty); + assert(!self.binding_info_dirty); + assert(!self.lazy_binding_info_dirty); assert(!self.export_info_dirty); assert(!self.string_table_dirty); @@ -831,7 +837,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { symbol.dylib_ordinal = next_ordinal; } - // Write update dyld info + // Write updated dyld info. const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; { const size = try self.binding_info_table.calcSize(); @@ -861,6 +867,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // Write updated load commands and the header try self.writeLoadCommands(); try self.writeHeader(); + + assert(!self.header_dirty); + assert(!self.load_commands_dirty); } if (self.code_signature_cmd_index == null) outer: { if (target.cpu.arch != .aarch64) break :outer; // This is currently needed only for aarch64 targets. @@ -896,6 +905,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try self.writeHeader(); // Generate adhoc code signature try self.writeCodeSignature(); + + assert(!self.header_dirty); + assert(!self.load_commands_dirty); } } } @@ -2136,7 +2148,7 @@ fn writeExportTrie(self: *MachO) !void { dyld_info.export_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1)); } dyld_info.export_size = @intCast(u32, needed_size); - log.debug("writing export trie from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.base.file.?.pwriteAll(buffer, dyld_info.export_off); self.load_commands_dirty = true; @@ -2144,65 +2156,62 @@ fn writeExportTrie(self: *MachO) !void { } fn writeBindingInfoTable(self: *MachO) !void { - const size = self.binding_info_table.calcSize(); + if (!self.binding_info_dirty) return; + + const tracy = trace(@src()); + defer tracy.end(); + + const size = try self.binding_info_table.calcSize(); var buffer = try self.base.allocator.alloc(u8, size); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); try self.binding_info_table.write(stream.writer()); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - const bind_size = @intCast(u32, mem.alignForward(buffer.len, @sizeOf(u64))); - dyld_info.bind_off = self.linkedit_segment_next_offset.?; - dyld_info.bind_size = bind_size; + const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.bind_off); + const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); - log.debug("writing binding info table from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + bind_size }); - - if (bind_size > buffer.len) { - // Pad out to align(8). - try self.base.file.?.pwriteAll(&[_]u8{0}, dyld_info.bind_off + bind_size); + if (needed_size > allocated_size) { + dyld_info.bind_off = 0; + dyld_info.bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1)); } + + dyld_info.bind_size = @intCast(u32, needed_size); + log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); - - self.linkedit_segment_next_offset = dyld_info.bind_off + dyld_info.bind_size; - // Advance size of __LINKEDIT segment - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - linkedit.inner.filesize += dyld_info.bind_size; - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); - } - self.cmd_table_dirty = true; + self.load_commands_dirty = true; + self.binding_info_dirty = false; } fn writeLazyBindingInfoTable(self: *MachO) !void { - const size = self.lazy_binding_info_table.calcSize(); + if (!self.lazy_binding_info_dirty) return; + + const size = try self.lazy_binding_info_table.calcSize(); var buffer = try self.base.allocator.alloc(u8, size); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); try self.lazy_binding_info_table.write(stream.writer()); + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - const bind_size = @intCast(u32, mem.alignForward(buffer.len, @sizeOf(u64))); - dyld_info.lazy_bind_off = self.linkedit_segment_next_offset.?; - dyld_info.lazy_bind_size = bind_size; + const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.lazy_bind_off); + const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); - log.debug("writing lazy binding info table from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + bind_size }); - - if (bind_size > buffer.len) { - // Pad out to align(8). - try self.base.file.?.pwriteAll(&[_]u8{0}, dyld_info.lazy_bind_off + bind_size); + if (needed_size > allocated_size) { + dyld_info.lazy_bind_off = 0; + dyld_info.lazy_bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1)); } + + dyld_info.lazy_bind_size = @intCast(u32, needed_size); + log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); - - self.linkedit_segment_next_offset = dyld_info.lazy_bind_off + dyld_info.lazy_bind_size; - // Advance size of __LINKEDIT segment - const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - linkedit.inner.filesize += dyld_info.lazy_bind_size; - if (linkedit.inner.vmsize < linkedit.inner.filesize) { - linkedit.inner.vmsize = mem.alignForwardGeneric(u64, linkedit.inner.filesize, self.page_size); - } - self.cmd_table_dirty = true; + self.load_commands_dirty = true; + self.lazy_binding_info_dirty = false; } fn writeStringTable(self: *MachO) !void { From 34663abc9090d7b3afc2bc83d159c1d950b23e1d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 22 Dec 2020 10:57:58 +0100 Subject: [PATCH 13/13] macho: pad out LINKEDIT upto specified filesize --- src/link/MachO.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b51f094648..65dbe4e5f6 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2280,6 +2280,7 @@ fn updateLinkeditSegmentSizes(self: *MachO) !void { const filesize = final_offset - linkedit_segment.inner.fileoff; linkedit_segment.inner.filesize = filesize; linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, filesize, self.page_size); + try self.base.file.?.pwriteAll(&[_]u8{ 0 }, final_offset); self.load_commands_dirty = true; } @@ -2299,7 +2300,9 @@ fn writeLoadCommands(self: *MachO) !void { try lc.write(writer); } - try self.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); + const off = @sizeOf(macho.mach_header_64); + log.debug("writing {} load commands from 0x{x} to 0x{x}", .{self.load_commands.items.len, off, off + sizeofcmds}); + try self.base.file.?.pwriteAll(buffer, off); self.load_commands_dirty = false; }