Commit a1b3606f0e

Jakub Konka <kubkon@jakubkonka.com>
2020-12-21 15:13:04
macho: dynamically preallocate space for LINKEDIT sections as well
1 parent 3f7dbde
Changed files (1)
src
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
+fn writeLocalSymbol(self: *MachO, index: usize) !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;
 
-    const locals_off = self.linkedit_segment_next_offset.?;
+    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;
-
-    log.debug("writing export trie from 0x{x} to 0x{x}\n", .{ dyld_info.export_off, dyld_info.export_off + export_size });
+    const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.export_off);
+    const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
 
-    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));
     }
-    try self.base.file.?.pwriteAll(buffer, dyld_info.export_off);
+    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 });
 
-    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);
-    }
+    try self.base.file.?.pwriteAll(buffer, dyld_info.export_off);
     self.cmd_table_dirty = true;
 }
 
@@ -2156,30 +2189,68 @@ fn writeLazyBindingInfoTable(self: *MachO) !void {
 }
 
 fn writeStringTable(self: *MachO) !void {
-    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-    const needed_size = self.string_table.items.len;
+    const tracy = trace(@src());
+    defer tracy.end();
 
-    symtab.stroff = self.linkedit_segment_next_offset.?;
-    symtab.strsize = @intCast(u32, mem.alignForward(needed_size, @sizeOf(u64)));
+    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 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;