Commit 09dee74414

Jakub Konka <kubkon@jakubkonka.com>
2022-12-15 13:32:51
macho: store LC headers to often updated LINKEDIT sections
1 parent 3af6a4e
Changed files (5)
lib/std/macho.zig
@@ -58,10 +58,10 @@ pub const uuid_command = extern struct {
     cmd: LC = .UUID,
 
     /// sizeof(struct uuid_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(uuid_command),
 
     /// the 128-bit uuid
-    uuid: [16]u8,
+    uuid: [16]u8 = undefined,
 };
 
 /// The version_min_command contains the min OS version on which this
@@ -71,7 +71,7 @@ pub const version_min_command = extern struct {
     cmd: LC,
 
     /// sizeof(struct version_min_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(version_min_command),
 
     /// X.Y.Z is encoded in nibbles xxxx.yy.zz
     version: u32,
@@ -87,7 +87,7 @@ pub const source_version_command = extern struct {
     cmd: LC = .SOURCE_VERSION,
 
     /// sizeof(source_version_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(source_version_command),
 
     /// A.B.C.D.E packed as a24.b10.c10.d10.e10
     version: u64,
@@ -155,13 +155,13 @@ pub const entry_point_command = extern struct {
     cmd: LC = .MAIN,
 
     /// sizeof(struct entry_point_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(entry_point_command),
 
     /// file (__TEXT) offset of main()
-    entryoff: u64,
+    entryoff: u64 = 0,
 
     /// if not zero, initial stack size
-    stacksize: u64,
+    stacksize: u64 = 0,
 };
 
 /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
@@ -172,19 +172,19 @@ pub const symtab_command = extern struct {
     cmd: LC = .SYMTAB,
 
     /// sizeof(struct symtab_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(symtab_command),
 
     /// symbol table offset
-    symoff: u32,
+    symoff: u32 = 0,
 
     /// number of symbol table entries
-    nsyms: u32,
+    nsyms: u32 = 0,
 
     /// string table offset
-    stroff: u32,
+    stroff: u32 = 0,
 
     /// string table size in bytes
-    strsize: u32,
+    strsize: u32 = 0,
 };
 
 /// This is the second set of the symbolic information which is used to support
@@ -230,7 +230,7 @@ pub const dysymtab_command = extern struct {
     cmd: LC = .DYSYMTAB,
 
     /// sizeof(struct dysymtab_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(dysymtab_command),
 
     // The symbols indicated by symoff and nsyms of the LC_SYMTAB load command
     // are grouped into the following three groups:
@@ -247,22 +247,22 @@ pub const dysymtab_command = extern struct {
     // table when this is a dynamically linked shared library file).
 
     /// index of local symbols
-    ilocalsym: u32,
+    ilocalsym: u32 = 0,
 
     /// number of local symbols
-    nlocalsym: u32,
+    nlocalsym: u32 = 0,
 
     /// index to externally defined symbols
-    iextdefsym: u32,
+    iextdefsym: u32 = 0,
 
     /// number of externally defined symbols
-    nextdefsym: u32,
+    nextdefsym: u32 = 0,
 
     /// index to undefined symbols
-    iundefsym: u32,
+    iundefsym: u32 = 0,
 
     /// number of undefined symbols
-    nundefsym: u32,
+    nundefsym: u32 = 0,
 
     // For the for the dynamic binding process to find which module a symbol
     // is defined in the table of contents is used (analogous to the ranlib
@@ -272,10 +272,10 @@ pub const dysymtab_command = extern struct {
     // symbols are sorted by name and is use as the table of contents.
 
     /// file offset to table of contents
-    tocoff: u32,
+    tocoff: u32 = 0,
 
     /// number of entries in table of contents
-    ntoc: u32,
+    ntoc: u32 = 0,
 
     // To support dynamic binding of "modules" (whole object files) the symbol
     // table must reflect the modules that the file was created from.  This is
@@ -286,10 +286,10 @@ pub const dysymtab_command = extern struct {
     // contains one module so everything in the file belongs to the module.
 
     /// file offset to module table
-    modtaboff: u32,
+    modtaboff: u32 = 0,
 
     /// number of module table entries
-    nmodtab: u32,
+    nmodtab: u32 = 0,
 
     // To support dynamic module binding the module structure for each module
     // indicates the external references (defined and undefined) each module
@@ -300,10 +300,10 @@ pub const dysymtab_command = extern struct {
     // undefined external symbols indicates the external references.
 
     /// offset to referenced symbol table
-    extrefsymoff: u32,
+    extrefsymoff: u32 = 0,
 
     /// number of referenced symbol table entries
-    nextrefsyms: u32,
+    nextrefsyms: u32 = 0,
 
     // The sections that contain "symbol pointers" and "routine stubs" have
     // indexes and (implied counts based on the size of the section and fixed
@@ -315,10 +315,10 @@ pub const dysymtab_command = extern struct {
     // The indirect symbol table is ordered to match the entries in the section.
 
     /// file offset to the indirect symbol table
-    indirectsymoff: u32,
+    indirectsymoff: u32 = 0,
 
     /// number of indirect symbol table entries
-    nindirectsyms: u32,
+    nindirectsyms: u32 = 0,
 
     // To support relocating an individual module in a library file quickly the
     // external relocation entries for each module in the library need to be
@@ -347,20 +347,20 @@ pub const dysymtab_command = extern struct {
     // remaining relocation entries must be local).
 
     /// offset to external relocation entries
-    extreloff: u32,
+    extreloff: u32 = 0,
 
     /// number of external relocation entries
-    nextrel: u32,
+    nextrel: u32 = 0,
 
     // All the local relocation entries are grouped together (they are not
     // grouped by their module since they are only used if the object is moved
     // from it staticly link edited address).
 
     /// offset to local relocation entries
-    locreloff: u32,
+    locreloff: u32 = 0,
 
     /// number of local relocation entries
-    nlocrel: u32,
+    nlocrel: u32 = 0,
 };
 
 /// The linkedit_data_command contains the offsets and sizes of a blob
@@ -370,13 +370,13 @@ pub const linkedit_data_command = extern struct {
     cmd: LC,
 
     /// sizeof(struct linkedit_data_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(linkedit_data_command),
 
     /// file offset of data in __LINKEDIT segment
-    dataoff: u32,
+    dataoff: u32 = 0,
 
     /// file size of data in __LINKEDIT segment
-    datasize: u32,
+    datasize: u32 = 0,
 };
 
 /// The dyld_info_command contains the file offsets and sizes of
@@ -387,10 +387,10 @@ pub const linkedit_data_command = extern struct {
 /// to interpret it.
 pub const dyld_info_command = extern struct {
     /// LC_DYLD_INFO or LC_DYLD_INFO_ONLY
-    cmd: LC,
+    cmd: LC = .DYLD_INFO_ONLY,
 
     /// sizeof(struct dyld_info_command)
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(dyld_info_command),
 
     // Dyld rebases an image whenever dyld loads it at an address different
     // from its preferred address.  The rebase information is a stream
@@ -403,10 +403,10 @@ pub const dyld_info_command = extern struct {
     // bytes.
 
     /// file offset to rebase info
-    rebase_off: u32,
+    rebase_off: u32 = 0,
 
     /// size of rebase info
-    rebase_size: u32,
+    rebase_size: u32 = 0,
 
     // Dyld binds an image during the loading process, if the image
     // requires any pointers to be initialized to symbols in other images.
@@ -420,10 +420,10 @@ pub const dyld_info_command = extern struct {
     // encoded in a few bytes.
 
     /// file offset to binding info
-    bind_off: u32,
+    bind_off: u32 = 0,
 
     /// size of binding info
-    bind_size: u32,
+    bind_size: u32 = 0,
 
     // Some C++ programs require dyld to unique symbols so that all
     // images in the process use the same copy of some code/data.
@@ -440,10 +440,10 @@ pub const dyld_info_command = extern struct {
     // and the call to operator new is then rebound.
 
     /// file offset to weak binding info
-    weak_bind_off: u32,
+    weak_bind_off: u32 = 0,
 
     /// size of weak binding info
-    weak_bind_size: u32,
+    weak_bind_size: u32 = 0,
 
     // Some uses of external symbols do not need to be bound immediately.
     // Instead they can be lazily bound on first use.  The lazy_bind
@@ -457,10 +457,10 @@ pub const dyld_info_command = extern struct {
     // to bind.
 
     /// file offset to lazy binding info
-    lazy_bind_off: u32,
+    lazy_bind_off: u32 = 0,
 
     /// size of lazy binding info
-    lazy_bind_size: u32,
+    lazy_bind_size: u32 = 0,
 
     // The symbols exported by a dylib are encoded in a trie.  This
     // is a compact representation that factors out common prefixes.
@@ -494,10 +494,10 @@ pub const dyld_info_command = extern struct {
     // edge points to.
 
     /// file offset to lazy binding info
-    export_off: u32,
+    export_off: u32 = 0,
 
     /// size of lazy binding info
-    export_size: u32,
+    export_size: u32 = 0,
 };
 
 /// A program that uses a dynamic linker contains a dylinker_command to identify
src/link/MachO/DebugSymbols.zig
@@ -26,6 +26,8 @@ dwarf: Dwarf,
 file: fs.File,
 page_size: u16,
 
+symtab_cmd: macho.symtab_command = .{},
+
 segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
 sections: std.ArrayListUnmanaged(macho.section_64) = .{},
 
@@ -296,28 +298,21 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
         }
     }
 
+    self.finalizeDwarfSegment(macho_file);
+    try self.writeLinkeditSegmentData(macho_file);
+
+    // Write load commands
     var lc_buffer = std.ArrayList(u8).init(self.allocator);
     defer lc_buffer.deinit();
     const lc_writer = lc_buffer.writer();
-    var ncmds: u32 = 0;
-
-    self.finalizeDwarfSegment(macho_file);
-    try self.writeLinkeditSegmentData(macho_file, &ncmds, lc_writer);
 
-    try load_commands.writeUuidLC(&macho_file.uuid.buf, &ncmds, lc_writer);
+    try self.writeSegmentHeaders(macho_file, lc_writer);
+    try lc_writer.writeStruct(self.symtab_cmd);
+    try lc_writer.writeStruct(macho_file.uuid_cmd);
 
-    var headers_buf = std.ArrayList(u8).init(self.allocator);
-    defer headers_buf.deinit();
-    try self.writeSegmentHeaders(macho_file, &ncmds, headers_buf.writer());
-
-    try self.file.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64));
-    try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len);
-
-    try self.writeHeader(
-        macho_file,
-        ncmds,
-        @intCast(u32, lc_buffer.items.len + headers_buf.items.len),
-    );
+    const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
+    try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
+    try self.writeHeader(macho_file, ncmds, @intCast(u32, lc_buffer.items.len));
 
     assert(!self.debug_abbrev_section_dirty);
     assert(!self.debug_aranges_section_dirty);
@@ -384,7 +379,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
     log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff});
 }
 
-fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, writer: anytype) !void {
+fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, writer: anytype) !void {
     // Write segment/section headers from the binary file first.
     const end = macho_file.linkedit_segment_cmd_index.?;
     for (macho_file.segments.items[0..end]) |seg, i| {
@@ -414,8 +409,6 @@ fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, wri
             out_header.offset = 0;
             try writer.writeStruct(out_header);
         }
-
-        ncmds.* += 1;
     }
     // Next, commit DSYM's __LINKEDIT and __DWARF segments headers.
     for (self.segments.items) |seg, i| {
@@ -424,7 +417,6 @@ fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, wri
         for (self.sections.items[indexes.start..indexes.end]) |header| {
             try writer.writeStruct(header);
         }
-        ncmds.* += 1;
     }
 }
 
@@ -463,33 +455,19 @@ fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
     return min_pos - start;
 }
 
-fn writeLinkeditSegmentData(
-    self: *DebugSymbols,
-    macho_file: *MachO,
-    ncmds: *u32,
-    lc_writer: anytype,
-) !void {
+fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    var symtab_cmd = macho.symtab_command{
-        .cmdsize = @sizeOf(macho.symtab_command),
-        .symoff = 0,
-        .nsyms = 0,
-        .stroff = 0,
-        .strsize = 0,
-    };
-    try self.writeSymtab(macho_file, &symtab_cmd);
-    try self.writeStrtab(&symtab_cmd);
-    try lc_writer.writeStruct(symtab_cmd);
-    ncmds.* += 1;
+    try self.writeSymtab(macho_file);
+    try self.writeStrtab();
 
     const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
     const aligned_size = mem.alignForwardGeneric(u64, seg.filesize, self.page_size);
     seg.vmsize = aligned_size;
 }
 
-fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_command) !void {
+fn writeSymtab(self: *DebugSymbols, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -528,10 +506,10 @@ fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_comman
     const needed_size = nsyms * @sizeOf(macho.nlist_64);
     seg.filesize = offset + needed_size - seg.fileoff;
 
-    lc.symoff = @intCast(u32, offset);
-    lc.nsyms = @intCast(u32, nsyms);
+    self.symtab_cmd.symoff = @intCast(u32, offset);
+    self.symtab_cmd.nsyms = @intCast(u32, nsyms);
 
-    const locals_off = lc.symoff;
+    const locals_off = @intCast(u32, offset);
     const locals_size = nlocals * @sizeOf(macho.nlist_64);
     const exports_off = locals_off + locals_size;
     const exports_size = nexports * @sizeOf(macho.nlist_64);
@@ -543,26 +521,26 @@ fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_comman
     try self.file.pwriteAll(mem.sliceAsBytes(exports.items), exports_off);
 }
 
-fn writeStrtab(self: *DebugSymbols, lc: *macho.symtab_command) !void {
+fn writeStrtab(self: *DebugSymbols) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
     const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
-    const symtab_size = @intCast(u32, lc.nsyms * @sizeOf(macho.nlist_64));
-    const offset = mem.alignForwardGeneric(u64, lc.symoff + symtab_size, @alignOf(u64));
+    const symtab_size = @intCast(u32, self.symtab_cmd.nsyms * @sizeOf(macho.nlist_64));
+    const offset = mem.alignForwardGeneric(u64, self.symtab_cmd.symoff + symtab_size, @alignOf(u64));
     const needed_size = mem.alignForwardGeneric(u64, self.strtab.buffer.items.len, @alignOf(u64));
 
     seg.filesize = offset + needed_size - seg.fileoff;
-    lc.stroff = @intCast(u32, offset);
-    lc.strsize = @intCast(u32, needed_size);
+    self.symtab_cmd.stroff = @intCast(u32, offset);
+    self.symtab_cmd.strsize = @intCast(u32, needed_size);
 
-    log.debug("writing string table from 0x{x} to 0x{x}", .{ lc.stroff, lc.stroff + lc.strsize });
+    log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
 
-    try self.file.pwriteAll(self.strtab.buffer.items, lc.stroff);
+    try self.file.pwriteAll(self.strtab.buffer.items, offset);
 
     if (self.strtab.buffer.items.len < needed_size) {
         // Ensure we are always padded to the actual length of the file.
-        try self.file.pwriteAll(&[_]u8{0}, lc.stroff + lc.strsize);
+        try self.file.pwriteAll(&[_]u8{0}, offset + needed_size);
     }
 }
 
src/link/MachO/load_commands.zig
@@ -131,7 +131,19 @@ pub fn calcMinHeaderPad(gpa: Allocator, options: *const link.Options, ctx: CalcL
     return offset;
 }
 
-pub fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void {
+pub fn calcNumOfLCs(lc_buffer: []const u8) u32 {
+    var ncmds: u32 = 0;
+    var pos: usize = 0;
+    while (true) {
+        if (pos >= lc_buffer.len) break;
+        const cmd = @ptrCast(*align(1) const macho.load_command, lc_buffer.ptr + pos).*;
+        ncmds += 1;
+        pos += cmd.cmdsize;
+    }
+    return ncmds;
+}
+
+pub fn writeDylinkerLC(lc_writer: anytype) !void {
     const name_len = mem.sliceTo(default_dyld_path, 0).len;
     const cmdsize = @intCast(u32, mem.alignForwardGeneric(
         u64,
@@ -148,7 +160,6 @@ pub fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void {
     if (padding > 0) {
         try lc_writer.writeByteNTimes(0, padding);
     }
-    ncmds.* += 1;
 }
 
 const WriteDylibLCCtx = struct {
@@ -159,7 +170,7 @@ const WriteDylibLCCtx = struct {
     compatibility_version: u32 = 0x10000,
 };
 
-fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void {
+fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
     const name_len = ctx.name.len + 1;
     const cmdsize = @intCast(u32, mem.alignForwardGeneric(
         u64,
@@ -182,10 +193,9 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void {
     if (padding > 0) {
         try lc_writer.writeByteNTimes(0, padding);
     }
-    ncmds.* += 1;
 }
 
-pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
+pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, lc_writer: anytype) !void {
     assert(options.output_mode == .Lib and options.link_mode == .Dynamic);
     const emit = options.emit.?;
     const install_name = options.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});
@@ -205,18 +215,7 @@ pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, ncmds: *u32,
         .name = install_name,
         .current_version = curr.major << 16 | curr.minor << 8 | curr.patch,
         .compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch,
-    }, ncmds, lc_writer);
-}
-
-pub fn writeMainLC(entryoff: u32, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
-    assert(options.output_mode == .Exe);
-    try lc_writer.writeStruct(macho.entry_point_command{
-        .cmd = .MAIN,
-        .cmdsize = @sizeOf(macho.entry_point_command),
-        .entryoff = entryoff,
-        .stacksize = options.stack_size_override orelse 0,
-    });
-    ncmds.* += 1;
+    }, lc_writer);
 }
 
 const RpathIterator = struct {
@@ -244,7 +243,7 @@ const RpathIterator = struct {
     }
 };
 
-pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
+pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: anytype) !void {
     var it = RpathIterator.init(gpa, options.rpath_list);
     defer it.deinit();
 
@@ -265,11 +264,10 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, ncmds: *u32,
         if (padding > 0) {
             try lc_writer.writeByteNTimes(0, padding);
         }
-        ncmds.* += 1;
     }
 }
 
-pub fn writeBuildVersionLC(options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
+pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void {
     const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
     const platform_version = blk: {
         const ver = options.target.os.version_range.semver.min;
@@ -299,10 +297,9 @@ pub fn writeBuildVersionLC(options: *const link.Options, ncmds: *u32, lc_writer:
         .tool = .LD,
         .version = 0x0,
     }));
-    ncmds.* += 1;
 }
 
-pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, ncmds: *u32, lc_writer: anytype) !void {
+pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void {
     for (referenced) |index| {
         const dylib = dylibs[index];
         const dylib_id = dylib.id orelse unreachable;
@@ -312,23 +309,6 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, ncmds: *u32,
             .timestamp = dylib_id.timestamp,
             .current_version = dylib_id.current_version,
             .compatibility_version = dylib_id.compatibility_version,
-        }, ncmds, lc_writer);
+        }, lc_writer);
     }
 }
-
-pub fn writeSourceVersionLC(ncmds: *u32, lc_writer: anytype) !void {
-    try lc_writer.writeStruct(macho.source_version_command{
-        .cmdsize = @sizeOf(macho.source_version_command),
-        .version = 0x0,
-    });
-    ncmds.* += 1;
-}
-
-pub fn writeUuidLC(uuid: *const [16]u8, ncmds: *u32, lc_writer: anytype) !void {
-    var uuid_lc = macho.uuid_command{
-        .cmdsize = @sizeOf(macho.uuid_command),
-        .uuid = uuid.*,
-    };
-    try lc_writer.writeStruct(uuid_lc);
-    ncmds.* += 1;
-}
src/link/MachO/zld.zig
@@ -38,6 +38,14 @@ pub const Zld = struct {
     page_size: u16,
     options: *const link.Options,
 
+    dyld_info_cmd: macho.dyld_info_command = .{},
+    symtab_cmd: macho.symtab_command = .{},
+    dysymtab_cmd: macho.dysymtab_command = .{},
+    function_starts_cmd: macho.linkedit_data_command = .{ .cmd = .FUNCTION_STARTS },
+    data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE },
+    uuid_cmd: macho.uuid_command = .{},
+    codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
+
     objects: std.ArrayListUnmanaged(Object) = .{},
     archives: std.ArrayListUnmanaged(Archive) = .{},
     dylibs: std.ArrayListUnmanaged(Dylib) = .{},
@@ -1728,7 +1736,7 @@ pub const Zld = struct {
         return (@intCast(u8, segment_precedence) << 4) + section_precedence;
     }
 
-    fn writeSegmentHeaders(self: *Zld, ncmds: *u32, writer: anytype) !void {
+    fn writeSegmentHeaders(self: *Zld, writer: anytype) !void {
         for (self.segments.items) |seg, i| {
             const indexes = self.getSectionIndexes(@intCast(u8, i));
             var out_seg = seg;
@@ -1752,16 +1760,14 @@ pub const Zld = struct {
                 if (header.size == 0) continue;
                 try writer.writeStruct(header);
             }
-
-            ncmds.* += 1;
         }
     }
 
-    fn writeLinkeditSegmentData(self: *Zld, ncmds: *u32, lc_writer: anytype, reverse_lookups: [][]u32) !void {
-        try self.writeDyldInfoData(ncmds, lc_writer, reverse_lookups);
-        try self.writeFunctionStarts(ncmds, lc_writer);
-        try self.writeDataInCode(ncmds, lc_writer);
-        try self.writeSymtabs(ncmds, lc_writer);
+    fn writeLinkeditSegmentData(self: *Zld, reverse_lookups: [][]u32) !void {
+        try self.writeDyldInfoData(reverse_lookups);
+        try self.writeFunctionStarts();
+        try self.writeDataInCode();
+        try self.writeSymtabs();
 
         const seg = self.getLinkeditSegmentPtr();
         seg.vmsize = mem.alignForwardGeneric(u64, seg.filesize, self.page_size);
@@ -2150,7 +2156,7 @@ pub const Zld = struct {
         try trie.finalize(gpa);
     }
 
-    fn writeDyldInfoData(self: *Zld, ncmds: *u32, lc_writer: anytype, reverse_lookups: [][]u32) !void {
+    fn writeDyldInfoData(self: *Zld, reverse_lookups: [][]u32) !void {
         const gpa = self.gpa;
 
         var rebase_pointers = std.ArrayList(bind.Pointer).init(gpa);
@@ -2219,21 +2225,14 @@ pub const Zld = struct {
         const size = math.cast(usize, lazy_bind_size) orelse return error.Overflow;
         try self.populateLazyBindOffsetsInStubHelper(buffer[offset..][0..size]);
 
-        try lc_writer.writeStruct(macho.dyld_info_command{
-            .cmd = .DYLD_INFO_ONLY,
-            .cmdsize = @sizeOf(macho.dyld_info_command),
-            .rebase_off = @intCast(u32, rebase_off),
-            .rebase_size = @intCast(u32, rebase_size),
-            .bind_off = @intCast(u32, bind_off),
-            .bind_size = @intCast(u32, bind_size),
-            .weak_bind_off = 0,
-            .weak_bind_size = 0,
-            .lazy_bind_off = @intCast(u32, lazy_bind_off),
-            .lazy_bind_size = @intCast(u32, lazy_bind_size),
-            .export_off = @intCast(u32, export_off),
-            .export_size = @intCast(u32, export_size),
-        });
-        ncmds.* += 1;
+        self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off);
+        self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size);
+        self.dyld_info_cmd.bind_off = @intCast(u32, bind_off);
+        self.dyld_info_cmd.bind_size = @intCast(u32, bind_size);
+        self.dyld_info_cmd.lazy_bind_off = @intCast(u32, lazy_bind_off);
+        self.dyld_info_cmd.lazy_bind_size = @intCast(u32, lazy_bind_size);
+        self.dyld_info_cmd.export_off = @intCast(u32, export_off);
+        self.dyld_info_cmd.export_size = @intCast(u32, export_size);
     }
 
     fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void {
@@ -2351,7 +2350,7 @@ pub const Zld = struct {
 
     const asc_u64 = std.sort.asc(u64);
 
-    fn writeFunctionStarts(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
+    fn writeFunctionStarts(self: *Zld) !void {
         const text_seg_index = self.getSegmentByName("__TEXT") orelse return;
         const text_sect_index = self.getSectionByName("__TEXT", "__text") orelse return;
         const text_seg = self.segments.items[text_seg_index];
@@ -2410,13 +2409,8 @@ pub const Zld = struct {
 
         try self.file.pwriteAll(buffer.items, offset);
 
-        try lc_writer.writeStruct(macho.linkedit_data_command{
-            .cmd = .FUNCTION_STARTS,
-            .cmdsize = @sizeOf(macho.linkedit_data_command),
-            .dataoff = @intCast(u32, offset),
-            .datasize = @intCast(u32, needed_size),
-        });
-        ncmds.* += 1;
+        self.function_starts_cmd.dataoff = @intCast(u32, offset);
+        self.function_starts_cmd.datasize = @intCast(u32, needed_size);
     }
 
     fn filterDataInCode(
@@ -2438,7 +2432,7 @@ pub const Zld = struct {
         return dices[start..end];
     }
 
-    fn writeDataInCode(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
+    fn writeDataInCode(self: *Zld) !void {
         var out_dice = std.ArrayList(macho.data_in_code_entry).init(self.gpa);
         defer out_dice.deinit();
 
@@ -2488,54 +2482,19 @@ pub const Zld = struct {
         log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
 
         try self.file.pwriteAll(mem.sliceAsBytes(out_dice.items), offset);
-        try lc_writer.writeStruct(macho.linkedit_data_command{
-            .cmd = .DATA_IN_CODE,
-            .cmdsize = @sizeOf(macho.linkedit_data_command),
-            .dataoff = @intCast(u32, offset),
-            .datasize = @intCast(u32, needed_size),
-        });
-        ncmds.* += 1;
+
+        self.data_in_code_cmd.dataoff = @intCast(u32, offset);
+        self.data_in_code_cmd.datasize = @intCast(u32, needed_size);
     }
 
-    fn writeSymtabs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
-        var symtab_cmd = macho.symtab_command{
-            .cmdsize = @sizeOf(macho.symtab_command),
-            .symoff = 0,
-            .nsyms = 0,
-            .stroff = 0,
-            .strsize = 0,
-        };
-        var dysymtab_cmd = macho.dysymtab_command{
-            .cmdsize = @sizeOf(macho.dysymtab_command),
-            .ilocalsym = 0,
-            .nlocalsym = 0,
-            .iextdefsym = 0,
-            .nextdefsym = 0,
-            .iundefsym = 0,
-            .nundefsym = 0,
-            .tocoff = 0,
-            .ntoc = 0,
-            .modtaboff = 0,
-            .nmodtab = 0,
-            .extrefsymoff = 0,
-            .nextrefsyms = 0,
-            .indirectsymoff = 0,
-            .nindirectsyms = 0,
-            .extreloff = 0,
-            .nextrel = 0,
-            .locreloff = 0,
-            .nlocrel = 0,
-        };
-        var ctx = try self.writeSymtab(&symtab_cmd);
+    fn writeSymtabs(self: *Zld) !void {
+        var ctx = try self.writeSymtab();
         defer ctx.imports_table.deinit();
-        try self.writeDysymtab(ctx, &dysymtab_cmd);
-        try self.writeStrtab(&symtab_cmd);
-        try lc_writer.writeStruct(symtab_cmd);
-        try lc_writer.writeStruct(dysymtab_cmd);
-        ncmds.* += 2;
+        try self.writeDysymtab(ctx);
+        try self.writeStrtab();
     }
 
-    fn writeSymtab(self: *Zld, lc: *macho.symtab_command) !SymtabCtx {
+    fn writeSymtab(self: *Zld) !SymtabCtx {
         const gpa = self.gpa;
 
         var locals = std.ArrayList(macho.nlist_64).init(gpa);
@@ -2618,8 +2577,8 @@ pub const Zld = struct {
         log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
         try self.file.pwriteAll(buffer.items, offset);
 
-        lc.symoff = @intCast(u32, offset);
-        lc.nsyms = nsyms;
+        self.symtab_cmd.symoff = @intCast(u32, offset);
+        self.symtab_cmd.nsyms = nsyms;
 
         return SymtabCtx{
             .nlocalsym = nlocals,
@@ -2629,7 +2588,7 @@ pub const Zld = struct {
         };
     }
 
-    fn writeStrtab(self: *Zld, lc: *macho.symtab_command) !void {
+    fn writeStrtab(self: *Zld) !void {
         const seg = self.getLinkeditSegmentPtr();
         const offset = mem.alignForwardGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64));
         const needed_size = self.strtab.buffer.items.len;
@@ -2639,8 +2598,8 @@ pub const Zld = struct {
 
         try self.file.pwriteAll(self.strtab.buffer.items, offset);
 
-        lc.stroff = @intCast(u32, offset);
-        lc.strsize = @intCast(u32, needed_size);
+        self.symtab_cmd.stroff = @intCast(u32, offset);
+        self.symtab_cmd.strsize = @intCast(u32, needed_size);
     }
 
     const SymtabCtx = struct {
@@ -2650,7 +2609,7 @@ pub const Zld = struct {
         imports_table: std.AutoHashMap(SymbolWithLoc, u32),
     };
 
-    fn writeDysymtab(self: *Zld, ctx: SymtabCtx, lc: *macho.dysymtab_command) !void {
+    fn writeDysymtab(self: *Zld, ctx: SymtabCtx) !void {
         const gpa = self.gpa;
         const nstubs = @intCast(u32, self.stubs.items.len);
         const ngot_entries = @intCast(u32, self.got_entries.items.len);
@@ -2706,21 +2665,33 @@ pub const Zld = struct {
         assert(buf.items.len == needed_size);
         try self.file.pwriteAll(buf.items, offset);
 
-        lc.nlocalsym = ctx.nlocalsym;
-        lc.iextdefsym = iextdefsym;
-        lc.nextdefsym = ctx.nextdefsym;
-        lc.iundefsym = iundefsym;
-        lc.nundefsym = ctx.nundefsym;
-        lc.indirectsymoff = @intCast(u32, offset);
-        lc.nindirectsyms = nindirectsyms;
+        self.dysymtab_cmd.nlocalsym = ctx.nlocalsym;
+        self.dysymtab_cmd.iextdefsym = iextdefsym;
+        self.dysymtab_cmd.nextdefsym = ctx.nextdefsym;
+        self.dysymtab_cmd.iundefsym = iundefsym;
+        self.dysymtab_cmd.nundefsym = ctx.nundefsym;
+        self.dysymtab_cmd.indirectsymoff = @intCast(u32, offset);
+        self.dysymtab_cmd.nindirectsyms = nindirectsyms;
     }
 
-    fn writeCodeSignaturePadding(
-        self: *Zld,
-        code_sig: *CodeSignature,
-        ncmds: *u32,
-        lc_writer: anytype,
-    ) !u32 {
+    fn writeUuid(self: *Zld, comp: *const Compilation, offset: u32) !void {
+        switch (self.options.optimize_mode) {
+            .Debug => {
+                // In Debug we don't really care about reproducibility, so put in a random value
+                // and be done with it.
+                std.crypto.random.bytes(&self.uuid_cmd.uuid);
+            },
+            else => {
+                const seg = self.getLinkeditSegmentPtr();
+                const file_size = seg.fileoff + seg.filesize;
+                try uuid.calcUuidParallel(comp, self.file, file_size, &self.uuid_cmd.uuid);
+            },
+        }
+        const in_file = @sizeOf(macho.mach_header_64) + offset + @sizeOf(macho.load_command);
+        try self.file.pwriteAll(&self.uuid_cmd.uuid, in_file);
+    }
+
+    fn writeCodeSignaturePadding(self: *Zld, code_sig: *CodeSignature) !void {
         const seg = self.getLinkeditSegmentPtr();
         // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
         // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
@@ -2733,23 +2704,11 @@ pub const Zld = struct {
         // except for code signature data.
         try self.file.pwriteAll(&[_]u8{0}, offset + needed_size - 1);
 
-        try lc_writer.writeStruct(macho.linkedit_data_command{
-            .cmd = .CODE_SIGNATURE,
-            .cmdsize = @sizeOf(macho.linkedit_data_command),
-            .dataoff = @intCast(u32, offset),
-            .datasize = @intCast(u32, needed_size),
-        });
-        ncmds.* += 1;
-
-        return @intCast(u32, offset);
+        self.codesig_cmd.dataoff = @intCast(u32, offset);
+        self.codesig_cmd.datasize = @intCast(u32, needed_size);
     }
 
-    fn writeCodeSignature(
-        self: *Zld,
-        comp: *const Compilation,
-        code_sig: *CodeSignature,
-        offset: u32,
-    ) !void {
+    fn writeCodeSignature(self: *Zld, comp: *const Compilation, code_sig: *CodeSignature) !void {
         const seg_id = self.getSegmentByName("__TEXT").?;
         const seg = self.segments.items[seg_id];
 
@@ -2760,17 +2719,17 @@ pub const Zld = struct {
             .file = self.file,
             .exec_seg_base = seg.fileoff,
             .exec_seg_limit = seg.filesize,
-            .file_size = offset,
+            .file_size = self.codesig_cmd.dataoff,
             .output_mode = self.options.output_mode,
         }, buffer.writer());
         assert(buffer.items.len == code_sig.size());
 
         log.debug("writing code signature from 0x{x} to 0x{x}", .{
-            offset,
-            offset + buffer.items.len,
+            self.codesig_cmd.dataoff,
+            self.codesig_cmd.dataoff + buffer.items.len,
         });
 
-        try self.file.pwriteAll(buffer.items, offset);
+        try self.file.pwriteAll(buffer.items, self.codesig_cmd.dataoff);
     }
 
     /// Writes Mach-O file header.
@@ -3986,13 +3945,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
         }
 
         try zld.writeAtoms(reverse_lookups);
-
-        var lc_buffer = std.ArrayList(u8).init(arena);
-        const lc_writer = lc_buffer.writer();
-
-        var ncmds: u32 = 0;
-
-        try zld.writeLinkeditSegmentData(&ncmds, lc_writer, reverse_lookups);
+        try zld.writeLinkeditSegmentData(reverse_lookups);
 
         // If the last section of __DATA segment is zerofill section, we need to ensure
         // that the free space between the end of the last non-zerofill section of __DATA
@@ -4017,47 +3970,48 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             }
         }
 
-        try load_commands.writeDylinkerLC(&ncmds, lc_writer);
+        // Write load commands
+        var lc_buffer = std.ArrayList(u8).init(arena);
+        const lc_writer = lc_buffer.writer();
+
+        try zld.writeSegmentHeaders(lc_writer);
+        try lc_writer.writeStruct(zld.dyld_info_cmd);
+        try lc_writer.writeStruct(zld.function_starts_cmd);
+        try lc_writer.writeStruct(zld.data_in_code_cmd);
+        try lc_writer.writeStruct(zld.symtab_cmd);
+        try lc_writer.writeStruct(zld.dysymtab_cmd);
+        try load_commands.writeDylinkerLC(lc_writer);
 
         if (zld.options.output_mode == .Exe) {
             const seg_id = zld.getSegmentByName("__TEXT").?;
             const seg = zld.segments.items[seg_id];
             const global = zld.getEntryPoint();
             const sym = zld.getSymbol(global);
-            try load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), options, &ncmds, lc_writer);
+            try lc_writer.writeStruct(macho.entry_point_command{
+                .entryoff = @intCast(u32, sym.n_value - seg.vmaddr),
+                .stacksize = options.stack_size_override orelse 0,
+            });
         } else {
             assert(zld.options.output_mode == .Lib);
-            try load_commands.writeDylibIdLC(zld.gpa, zld.options, &ncmds, lc_writer);
+            try load_commands.writeDylibIdLC(zld.gpa, zld.options, lc_writer);
         }
 
-        try load_commands.writeRpathLCs(zld.gpa, zld.options, &ncmds, lc_writer);
-        try load_commands.writeSourceVersionLC(&ncmds, lc_writer);
-        try load_commands.writeBuildVersionLC(zld.options, &ncmds, lc_writer);
-
-        // Looking forward into the future, we will want to offer `-no_uuid` support in which case
-        // there will be nothing to backpatch.
-        const uuid_offset_backpatch: ?usize = blk: {
-            const index = lc_buffer.items.len;
-            var uuid_buf: [16]u8 = [_]u8{0} ** 16;
-
-            if (zld.options.optimize_mode == .Debug) {
-                // In Debug we don't really care about reproducibility, so put in a random value
-                // and be done with it.
-                std.crypto.random.bytes(&uuid_buf);
-            }
+        try load_commands.writeRpathLCs(zld.gpa, zld.options, lc_writer);
+        try lc_writer.writeStruct(macho.source_version_command{
+            .version = 0,
+        });
+        try load_commands.writeBuildVersionLC(zld.options, lc_writer);
 
-            try load_commands.writeUuidLC(&uuid_buf, &ncmds, lc_writer);
-            break :blk if (zld.options.optimize_mode == .Debug) null else index;
-        };
+        const uuid_offset = @intCast(u32, lc_buffer.items.len);
+        try lc_writer.writeStruct(zld.uuid_cmd);
 
-        try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), &ncmds, lc_writer);
+        try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), lc_writer);
 
         const requires_codesig = blk: {
             if (options.entitlements) |_| break :blk true;
             if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) break :blk true;
             break :blk false;
         };
-        var codesig_offset: ?u32 = null;
         var codesig: ?CodeSignature = if (requires_codesig) blk: {
             // Preallocate space for the code signature.
             // We need to do this at this stage so that we have the load commands with proper values
@@ -4069,29 +4023,20 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             if (options.entitlements) |path| {
                 try codesig.addEntitlements(gpa, path);
             }
-            codesig_offset = try zld.writeCodeSignaturePadding(&codesig, &ncmds, lc_writer);
+            try zld.writeCodeSignaturePadding(&codesig);
+            try lc_writer.writeStruct(zld.codesig_cmd);
             break :blk codesig;
         } else null;
         defer if (codesig) |*csig| csig.deinit(gpa);
 
-        var headers_buf = std.ArrayList(u8).init(arena);
-        try zld.writeSegmentHeaders(&ncmds, headers_buf.writer());
+        const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
+        try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
+        try zld.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len));
 
-        try zld.file.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64));
-        try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len);
-        try zld.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len + headers_buf.items.len));
-
-        if (uuid_offset_backpatch) |backpatch| {
-            const seg = zld.getLinkeditSegmentPtr();
-            const file_size = seg.fileoff + seg.filesize;
-            var uuid_buf: [16]u8 = undefined;
-            try uuid.calcUuidParallel(comp, zld.file, file_size, &uuid_buf);
-            const offset = @sizeOf(macho.mach_header_64) + headers_buf.items.len + backpatch + @sizeOf(macho.load_command);
-            try zld.file.pwriteAll(&uuid_buf, offset);
-        }
+        try zld.writeUuid(comp, uuid_offset);
 
         if (codesig) |*csig| {
-            try zld.writeCodeSignature(comp, csig, codesig_offset.?); // code signing always comes last
+            try zld.writeCodeSignature(comp, csig); // code signing always comes last
         }
     }
 
src/link/MachO.zig
@@ -99,10 +99,11 @@ page_size: u16,
 /// fashion (default for LLVM backend).
 mode: enum { incremental, one_shot },
 
-uuid: struct {
-    buf: [16]u8 = undefined,
-    final: bool = false,
-} = .{},
+dyld_info_cmd: macho.dyld_info_command = .{},
+symtab_cmd: macho.symtab_command = .{},
+dysymtab_cmd: macho.dysymtab_command = .{},
+uuid_cmd: macho.uuid_command = .{},
+codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
 
 dylibs: std.ArrayListUnmanaged(Dylib) = .{},
 dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
@@ -554,12 +555,17 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
         self.logAtoms();
     }
 
+    try self.writeLinkeditSegmentData();
+
+    // Write load commands
     var lc_buffer = std.ArrayList(u8).init(arena);
     const lc_writer = lc_buffer.writer();
-    var ncmds: u32 = 0;
 
-    try self.writeLinkeditSegmentData(&ncmds, lc_writer);
-    try load_commands.writeDylinkerLC(&ncmds, lc_writer);
+    try self.writeSegmentHeaders(lc_writer);
+    try lc_writer.writeStruct(self.dyld_info_cmd);
+    try lc_writer.writeStruct(self.symtab_cmd);
+    try lc_writer.writeStruct(self.dysymtab_cmd);
+    try load_commands.writeDylinkerLC(lc_writer);
 
     switch (self.base.options.output_mode) {
         .Exe => blk: {
@@ -573,33 +579,29 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
                 else => |e| return e,
             };
             const sym = self.getSymbol(global);
-            try load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), &self.base.options, &ncmds, lc_writer);
+            try lc_writer.writeStruct(macho.entry_point_command{
+                .entryoff = @intCast(u32, sym.n_value - seg.vmaddr),
+                .stacksize = self.base.options.stack_size_override orelse 0,
+            });
         },
         .Lib => if (self.base.options.link_mode == .Dynamic) {
-            try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, &ncmds, lc_writer);
+            try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, lc_writer);
         },
         else => {},
     }
 
-    try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, &ncmds, lc_writer);
-
-    {
-        try lc_writer.writeStruct(macho.source_version_command{
-            .cmdsize = @sizeOf(macho.source_version_command),
-            .version = 0x0,
-        });
-        ncmds += 1;
-    }
-
-    try load_commands.writeBuildVersionLC(&self.base.options, &ncmds, lc_writer);
+    try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, lc_writer);
+    try lc_writer.writeStruct(macho.source_version_command{
+        .version = 0,
+    });
+    try load_commands.writeBuildVersionLC(&self.base.options, lc_writer);
 
-    if (!self.uuid.final) {
-        std.crypto.random.bytes(&self.uuid.buf);
-        self.uuid.final = true;
+    if (self.cold_start) {
+        std.crypto.random.bytes(&self.uuid_cmd.uuid);
     }
-    try load_commands.writeUuidLC(&self.uuid.buf, &ncmds, lc_writer);
+    try lc_writer.writeStruct(self.uuid_cmd);
 
-    try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), &ncmds, lc_writer);
+    try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer);
 
     const target = self.base.options.target;
     const requires_codesig = blk: {
@@ -608,7 +610,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
             break :blk true;
         break :blk false;
     };
-    var codesig_offset: ?u32 = null;
     var codesig: ?CodeSignature = if (requires_codesig) blk: {
         // Preallocate space for the code signature.
         // We need to do this at this stage so that we have the load commands with proper values
@@ -620,20 +621,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
         if (self.base.options.entitlements) |path| {
             try codesig.addEntitlements(arena, path);
         }
-        codesig_offset = try self.writeCodeSignaturePadding(&codesig, &ncmds, lc_writer);
+        try self.writeCodeSignaturePadding(&codesig);
+        try lc_writer.writeStruct(self.codesig_cmd);
         break :blk codesig;
     } else null;
 
-    var headers_buf = std.ArrayList(u8).init(arena);
-    try self.writeSegmentHeaders(&ncmds, headers_buf.writer());
+    try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
 
-    try self.base.file.?.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64));
-    try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len);
-
-    try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len + headers_buf.items.len));
+    const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
+    try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len));
 
     if (codesig) |*csig| {
-        try self.writeCodeSignature(comp, csig, codesig_offset.?); // code signing always comes last
+        try self.writeCodeSignature(comp, csig); // code signing always comes last
     }
 
     if (self.d_sym) |*d_sym| {
@@ -3146,18 +3145,17 @@ pub fn getGlobalSymbol(self: *MachO, name: []const u8) !u32 {
     return global_index;
 }
 
-fn writeSegmentHeaders(self: *MachO, ncmds: *u32, writer: anytype) !void {
+fn writeSegmentHeaders(self: *MachO, writer: anytype) !void {
     for (self.segments.items) |seg, i| {
         const indexes = self.getSectionIndexes(@intCast(u8, i));
         try writer.writeStruct(seg);
         for (self.sections.items(.header)[indexes.start..indexes.end]) |header| {
             try writer.writeStruct(header);
         }
-        ncmds.* += 1;
     }
 }
 
-fn writeLinkeditSegmentData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
+fn writeLinkeditSegmentData(self: *MachO) !void {
     const seg = self.getLinkeditSegmentPtr();
     seg.filesize = 0;
     seg.vmsize = 0;
@@ -3172,8 +3170,8 @@ fn writeLinkeditSegmentData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void
         }
     }
 
-    try self.writeDyldInfoData(ncmds, lc_writer);
-    try self.writeSymtabs(ncmds, lc_writer);
+    try self.writeDyldInfoData();
+    try self.writeSymtabs();
 
     seg.vmsize = mem.alignForwardGeneric(u64, seg.filesize, self.page_size);
 }
@@ -3325,7 +3323,7 @@ fn collectExportData(self: *MachO, trie: *Trie) !void {
     try trie.finalize(gpa);
 }
 
-fn writeDyldInfoData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
+fn writeDyldInfoData(self: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -3396,21 +3394,14 @@ fn writeDyldInfoData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
     const end = start + (math.cast(usize, lazy_bind_size) orelse return error.Overflow);
     try self.populateLazyBindOffsetsInStubHelper(buffer[start..end]);
 
-    try lc_writer.writeStruct(macho.dyld_info_command{
-        .cmd = .DYLD_INFO_ONLY,
-        .cmdsize = @sizeOf(macho.dyld_info_command),
-        .rebase_off = @intCast(u32, rebase_off),
-        .rebase_size = @intCast(u32, rebase_size),
-        .bind_off = @intCast(u32, bind_off),
-        .bind_size = @intCast(u32, bind_size),
-        .weak_bind_off = 0,
-        .weak_bind_size = 0,
-        .lazy_bind_off = @intCast(u32, lazy_bind_off),
-        .lazy_bind_size = @intCast(u32, lazy_bind_size),
-        .export_off = @intCast(u32, export_off),
-        .export_size = @intCast(u32, export_size),
-    });
-    ncmds.* += 1;
+    self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off);
+    self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size);
+    self.dyld_info_cmd.bind_off = @intCast(u32, bind_off);
+    self.dyld_info_cmd.bind_size = @intCast(u32, bind_size);
+    self.dyld_info_cmd.lazy_bind_off = @intCast(u32, lazy_bind_off);
+    self.dyld_info_cmd.lazy_bind_size = @intCast(u32, lazy_bind_size);
+    self.dyld_info_cmd.export_off = @intCast(u32, export_off);
+    self.dyld_info_cmd.export_size = @intCast(u32, export_size);
 }
 
 fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void {
@@ -3512,45 +3503,14 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void {
     }
 }
 
-fn writeSymtabs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
-    var symtab_cmd = macho.symtab_command{
-        .cmdsize = @sizeOf(macho.symtab_command),
-        .symoff = 0,
-        .nsyms = 0,
-        .stroff = 0,
-        .strsize = 0,
-    };
-    var dysymtab_cmd = macho.dysymtab_command{
-        .cmdsize = @sizeOf(macho.dysymtab_command),
-        .ilocalsym = 0,
-        .nlocalsym = 0,
-        .iextdefsym = 0,
-        .nextdefsym = 0,
-        .iundefsym = 0,
-        .nundefsym = 0,
-        .tocoff = 0,
-        .ntoc = 0,
-        .modtaboff = 0,
-        .nmodtab = 0,
-        .extrefsymoff = 0,
-        .nextrefsyms = 0,
-        .indirectsymoff = 0,
-        .nindirectsyms = 0,
-        .extreloff = 0,
-        .nextrel = 0,
-        .locreloff = 0,
-        .nlocrel = 0,
-    };
-    var ctx = try self.writeSymtab(&symtab_cmd);
+fn writeSymtabs(self: *MachO) !void {
+    var ctx = try self.writeSymtab();
     defer ctx.imports_table.deinit();
-    try self.writeDysymtab(ctx, &dysymtab_cmd);
-    try self.writeStrtab(&symtab_cmd);
-    try lc_writer.writeStruct(symtab_cmd);
-    try lc_writer.writeStruct(dysymtab_cmd);
-    ncmds.* += 2;
+    try self.writeDysymtab(ctx);
+    try self.writeStrtab();
 }
 
-fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx {
+fn writeSymtab(self: *MachO) !SymtabCtx {
     const gpa = self.base.allocator;
 
     var locals = std.ArrayList(macho.nlist_64).init(gpa);
@@ -3615,8 +3575,8 @@ fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx {
     log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
     try self.base.file.?.pwriteAll(buffer.items, offset);
 
-    lc.symoff = @intCast(u32, offset);
-    lc.nsyms = nsyms;
+    self.symtab_cmd.symoff = @intCast(u32, offset);
+    self.symtab_cmd.nsyms = nsyms;
 
     return SymtabCtx{
         .nlocalsym = nlocals,
@@ -3626,7 +3586,7 @@ fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx {
     };
 }
 
-fn writeStrtab(self: *MachO, lc: *macho.symtab_command) !void {
+fn writeStrtab(self: *MachO) !void {
     const seg = self.getLinkeditSegmentPtr();
     const offset = mem.alignForwardGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64));
     const needed_size = self.strtab.buffer.items.len;
@@ -3636,8 +3596,8 @@ fn writeStrtab(self: *MachO, lc: *macho.symtab_command) !void {
 
     try self.base.file.?.pwriteAll(self.strtab.buffer.items, offset);
 
-    lc.stroff = @intCast(u32, offset);
-    lc.strsize = @intCast(u32, needed_size);
+    self.symtab_cmd.stroff = @intCast(u32, offset);
+    self.symtab_cmd.strsize = @intCast(u32, needed_size);
 }
 
 const SymtabCtx = struct {
@@ -3647,7 +3607,7 @@ const SymtabCtx = struct {
     imports_table: std.AutoHashMap(SymbolWithLoc, u32),
 };
 
-fn writeDysymtab(self: *MachO, ctx: SymtabCtx, lc: *macho.dysymtab_command) !void {
+fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
     const gpa = self.base.allocator;
     const nstubs = @intCast(u32, self.stubs_table.count());
     const ngot_entries = @intCast(u32, self.got_entries_table.count());
@@ -3706,21 +3666,16 @@ fn writeDysymtab(self: *MachO, ctx: SymtabCtx, lc: *macho.dysymtab_command) !voi
     assert(buf.items.len == needed_size);
     try self.base.file.?.pwriteAll(buf.items, offset);
 
-    lc.nlocalsym = ctx.nlocalsym;
-    lc.iextdefsym = iextdefsym;
-    lc.nextdefsym = ctx.nextdefsym;
-    lc.iundefsym = iundefsym;
-    lc.nundefsym = ctx.nundefsym;
-    lc.indirectsymoff = @intCast(u32, offset);
-    lc.nindirectsyms = nindirectsyms;
+    self.dysymtab_cmd.nlocalsym = ctx.nlocalsym;
+    self.dysymtab_cmd.iextdefsym = iextdefsym;
+    self.dysymtab_cmd.nextdefsym = ctx.nextdefsym;
+    self.dysymtab_cmd.iundefsym = iundefsym;
+    self.dysymtab_cmd.nundefsym = ctx.nundefsym;
+    self.dysymtab_cmd.indirectsymoff = @intCast(u32, offset);
+    self.dysymtab_cmd.nindirectsyms = nindirectsyms;
 }
 
-fn writeCodeSignaturePadding(
-    self: *MachO,
-    code_sig: *CodeSignature,
-    ncmds: *u32,
-    lc_writer: anytype,
-) !u32 {
+fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
     const seg = self.getLinkeditSegmentPtr();
     // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
     // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
@@ -3733,19 +3688,13 @@ fn writeCodeSignaturePadding(
     // except for code signature data.
     try self.base.file.?.pwriteAll(&[_]u8{0}, offset + needed_size - 1);
 
-    try lc_writer.writeStruct(macho.linkedit_data_command{
-        .cmd = .CODE_SIGNATURE,
-        .cmdsize = @sizeOf(macho.linkedit_data_command),
-        .dataoff = @intCast(u32, offset),
-        .datasize = @intCast(u32, needed_size),
-    });
-    ncmds.* += 1;
-
-    return @intCast(u32, offset);
+    self.codesig_cmd.dataoff = @intCast(u32, offset);
+    self.codesig_cmd.datasize = @intCast(u32, needed_size);
 }
 
-fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature, offset: u32) !void {
+fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void {
     const seg = self.getSegment(self.text_section_index.?);
+    const offset = self.codesig_cmd.dataoff;
 
     var buffer = std.ArrayList(u8).init(self.base.allocator);
     defer buffer.deinit();