Commit 50db993119

Jakub Konka <kubkon@jakubkonka.com>
2021-08-31 23:05:01
macho: fix allocating sections within segment when parsing objects
1 parent 2831d6e
Changed files (4)
lib/std/macho.zig
@@ -601,35 +601,35 @@ pub const segment_command = extern struct {
 /// command and their size is reflected in cmdsize.
 pub const segment_command_64 = extern struct {
     /// LC_SEGMENT_64
-    cmd: u32,
+    cmd: u32 = LC_SEGMENT_64,
 
     /// includes sizeof section_64 structs
-    cmdsize: u32,
+    cmdsize: u32 = @sizeOf(segment_command_64),
 
     /// segment name
     segname: [16]u8,
 
     /// memory address of this segment
-    vmaddr: u64,
+    vmaddr: u64 = 0,
 
     /// memory size of this segment
-    vmsize: u64,
+    vmsize: u64 = 0,
 
     /// file offset of this segment
-    fileoff: u64,
+    fileoff: u64 = 0,
 
     /// amount to map from the file
-    filesize: u64,
+    filesize: u64 = 0,
 
     /// maximum VM protection
-    maxprot: vm_prot_t,
+    maxprot: vm_prot_t = VM_PROT_NONE,
 
     /// initial VM protection
-    initprot: vm_prot_t,
+    initprot: vm_prot_t = VM_PROT_NONE,
 
     /// number of sections in segment
-    nsects: u32,
-    flags: u32,
+    nsects: u32 = 0,
+    flags: u32 = 0,
 };
 
 /// A segment is made up of zero or more sections.  Non-MH_OBJECT files have
@@ -700,34 +700,34 @@ pub const section_64 = extern struct {
     segname: [16]u8,
 
     /// memory address of this section
-    addr: u64,
+    addr: u64 = 0,
 
     /// size in bytes of this section
-    size: u64,
+    size: u64 = 0,
 
     /// file offset of this section
-    offset: u32,
+    offset: u32 = 0,
 
     /// section alignment (power of 2)
-    @"align": u32,
+    @"align": u32 = 0,
 
     /// file offset of relocation entries
-    reloff: u32,
+    reloff: u32 = 0,
 
     /// number of relocation entries
-    nreloc: u32,
+    nreloc: u32 = 0,
 
     /// flags (section type and attributes
-    flags: u32,
+    flags: u32 = S_REGULAR,
 
     /// reserved (for offset or index)
-    reserved1: u32,
+    reserved1: u32 = 0,
 
     /// reserved (for count or sizeof)
-    reserved2: u32,
+    reserved2: u32 = 0,
 
     /// reserved
-    reserved3: u32,
+    reserved3: u32 = 0,
 };
 
 pub const nlist = extern struct {
src/link/MachO/commands.zig
@@ -9,6 +9,7 @@ const assert = std.debug.assert;
 
 const Allocator = std.mem.Allocator;
 const MachO = @import("../MachO.zig");
+const makeStaticString = MachO.makeStaticString;
 const padToIdeal = MachO.padToIdeal;
 
 pub const HeaderArgs = struct {
@@ -217,75 +218,6 @@ pub const SegmentCommand = struct {
     inner: macho.segment_command_64,
     sections: std.ArrayListUnmanaged(macho.section_64) = .{},
 
-    const SegmentOptions = struct {
-        cmdsize: u32 = @sizeOf(macho.segment_command_64),
-        vmaddr: u64 = 0,
-        vmsize: u64 = 0,
-        fileoff: u64 = 0,
-        filesize: u64 = 0,
-        maxprot: macho.vm_prot_t = macho.VM_PROT_NONE,
-        initprot: macho.vm_prot_t = macho.VM_PROT_NONE,
-        nsects: u32 = 0,
-        flags: u32 = 0,
-    };
-
-    pub fn empty(comptime segname: []const u8, opts: SegmentOptions) SegmentCommand {
-        return .{
-            .inner = .{
-                .cmd = macho.LC_SEGMENT_64,
-                .cmdsize = opts.cmdsize,
-                .segname = makeStaticString(segname),
-                .vmaddr = opts.vmaddr,
-                .vmsize = opts.vmsize,
-                .fileoff = opts.fileoff,
-                .filesize = opts.filesize,
-                .maxprot = opts.maxprot,
-                .initprot = opts.initprot,
-                .nsects = opts.nsects,
-                .flags = opts.flags,
-            },
-        };
-    }
-
-    const SectionOptions = struct {
-        addr: u64 = 0,
-        size: u64 = 0,
-        offset: u32 = 0,
-        @"align": u32 = 0,
-        reloff: u32 = 0,
-        nreloc: u32 = 0,
-        flags: u32 = macho.S_REGULAR,
-        reserved1: u32 = 0,
-        reserved2: u32 = 0,
-        reserved3: u32 = 0,
-    };
-
-    pub fn addSection(
-        self: *SegmentCommand,
-        alloc: *Allocator,
-        comptime sectname: []const u8,
-        opts: SectionOptions,
-    ) !void {
-        var section = macho.section_64{
-            .sectname = makeStaticString(sectname),
-            .segname = undefined,
-            .addr = opts.addr,
-            .size = opts.size,
-            .offset = opts.offset,
-            .@"align" = opts.@"align",
-            .reloff = opts.reloff,
-            .nreloc = opts.nreloc,
-            .flags = opts.flags,
-            .reserved1 = opts.reserved1,
-            .reserved2 = opts.reserved2,
-            .reserved3 = opts.reserved3,
-        };
-        mem.copy(u8, &section.segname, &self.inner.segname);
-        try self.sections.append(alloc, section);
-        self.inner.cmdsize += @sizeOf(macho.section_64);
-        self.inner.nsects += 1;
-    }
-
     pub fn read(alloc: *Allocator, reader: anytype) !SegmentCommand {
         const inner = try reader.readStruct(macho.segment_command_64);
         var segment = SegmentCommand{
@@ -427,13 +359,6 @@ pub fn createLoadDylibCommand(
     return dylib_cmd;
 }
 
-fn makeStaticString(bytes: []const u8) [16]u8 {
-    var buf = [_]u8{0} ** 16;
-    assert(bytes.len <= buf.len);
-    mem.copy(u8, &buf, bytes);
-    return buf;
-}
-
 fn parseName(name: *const [16]u8) []const u8 {
     const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
     return name[0..len];
@@ -513,34 +438,26 @@ test "read-write segment command" {
         0x00, 0x00, 0x00, 0x00, // reserved3
     };
     var cmd = SegmentCommand{
-        .inner = .{
-            .cmd = macho.LC_SEGMENT_64,
+        .inner = macho.segment_command_64.new(.{
             .cmdsize = 152,
             .segname = makeStaticString("__TEXT"),
             .vmaddr = 4294967296,
             .vmsize = 294912,
-            .fileoff = 0,
             .filesize = 294912,
             .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE,
             .initprot = macho.VM_PROT_EXECUTE | macho.VM_PROT_READ,
             .nsects = 1,
-            .flags = 0,
-        },
+        }),
     };
-    try cmd.sections.append(gpa, .{
+    try cmd.sections.append(gpa, macho.section_64.new(.{
         .sectname = makeStaticString("__text"),
         .segname = makeStaticString("__TEXT"),
         .addr = 4294983680,
         .size = 448,
         .offset = 16384,
         .@"align" = 2,
-        .reloff = 0,
-        .nreloc = 0,
         .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-        .reserved1 = 0,
-        .reserved2 = 0,
-        .reserved3 = 0,
-    });
+    }));
     defer cmd.deinit(gpa);
     try testRead(gpa, in_buffer, LoadCommand{ .Segment = cmd });
 
src/link/MachO/DebugSymbols.zig
@@ -5,23 +5,26 @@ const assert = std.debug.assert;
 const fs = std.fs;
 const log = std.log.scoped(.dsym);
 const macho = std.macho;
+const math = std.math;
 const mem = std.mem;
 const DW = std.dwarf;
 const leb = std.leb;
 const Allocator = mem.Allocator;
 
 const build_options = @import("build_options");
+const commands = @import("commands.zig");
 const trace = @import("../../tracy.zig").trace;
+const LoadCommand = commands.LoadCommand;
 const Module = @import("../../Module.zig");
 const Type = @import("../../type.zig").Type;
 const link = @import("../../link.zig");
 const MachO = @import("../MachO.zig");
-const SrcFn = MachO.SrcFn;
 const TextBlock = MachO.TextBlock;
+const SegmentCommand = commands.SegmentCommand;
+const SrcFn = MachO.SrcFn;
+const makeStaticString = MachO.makeStaticString;
 const padToIdeal = MachO.padToIdeal;
 
-usingnamespace @import("commands.zig");
-
 const page_size: u16 = 0x1000;
 
 base: *MachO,
@@ -185,105 +188,84 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
         log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
 
         try self.load_commands.append(allocator, .{
-            .Segment = SegmentCommand.empty("__DWARF", .{
-                .vmaddr = vmaddr,
-                .vmsize = needed_size,
-                .fileoff = off,
-                .filesize = needed_size,
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__DWARF"),
+                    .vmaddr = vmaddr,
+                    .vmsize = needed_size,
+                    .fileoff = off,
+                    .filesize = needed_size,
+                },
+            },
         });
         self.load_commands_dirty = true;
     }
     if (self.debug_str_section_index == null) {
-        const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-        self.debug_str_section_index = @intCast(u16, dwarf_segment.sections.items.len);
         assert(self.debug_string_table.items.len == 0);
-
-        try dwarf_segment.addSection(allocator, "__debug_str", .{
-            .addr = dwarf_segment.inner.vmaddr,
-            .size = @intCast(u32, self.debug_string_table.items.len),
-            .offset = @intCast(u32, dwarf_segment.inner.fileoff),
-            .@"align" = 1,
-        });
-        self.load_commands_dirty = true;
+        self.debug_str_section_index = try self.allocateSection(
+            "__debug_str",
+            @intCast(u32, self.debug_string_table.items.len),
+            0,
+        );
         self.debug_string_table_dirty = true;
     }
     if (self.debug_info_section_index == null) {
-        const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-        self.debug_info_section_index = @intCast(u16, dwarf_segment.sections.items.len);
-
-        const file_size_hint = 200;
-        const p_align = 1;
-        const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
-
-        log.debug("found __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
-
-        try dwarf_segment.addSection(allocator, "__debug_info", .{
-            .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
-            .size = file_size_hint,
-            .offset = @intCast(u32, off),
-            .@"align" = p_align,
-        });
-        self.load_commands_dirty = true;
+        self.debug_info_section_index = try self.allocateSection("__debug_info", 200, 0);
         self.debug_info_header_dirty = true;
     }
     if (self.debug_abbrev_section_index == null) {
-        const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-        self.debug_abbrev_section_index = @intCast(u16, dwarf_segment.sections.items.len);
-
-        const file_size_hint = 128;
-        const p_align = 1;
-        const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
-
-        log.debug("found __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
-
-        try dwarf_segment.addSection(allocator, "__debug_abbrev", .{
-            .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
-            .size = file_size_hint,
-            .offset = @intCast(u32, off),
-            .@"align" = p_align,
-        });
-        self.load_commands_dirty = true;
+        self.debug_abbrev_section_index = try self.allocateSection("__debug_abbrev", 128, 0);
         self.debug_abbrev_section_dirty = true;
     }
     if (self.debug_aranges_section_index == null) {
-        const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-        self.debug_aranges_section_index = @intCast(u16, dwarf_segment.sections.items.len);
-
-        const file_size_hint = 160;
-        const p_align = 16;
-        const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
-
-        log.debug("found __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
-
-        try dwarf_segment.addSection(allocator, "__debug_aranges", .{
-            .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
-            .size = file_size_hint,
-            .offset = @intCast(u32, off),
-            .@"align" = p_align,
-        });
-        self.load_commands_dirty = true;
+        self.debug_aranges_section_index = try self.allocateSection("__debug_aranges", 160, 4);
         self.debug_aranges_section_dirty = true;
     }
     if (self.debug_line_section_index == null) {
-        const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-        self.debug_line_section_index = @intCast(u16, dwarf_segment.sections.items.len);
+        self.debug_line_section_index = try self.allocateSection("__debug_line", 250, 0);
+        self.debug_line_header_dirty = true;
+    }
+}
 
-        const file_size_hint = 250;
-        const p_align = 1;
-        const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
+fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignment: u16) !u16 {
+    const seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
+    var sect = macho.section_64{
+        .sectname = makeStaticString(sectname),
+        .segname = seg.inner.segname,
+        .size = @intCast(u32, size),
+        .@"align" = alignment,
+    };
+    const alignment_pow_2 = try math.powi(u32, 2, alignment);
+    const off = seg.findFreeSpace(size, alignment_pow_2, null);
 
-        log.debug("found __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
+    assert(off + size <= seg.inner.fileoff + seg.inner.filesize); // TODO expand
 
-        try dwarf_segment.addSection(allocator, "__debug_line", .{
-            .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
-            .size = file_size_hint,
-            .offset = @intCast(u32, off),
-            .@"align" = p_align,
-        });
-        self.load_commands_dirty = true;
-        self.debug_line_header_dirty = true;
-    }
+    log.debug("found {s},{s} section free space 0x{x} to 0x{x}", .{
+        commands.segmentName(sect),
+        commands.sectionName(sect),
+        off,
+        off + size,
+    });
+
+    sect.addr = seg.inner.vmaddr + off - seg.inner.fileoff;
+    sect.offset = @intCast(u32, off);
+
+    const index = @intCast(u16, seg.sections.items.len);
+    try seg.sections.append(self.base.base.allocator, sect);
+    seg.inner.cmdsize += @sizeOf(macho.section_64);
+    seg.inner.nsects += 1;
+
+    // TODO
+    // const match = MatchingSection{
+    //     .seg = segment_id,
+    //     .sect = index,
+    // };
+    // _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
+    // try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
+
+    self.load_commands_dirty = true;
+
+    return index;
 }
 
 pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Options) !void {
@@ -611,15 +593,18 @@ pub fn deinit(self: *DebugSymbols, allocator: *Allocator) void {
 }
 
 fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !SegmentCommand {
-    var cmd = SegmentCommand.empty("", .{
-        .cmdsize = base_cmd.inner.cmdsize,
-        .vmaddr = base_cmd.inner.vmaddr,
-        .vmsize = base_cmd.inner.vmsize,
-        .maxprot = base_cmd.inner.maxprot,
-        .initprot = base_cmd.inner.initprot,
-        .nsects = base_cmd.inner.nsects,
-        .flags = base_cmd.inner.flags,
-    });
+    var cmd = SegmentCommand{
+        .inner = .{
+            .segname = undefined,
+            .cmdsize = base_cmd.inner.cmdsize,
+            .vmaddr = base_cmd.inner.vmaddr,
+            .vmsize = base_cmd.inner.vmsize,
+            .maxprot = base_cmd.inner.maxprot,
+            .initprot = base_cmd.inner.initprot,
+            .nsects = base_cmd.inner.nsects,
+            .flags = base_cmd.inner.flags,
+        },
+    };
     mem.copy(u8, &cmd.inner.segname, &base_cmd.inner.segname);
 
     try cmd.sections.ensureCapacity(allocator, cmd.inner.nsects);
@@ -689,7 +674,7 @@ fn writeLoadCommands(self: *DebugSymbols, allocator: *Allocator) !void {
 }
 
 fn writeHeader(self: *DebugSymbols) !void {
-    var header = emptyHeader(.{
+    var header = commands.emptyHeader(.{
         .filetype = macho.MH_DSYM,
     });
 
src/link/MachO.zig
@@ -1133,20 +1133,19 @@ pub const MatchingSection = struct {
 };
 
 pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSection {
-    const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
     const segname = commands.segmentName(sect);
     const sectname = commands.sectionName(sect);
-
-    var needs_allocation = false;
     const res: ?MatchingSection = blk: {
         switch (commands.sectionType(sect)) {
             macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => {
                 if (self.text_const_section_index == null) {
-                    self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
-                    try text_seg.addSection(self.base.allocator, "__const", .{});
-                    needs_allocation = true;
+                    self.text_const_section_index = try self.allocateSection(
+                        self.text_segment_cmd_index.?,
+                        "__const",
+                        sect.size,
+                        sect.@"align",
+                        .{},
+                    );
                 }
 
                 break :blk .{
@@ -1159,11 +1158,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                     // TODO it seems the common values within the sections in objects are deduplicated/merged
                     // on merging the sections' contents.
                     if (self.objc_methname_section_index == null) {
-                        self.objc_methname_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.base.allocator, "__objc_methname", .{
-                            .flags = macho.S_CSTRING_LITERALS,
-                        });
-                        needs_allocation = true;
+                        self.objc_methname_section_index = try self.allocateSection(
+                            self.text_segment_cmd_index.?,
+                            "__objc_methname",
+                            sect.size,
+                            sect.@"align",
+                            .{},
+                        );
                     }
 
                     break :blk .{
@@ -1172,11 +1173,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                     };
                 } else if (mem.eql(u8, sectname, "__objc_methtype")) {
                     if (self.objc_methtype_section_index == null) {
-                        self.objc_methtype_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.base.allocator, "__objc_methtype", .{
-                            .flags = macho.S_CSTRING_LITERALS,
-                        });
-                        needs_allocation = true;
+                        self.objc_methtype_section_index = try self.allocateSection(
+                            self.text_segment_cmd_index.?,
+                            "__objc_methtype",
+                            sect.size,
+                            sect.@"align",
+                            .{},
+                        );
                     }
 
                     break :blk .{
@@ -1185,9 +1188,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                     };
                 } else if (mem.eql(u8, sectname, "__objc_classname")) {
                     if (self.objc_classname_section_index == null) {
-                        self.objc_classname_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.base.allocator, "__objc_classname", .{});
-                        needs_allocation = true;
+                        self.objc_classname_section_index = try self.allocateSection(
+                            self.text_segment_cmd_index.?,
+                            "__objc_classname",
+                            sect.size,
+                            sect.@"align",
+                            .{},
+                        );
                     }
 
                     break :blk .{
@@ -1197,11 +1204,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                 }
 
                 if (self.cstring_section_index == null) {
-                    self.cstring_section_index = @intCast(u16, text_seg.sections.items.len);
-                    try text_seg.addSection(self.base.allocator, "__cstring", .{
-                        .flags = macho.S_CSTRING_LITERALS,
-                    });
-                    needs_allocation = true;
+                    self.cstring_section_index = try self.allocateSection(
+                        self.text_segment_cmd_index.?,
+                        "__cstring",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_CSTRING_LITERALS,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1212,29 +1223,37 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             macho.S_LITERAL_POINTERS => {
                 if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__objc_selrefs")) {
                     if (self.objc_selrefs_section_index == null) {
-                        self.objc_selrefs_section_index = @intCast(u16, data_seg.sections.items.len);
-                        try data_seg.addSection(self.base.allocator, "__objc_selrefs", .{
-                            .flags = macho.S_LITERAL_POINTERS,
-                        });
-                        needs_allocation = true;
+                        self.objc_selrefs_section_index = try self.allocateSection(
+                            self.data_segment_cmd_index.?,
+                            "__objc_selrefs",
+                            sect.size,
+                            sect.@"align",
+                            .{
+                                .flags = macho.S_LITERAL_POINTERS,
+                            },
+                        );
                     }
 
                     break :blk .{
                         .seg = self.data_segment_cmd_index.?,
                         .sect = self.objc_selrefs_section_index.?,
                     };
+                } else {
+                    // TODO investigate
+                    break :blk null;
                 }
-
-                // TODO investigate
-                break :blk null;
             },
             macho.S_MOD_INIT_FUNC_POINTERS => {
                 if (self.mod_init_func_section_index == null) {
-                    self.mod_init_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.base.allocator, "__mod_init_func", .{
-                        .flags = macho.S_MOD_INIT_FUNC_POINTERS,
-                    });
-                    needs_allocation = true;
+                    self.mod_init_func_section_index = try self.allocateSection(
+                        self.data_const_segment_cmd_index.?,
+                        "__mod_init_func",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_MOD_INIT_FUNC_POINTERS,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1244,11 +1263,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             },
             macho.S_MOD_TERM_FUNC_POINTERS => {
                 if (self.mod_term_func_section_index == null) {
-                    self.mod_term_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.base.allocator, "__mod_term_func", .{
-                        .flags = macho.S_MOD_TERM_FUNC_POINTERS,
-                    });
-                    needs_allocation = true;
+                    self.mod_term_func_section_index = try self.allocateSection(
+                        self.data_const_segment_cmd_index.?,
+                        "__mod_term_func",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_MOD_TERM_FUNC_POINTERS,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1258,11 +1281,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             },
             macho.S_ZEROFILL => {
                 if (self.bss_section_index == null) {
-                    self.bss_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.base.allocator, "__bss", .{
-                        .flags = macho.S_ZEROFILL,
-                    });
-                    needs_allocation = true;
+                    self.bss_section_index = try self.allocateSection(
+                        self.data_segment_cmd_index.?,
+                        "__bss",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_ZEROFILL,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1272,11 +1299,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             },
             macho.S_THREAD_LOCAL_VARIABLES => {
                 if (self.tlv_section_index == null) {
-                    self.tlv_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.base.allocator, "__thread_vars", .{
-                        .flags = macho.S_THREAD_LOCAL_VARIABLES,
-                    });
-                    needs_allocation = true;
+                    self.tlv_section_index = try self.allocateSection(
+                        self.data_segment_cmd_index.?,
+                        "__thread_vars",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_THREAD_LOCAL_VARIABLES,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1286,11 +1317,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             },
             macho.S_THREAD_LOCAL_REGULAR => {
                 if (self.tlv_data_section_index == null) {
-                    self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.base.allocator, "__thread_data", .{
-                        .flags = macho.S_THREAD_LOCAL_REGULAR,
-                    });
-                    needs_allocation = true;
+                    self.tlv_data_section_index = try self.allocateSection(
+                        self.data_segment_cmd_index.?,
+                        "__thread_data",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_THREAD_LOCAL_REGULAR,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1300,11 +1335,15 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             },
             macho.S_THREAD_LOCAL_ZEROFILL => {
                 if (self.tlv_bss_section_index == null) {
-                    self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.base.allocator, "__thread_bss", .{
-                        .flags = macho.S_THREAD_LOCAL_ZEROFILL,
-                    });
-                    needs_allocation = true;
+                    self.tlv_bss_section_index = try self.allocateSection(
+                        self.data_segment_cmd_index.?,
+                        "__thread_bss",
+                        sect.size,
+                        sect.@"align",
+                        .{
+                            .flags = macho.S_THREAD_LOCAL_ZEROFILL,
+                        },
+                    );
                 }
 
                 break :blk .{
@@ -1317,9 +1356,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                     // TODO I believe __eh_frame is currently part of __unwind_info section
                     // in the latest ld64 output.
                     if (self.eh_frame_section_index == null) {
-                        self.eh_frame_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.base.allocator, "__eh_frame", .{});
-                        needs_allocation = true;
+                        self.eh_frame_section_index = try self.allocateSection(
+                            self.text_segment_cmd_index.?,
+                            "__eh_frame",
+                            sect.size,
+                            sect.@"align",
+                            .{},
+                        );
                     }
 
                     break :blk .{
@@ -1330,9 +1373,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
 
                 // TODO audit this: is this the right mapping?
                 if (self.data_const_section_index == null) {
-                    self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.base.allocator, "__const", .{});
-                    needs_allocation = true;
+                    self.data_const_section_index = try self.allocateSection(
+                        self.data_const_segment_cmd_index.?,
+                        "__const",
+                        sect.size,
+                        sect.@"align",
+                        .{},
+                    );
                 }
 
                 break :blk .{
@@ -1343,11 +1390,17 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             macho.S_REGULAR => {
                 if (commands.sectionIsCode(sect)) {
                     if (self.text_section_index == null) {
-                        self.text_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.base.allocator, "__text", .{
-                            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-                        });
-                        needs_allocation = true;
+                        self.text_section_index = try self.allocateSection(
+                            self.text_segment_cmd_index.?,
+                            "__text",
+                            sect.size,
+                            sect.@"align",
+                            .{
+                                .flags = macho.S_REGULAR |
+                                    macho.S_ATTR_PURE_INSTRUCTIONS |
+                                    macho.S_ATTR_SOME_INSTRUCTIONS,
+                            },
+                        );
                     }
 
                     break :blk .{
@@ -1368,9 +1421,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                 if (mem.eql(u8, segname, "__TEXT")) {
                     if (mem.eql(u8, sectname, "__ustring")) {
                         if (self.ustring_section_index == null) {
-                            self.ustring_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.base.allocator, "__ustring", .{});
-                            needs_allocation = true;
+                            self.ustring_section_index = try self.allocateSection(
+                                self.text_segment_cmd_index.?,
+                                "__ustring",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1379,9 +1436,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__gcc_except_tab")) {
                         if (self.gcc_except_tab_section_index == null) {
-                            self.gcc_except_tab_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.base.allocator, "__gcc_except_tab", .{});
-                            needs_allocation = true;
+                            self.gcc_except_tab_section_index = try self.allocateSection(
+                                self.text_segment_cmd_index.?,
+                                "__gcc_except_tab",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1390,9 +1451,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_methlist")) {
                         if (self.objc_methlist_section_index == null) {
-                            self.objc_methlist_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.base.allocator, "__objc_methlist", .{});
-                            needs_allocation = true;
+                            self.objc_methlist_section_index = try self.allocateSection(
+                                self.text_segment_cmd_index.?,
+                                "__objc_methlist",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1406,9 +1471,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         mem.eql(u8, sectname, "__gopclntab"))
                     {
                         if (self.data_const_section_index == null) {
-                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.base.allocator, "__const", .{});
-                            needs_allocation = true;
+                            self.data_const_section_index = try self.allocateSection(
+                                self.data_const_segment_cmd_index.?,
+                                "__const",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1417,9 +1486,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else {
                         if (self.text_const_section_index == null) {
-                            self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.base.allocator, "__const", .{});
-                            needs_allocation = true;
+                            self.text_const_section_index = try self.allocateSection(
+                                self.text_segment_cmd_index.?,
+                                "__const",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1431,9 +1504,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
 
                 if (mem.eql(u8, segname, "__DATA_CONST")) {
                     if (self.data_const_section_index == null) {
-                        self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                        try data_const_seg.addSection(self.base.allocator, "__const", .{});
-                        needs_allocation = true;
+                        self.data_const_section_index = try self.allocateSection(
+                            self.data_const_segment_cmd_index.?,
+                            "__const",
+                            sect.size,
+                            sect.@"align",
+                            .{},
+                        );
                     }
 
                     break :blk .{
@@ -1445,9 +1522,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                 if (mem.eql(u8, segname, "__DATA")) {
                     if (mem.eql(u8, sectname, "__const")) {
                         if (self.data_const_section_index == null) {
-                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.base.allocator, "__const", .{});
-                            needs_allocation = true;
+                            self.data_const_section_index = try self.allocateSection(
+                                self.data_const_segment_cmd_index.?,
+                                "__const",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1456,9 +1537,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__cfstring")) {
                         if (self.objc_cfstring_section_index == null) {
-                            self.objc_cfstring_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.base.allocator, "__cfstring", .{});
-                            needs_allocation = true;
+                            self.objc_cfstring_section_index = try self.allocateSection(
+                                self.data_const_segment_cmd_index.?,
+                                "__cfstring",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1467,9 +1552,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_classlist")) {
                         if (self.objc_classlist_section_index == null) {
-                            self.objc_classlist_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.base.allocator, "__objc_classlist", .{});
-                            needs_allocation = true;
+                            self.objc_classlist_section_index = try self.allocateSection(
+                                self.data_const_segment_cmd_index.?,
+                                "__objc_classlist",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1478,9 +1567,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_imageinfo")) {
                         if (self.objc_imageinfo_section_index == null) {
-                            self.objc_imageinfo_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.base.allocator, "__objc_imageinfo", .{});
-                            needs_allocation = true;
+                            self.objc_imageinfo_section_index = try self.allocateSection(
+                                self.data_const_segment_cmd_index.?,
+                                "__objc_imageinfo",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1489,9 +1582,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_const")) {
                         if (self.objc_const_section_index == null) {
-                            self.objc_const_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.base.allocator, "__objc_const", .{});
-                            needs_allocation = true;
+                            self.objc_const_section_index = try self.allocateSection(
+                                self.data_segment_cmd_index.?,
+                                "__objc_const",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1500,9 +1597,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_classrefs")) {
                         if (self.objc_classrefs_section_index == null) {
-                            self.objc_classrefs_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.base.allocator, "__objc_classrefs", .{});
-                            needs_allocation = true;
+                            self.objc_classrefs_section_index = try self.allocateSection(
+                                self.data_segment_cmd_index.?,
+                                "__objc_classrefs",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1511,9 +1612,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else if (mem.eql(u8, sectname, "__objc_data")) {
                         if (self.objc_data_section_index == null) {
-                            self.objc_data_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.base.allocator, "__objc_data", .{});
-                            needs_allocation = true;
+                            self.objc_data_section_index = try self.allocateSection(
+                                self.data_segment_cmd_index.?,
+                                "__objc_data",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1522,9 +1627,13 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
                         };
                     } else {
                         if (self.data_section_index == null) {
-                            self.data_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.base.allocator, "__data", .{});
-                            needs_allocation = true;
+                            self.data_section_index = try self.allocateSection(
+                                self.data_segment_cmd_index.?,
+                                "__data",
+                                sect.size,
+                                sect.@"align",
+                                .{},
+                            );
                         }
 
                         break :blk .{
@@ -1545,42 +1654,6 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
             else => break :blk null,
         }
     };
-
-    if (res) |match| {
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        _ = try self.block_free_lists.getOrPutValue(self.base.allocator, match, .{});
-
-        const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
-        if (!use_stage1) {
-            const target_seg = &self.load_commands.items[match.seg].Segment;
-            const target_sect = &target_seg.sections.items[match.sect];
-
-            // Update section's alignment
-            // TODO if sect.@"align" > target_sect.@"align", should we move the entire
-            // section to match the required alignment?
-            target_sect.@"align" = math.max(target_sect.@"align", sect.@"align");
-
-            if (needs_allocation) {
-                const alignment = try math.powi(u32, 2, target_sect.@"align");
-                const needed_size = sect.size;
-                const off = target_seg.findFreeSpace(needed_size, alignment, self.header_pad);
-                assert(off + needed_size <= target_seg.inner.fileoff + target_seg.inner.filesize); // TODO expand
-
-                log.debug("found {s},{s} section free space 0x{x} to 0x{x}", .{
-                    segname,
-                    sectname,
-                    off,
-                    off + needed_size,
-                });
-
-                target_sect.addr = target_seg.inner.vmaddr + off;
-                target_sect.size = needed_size;
-                target_sect.offset = @intCast(u32, off);
-                self.load_commands_dirty = true;
-            }
-        }
-    }
-
     return res;
 }
 
@@ -3878,9 +3951,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     if (self.pagezero_segment_cmd_index == null) {
         self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
         try self.load_commands.append(self.base.allocator, .{
-            .Segment = SegmentCommand.empty("__PAGEZERO", .{
-                .vmsize = 0x100000000, // size always set to 4GB
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__PAGEZERO"),
+                    .vmsize = 0x100000000, // size always set to 4GB
+                },
+            },
         });
         self.load_commands_dirty = true;
     }
@@ -3895,51 +3971,39 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         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("__TEXT", .{
-                .vmaddr = 0x100000000, // always starts at 4GB
-                .vmsize = needed_size,
-                .filesize = needed_size,
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__TEXT"),
+                    .vmaddr = 0x100000000, // always starts at 4GB
+                    .vmsize = needed_size,
+                    .filesize = needed_size,
+                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
+                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
+                },
+            },
         });
         self.load_commands_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: u2 = switch (self.base.options.target.cpu.arch) {
             .x86_64 => 0,
             .aarch64 => 2,
             else => unreachable, // unhandled architecture type
         };
         const needed_size = self.base.options.program_code_size_hint;
-        const off = text_segment.findFreeSpace(needed_size, @as(u16, 1) << alignment, self.header_pad);
-
-        log.debug("found __text section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try text_segment.addSection(self.base.allocator, "__text", .{
-            .addr = text_segment.inner.vmaddr + off,
-            .size = @intCast(u32, needed_size),
-            .offset = @intCast(u32, off),
-            .@"align" = alignment,
-            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-        });
-        const match = MatchingSection{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.text_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        self.text_section_index = try self.allocateSection(
+            self.text_segment_cmd_index.?,
+            "__text",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+            },
+        );
     }
 
     if (self.stubs_section_index == null) {
-        const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        self.stubs_section_index = @intCast(u16, text_segment.sections.items.len);
-
         const alignment: u2 = switch (self.base.options.target.cpu.arch) {
             .x86_64 => 0,
             .aarch64 => 2,
@@ -3951,32 +4015,19 @@ pub fn populateMissingMetadata(self: *MachO) !void {
             else => unreachable, // unhandled architecture type
         };
         const needed_size = stub_size * self.base.options.symbol_count_hint;
-        const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad);
-        assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment.
-
-        log.debug("found __stubs section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try text_segment.addSection(self.base.allocator, "__stubs", .{
-            .addr = text_segment.inner.vmaddr + off,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = alignment,
-            .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-            .reserved2 = stub_size,
-        });
-        const match = MatchingSection{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.stubs_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        self.stubs_section_index = try self.allocateSection(
+            self.text_segment_cmd_index.?,
+            "__stubs",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+                .reserved2 = stub_size,
+            },
+        );
     }
 
     if (self.stub_helper_section_index == null) {
-        const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        self.stub_helper_section_index = @intCast(u16, text_segment.sections.items.len);
-
         const alignment: u2 = switch (self.base.options.target.cpu.arch) {
             .x86_64 => 0,
             .aarch64 => 2,
@@ -3993,25 +4044,15 @@ pub fn populateMissingMetadata(self: *MachO) !void {
             else => unreachable,
         };
         const needed_size = stub_size * self.base.options.symbol_count_hint + preamble_size;
-        const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad);
-        assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment.
-
-        log.debug("found __stub_helper section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try text_segment.addSection(self.base.allocator, "__stub_helper", .{
-            .addr = text_segment.inner.vmaddr + off,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = alignment,
-            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-        });
-        const match = MatchingSection{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.stub_helper_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        self.stub_helper_section_index = try self.allocateSection(
+            self.text_segment_cmd_index.?,
+            "__stub_helper",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+            },
+        );
     }
 
     if (self.data_const_segment_cmd_index == null) {
@@ -4020,45 +4061,39 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         const ideal_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
         const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.page_size);
 
-        log.debug("found __DATA_CONST segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size });
+        log.debug("found __DATA_CONST segment free space 0x{x} to 0x{x}", .{
+            address_and_offset.offset,
+            address_and_offset.offset + needed_size,
+        });
 
         try self.load_commands.append(self.base.allocator, .{
-            .Segment = SegmentCommand.empty("__DATA_CONST", .{
-                .vmaddr = address_and_offset.address,
-                .vmsize = needed_size,
-                .fileoff = address_and_offset.offset,
-                .filesize = needed_size,
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__DATA_CONST"),
+                    .vmaddr = address_and_offset.address,
+                    .vmsize = needed_size,
+                    .fileoff = address_and_offset.offset,
+                    .filesize = needed_size,
+                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                },
+            },
         });
         self.load_commands_dirty = true;
     }
 
     if (self.got_section_index == null) {
-        const dc_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        self.got_section_index = @intCast(u16, dc_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = dc_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= dc_segment.inner.fileoff + dc_segment.inner.filesize); // TODO Must expand __DATA_CONST segment.
-
-        log.debug("found __got section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try dc_segment.addSection(self.base.allocator, "__got", .{
-            .addr = dc_segment.inner.vmaddr + off - dc_segment.inner.fileoff,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
-        });
-        const match = MatchingSection{
-            .seg = self.data_const_segment_cmd_index.?,
-            .sect = self.got_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.got_section_index = try self.allocateSection(
+            self.data_const_segment_cmd_index.?,
+            "__got",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
+            },
+        );
     }
 
     if (self.data_segment_cmd_index == null) {
@@ -4070,175 +4105,115 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         log.debug("found __DATA segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size });
 
         try self.load_commands.append(self.base.allocator, .{
-            .Segment = SegmentCommand.empty("__DATA", .{
-                .vmaddr = address_and_offset.address,
-                .vmsize = needed_size,
-                .fileoff = address_and_offset.offset,
-                .filesize = needed_size,
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__DATA"),
+                    .vmaddr = address_and_offset.address,
+                    .vmsize = needed_size,
+                    .fileoff = address_and_offset.offset,
+                    .filesize = needed_size,
+                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                },
+            },
         });
         self.load_commands_dirty = true;
     }
 
     if (self.la_symbol_ptr_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.la_symbol_ptr_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __la_symbol_ptr section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try data_segment.addSection(self.base.allocator, "__la_symbol_ptr", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_LAZY_SYMBOL_POINTERS,
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.la_symbol_ptr_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.la_symbol_ptr_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__la_symbol_ptr",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_LAZY_SYMBOL_POINTERS,
+            },
+        );
     }
 
     if (self.data_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.data_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __data section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try data_segment.addSection(self.base.allocator, "__data", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.data_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.data_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__data",
+            needed_size,
+            alignment,
+            .{},
+        );
     }
 
     if (self.tlv_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.tlv_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __thread_vars section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try data_segment.addSection(self.base.allocator, "__thread_vars", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_THREAD_LOCAL_VARIABLES,
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.tlv_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.tlv_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__thread_vars",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_THREAD_LOCAL_VARIABLES,
+            },
+        );
     }
 
     if (self.tlv_data_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.tlv_data_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __thread_data section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
-
-        try data_segment.addSection(self.base.allocator, "__thread_data", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = needed_size,
-            .offset = @intCast(u32, off),
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_THREAD_LOCAL_REGULAR,
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.tlv_data_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.tlv_data_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__thread_data",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_THREAD_LOCAL_REGULAR,
+            },
+        );
     }
 
     if (self.tlv_bss_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.tlv_bss_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __thread_bss section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.tlv_bss_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__thread_bss",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_THREAD_LOCAL_ZEROFILL,
+            },
+        );
 
         // We keep offset to the section in a separate variable as the actual section is usually pointing at the
         // beginning of the file.
-        self.tlv_bss_file_offset = off;
-        try data_segment.addSection(self.base.allocator, "__thread_bss", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = needed_size,
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_THREAD_LOCAL_ZEROFILL,
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.tlv_bss_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const out_sect = &seg.sections.items[self.tlv_bss_section_index.?];
+        self.tlv_bss_file_offset = out_sect.offset;
+        out_sect.offset = 0;
     }
 
     if (self.bss_section_index == null) {
-        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.bss_section_index = @intCast(u16, data_segment.sections.items.len);
-
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
-        assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
-
-        log.debug("found __bss section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
+        const alignment: u16 = 3; // 2^3 = @sizeOf(u64)
+        self.bss_section_index = try self.allocateSection(
+            self.data_segment_cmd_index.?,
+            "__bss",
+            needed_size,
+            alignment,
+            .{
+                .flags = macho.S_ZEROFILL,
+            },
+        );
 
         // We keep offset to the section in a separate variable as the actual section is usually pointing at the
         // beginning of the file.
-        self.bss_file_offset = off;
-        try data_segment.addSection(self.base.allocator, "__bss", .{
-            .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
-            .size = 0,
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_ZEROFILL,
-        });
-        const match = MatchingSection{
-            .seg = self.data_segment_cmd_index.?,
-            .sect = self.bss_section_index.?,
-        };
-        _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
-        try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
-        self.load_commands_dirty = true;
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const out_sect = &seg.sections.items[self.bss_section_index.?];
+        self.bss_file_offset = out_sect.offset;
+        out_sect.offset = 0;
     }
 
     if (self.linkedit_segment_cmd_index == null) {
@@ -4248,12 +4223,15 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         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("__LINKEDIT", .{
-                .vmaddr = address_and_offset.address,
-                .fileoff = address_and_offset.offset,
-                .maxprot = macho.VM_PROT_READ,
-                .initprot = macho.VM_PROT_READ,
-            }),
+            .Segment = .{
+                .inner = .{
+                    .segname = makeStaticString("__LINKEDIT"),
+                    .vmaddr = address_and_offset.address,
+                    .fileoff = address_and_offset.offset,
+                    .maxprot = macho.VM_PROT_READ,
+                    .initprot = macho.VM_PROT_READ,
+                },
+            },
         });
         self.load_commands_dirty = true;
     }
@@ -4485,6 +4463,67 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     }
 }
 
+const AllocateSectionOpts = struct {
+    flags: u32 = macho.S_REGULAR,
+    reserved1: u32 = 0,
+    reserved2: u32 = 0,
+};
+
+fn allocateSection(
+    self: *MachO,
+    segment_id: u16,
+    sectname: []const u8,
+    size: u64,
+    alignment: u32,
+    opts: AllocateSectionOpts,
+) !u16 {
+    const seg = &self.load_commands.items[segment_id].Segment;
+    var sect = macho.section_64{
+        .sectname = makeStaticString(sectname),
+        .segname = seg.inner.segname,
+        .size = @intCast(u32, size),
+        .@"align" = alignment,
+        .flags = opts.flags,
+        .reserved1 = opts.reserved1,
+        .reserved2 = opts.reserved2,
+    };
+
+    const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
+    if (!use_stage1) {
+        const alignment_pow_2 = try math.powi(u32, 2, alignment);
+        const padding: ?u64 = if (segment_id == self.text_segment_cmd_index.?) self.header_pad else null;
+        const off = seg.findFreeSpace(size, alignment_pow_2, padding);
+
+        assert(off + size <= seg.inner.fileoff + seg.inner.filesize); // TODO expand
+
+        log.debug("found {s},{s} section free space 0x{x} to 0x{x}", .{
+            commands.segmentName(sect),
+            commands.sectionName(sect),
+            off,
+            off + size,
+        });
+
+        sect.addr = seg.inner.vmaddr + off - seg.inner.fileoff;
+        sect.offset = @intCast(u32, off);
+    }
+
+    const index = @intCast(u16, seg.sections.items.len);
+    try seg.sections.append(self.base.allocator, sect);
+    seg.inner.cmdsize += @sizeOf(macho.section_64);
+    seg.inner.nsects += 1;
+
+    const match = MatchingSection{
+        .seg = segment_id,
+        .sect = index,
+    };
+    _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
+    try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
+
+    self.load_commands_dirty = true;
+
+    return index;
+}
+
 fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
     const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
     const text_section = &text_segment.sections.items[self.text_section_index.?];
@@ -5513,6 +5552,13 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
         std.math.maxInt(@TypeOf(actual_size));
 }
 
+pub fn makeStaticString(bytes: []const u8) [16]u8 {
+    var buf = [_]u8{0} ** 16;
+    assert(bytes.len <= buf.len);
+    mem.copy(u8, &buf, bytes);
+    return buf;
+}
+
 pub fn makeString(self: *MachO, string: []const u8) !u32 {
     if (self.strtab_dir.getAdapted(@as([]const u8, string), StringSliceAdapter{ .strtab = &self.strtab })) |off| {
         log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off });