Commit b86d0e488b

Jakub Konka <kubkon@jakubkonka.com>
2021-01-09 19:29:33
macho: refactor writing and managing externs
1 parent 21c7217
Changed files (3)
src/link/MachO/imports.zig
@@ -6,365 +6,177 @@ const mem = std.mem;
 const assert = std.debug.assert;
 const Allocator = mem.Allocator;
 
-pub const RebaseInfoTable = struct {
-    rebase_type: u8 = macho.REBASE_TYPE_POINTER,
-    symbols: std.ArrayListUnmanaged(Symbol) = .{},
+pub const ExternSymbol = struct {
+    /// Symbol name.
+    /// We own the memory, therefore we'll need to free it by calling `deinit`.
+    /// In self-hosted, we don't expect it to be null ever.
+    /// However, this is for backwards compatibility with LLD when
+    /// we'll be patching things up post mortem.
+    name: ?[]u8 = null,
 
-    pub const Symbol = struct {
-        segment: u8,
-        offset: i64,
-    };
-
-    pub fn deinit(self: *RebaseInfoTable, allocator: *Allocator) void {
-        self.symbols.deinit(allocator);
-    }
-
-    /// Write the rebase info table to byte stream.
-    pub fn write(self: RebaseInfoTable, writer: anytype) !void {
-        for (self.symbols.items) |symbol| {
-            try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type));
-            try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
-            try leb.writeILEB128(writer, symbol.offset);
-            try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1));
-        }
-
-        try writer.writeByte(macho.REBASE_OPCODE_DONE);
-    }
+    /// Id of the dynamic library where the specified entries can be found.
+    /// Id of 0 means self.
+    /// TODO this should really be an id into the table of all defined
+    /// dylibs.
+    dylib_ordinal: i64 = 0,
 
-    /// Calculate size in bytes of this rebase info table.
-    pub fn calcSize(self: *RebaseInfoTable) !u64 {
-        var stream = std.io.countingWriter(std.io.null_writer);
-        var writer = stream.writer();
-        var size: u64 = 0;
+    segment: u16 = 0,
+    offset: u32 = 0,
+    addend: ?i32 = null,
+    index: u32,
 
-        for (self.symbols.items) |symbol| {
-            size += 2;
-            try leb.writeILEB128(writer, symbol.offset);
-            size += 1;
+    pub fn deinit(self: *ExternSymbol, allocator: *Allocator) void {
+        if (self.name) |*name| {
+            allocator.free(name);
         }
-
-        size += 1 + stream.bytes_written;
-        return size;
     }
 };
 
-/// Table of binding info entries used to tell the dyld which
-/// symbols to bind at loading time.
-pub const BindingInfoTable = struct {
-    /// Id of the dynamic library where the specified entries can be found.
-    dylib_ordinal: i64 = 0,
-
-    /// Binding type; defaults to pointer type.
-    binding_type: u8 = macho.BIND_TYPE_POINTER,
-
-    symbols: std.ArrayListUnmanaged(Symbol) = .{},
+pub fn rebaseInfoSize(symbols: []*const ExternSymbol) !u64 {
+    var stream = std.io.countingWriter(std.io.null_writer);
+    var writer = stream.writer();
+    var size: u64 = 0;
 
-    pub const Symbol = struct {
-        /// Symbol name.
-        name: ?[]u8 = null,
+    for (symbols) |symbol| {
+        size += 2;
+        try leb.writeILEB128(writer, symbol.offset);
+        size += 1;
+    }
 
-        /// Id of the segment where to bind this symbol to.
-        segment: u8,
+    size += 1 + stream.bytes_written;
+    return size;
+}
 
-        /// Offset of this symbol wrt to the segment id encoded in `segment`.
-        offset: i64,
+pub fn writeRebaseInfo(symbols: []*const ExternSymbol, writer: anytype) !void {
+    for (symbols) |symbol| {
+        try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER));
+        try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
+        try leb.writeILEB128(writer, symbol.offset);
+        try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1));
+    }
+    try writer.writeByte(macho.REBASE_OPCODE_DONE);
+}
 
-        /// Addend value (if any).
-        addend: ?i64 = null,
-    };
+pub fn bindInfoSize(symbols: []*const ExternSymbol) !u64 {
+    var stream = std.io.countingWriter(std.io.null_writer);
+    var writer = stream.writer();
+    var size: u64 = 0;
 
-    pub fn deinit(self: *BindingInfoTable, allocator: *Allocator) void {
-        for (self.symbols.items) |*symbol| {
-            if (symbol.name) |name| {
-                allocator.free(name);
-            }
+    for (symbols) |symbol| {
+        size += 1;
+        if (symbol.dylib_ordinal > 15) {
+            try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
         }
-        self.symbols.deinit(allocator);
-    }
+        size += 1;
 
-    /// Parse the binding info table from byte stream.
-    pub fn read(self: *BindingInfoTable, reader: anytype, allocator: *Allocator) !void {
-        var symbol: Symbol = .{
-            .segment = 0,
-            .offset = 0,
-        };
+        if (symbol.name) |name| {
+            size += 1;
+            size += name.len;
+            size += 1;
+        }
 
-        var dylib_ordinal_set = false;
-        var done = false;
-        while (true) {
-            const inst = reader.readByte() catch |err| switch (err) {
-                error.EndOfStream => break,
-                else => return err,
-            };
-            const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK;
-            const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
+        size += 1;
+        try leb.writeILEB128(writer, symbol.offset);
 
-            switch (opcode) {
-                macho.BIND_OPCODE_DO_BIND => {
-                    try self.symbols.append(allocator, symbol);
-                    symbol = .{
-                        .segment = 0,
-                        .offset = 0,
-                    };
-                },
-                macho.BIND_OPCODE_DONE => {
-                    done = true;
-                    break;
-                },
-                macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
-                    var name = std.ArrayList(u8).init(allocator);
-                    var next = try reader.readByte();
-                    while (next != @as(u8, 0)) {
-                        try name.append(next);
-                        next = try reader.readByte();
-                    }
-                    symbol.name = name.toOwnedSlice();
-                },
-                macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
-                    symbol.segment = imm;
-                    symbol.offset = try leb.readILEB128(i64, reader);
-                },
-                macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
-                    assert(!dylib_ordinal_set);
-                    self.dylib_ordinal = imm;
-                },
-                macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
-                    assert(!dylib_ordinal_set);
-                    self.dylib_ordinal = try leb.readILEB128(i64, reader);
-                },
-                macho.BIND_OPCODE_SET_TYPE_IMM => {
-                    self.binding_type = imm;
-                },
-                macho.BIND_OPCODE_SET_ADDEND_SLEB => {
-                    symbol.addend = try leb.readILEB128(i64, reader);
-                },
-                else => {
-                    std.log.warn("unhandled BIND_OPCODE_: 0x{x}", .{opcode});
-                },
-            }
+        if (symbol.addend) |addend| {
+            size += 1;
+            try leb.writeILEB128(writer, addend);
         }
-        assert(done);
+
+        size += 2;
     }
 
-    /// Write the binding info table to byte stream.
-    pub fn write(self: BindingInfoTable, writer: anytype) !void {
-        if (self.dylib_ordinal > 15) {
+    size += stream.bytes_written;
+    return size;
+}
+
+pub fn writeBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void {
+    for (symbols) |symbol| {
+        if (symbol.dylib_ordinal > 15) {
             try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
-            try leb.writeULEB128(writer, @bitCast(u64, self.dylib_ordinal));
-        } else if (self.dylib_ordinal > 0) {
-            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, self.dylib_ordinal)));
+            try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
+        } else if (symbol.dylib_ordinal > 0) {
+            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
         } else {
-            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, self.dylib_ordinal)));
+            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
         }
-        try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, self.binding_type));
-
-        for (self.symbols.items) |symbol| {
-            if (symbol.name) |name| {
-                try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags.
-                try writer.writeAll(name);
-                try writer.writeByte(0);
-            }
+        try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER));
 
-            try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
-            try leb.writeILEB128(writer, symbol.offset);
+        if (symbol.name) |name| {
+            try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags.
+            try writer.writeAll(name);
+            try writer.writeByte(0);
+        }
 
-            if (symbol.addend) |addend| {
-                try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB);
-                try leb.writeILEB128(writer, addend);
-            }
+        try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
+        try leb.writeILEB128(writer, symbol.offset);
 
-            try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
+        if (symbol.addend) |addend| {
+            try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB);
+            try leb.writeILEB128(writer, addend);
         }
 
+        try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
         try writer.writeByte(macho.BIND_OPCODE_DONE);
     }
+}
 
-    /// Calculate size in bytes of this binding info table.
-    pub fn calcSize(self: *BindingInfoTable) !u64 {
-        var stream = std.io.countingWriter(std.io.null_writer);
-        var writer = stream.writer();
-        var size: u64 = 1;
-
-        if (self.dylib_ordinal > 15) {
-            try leb.writeULEB128(writer, @bitCast(u64, self.dylib_ordinal));
-        }
+pub fn lazyBindInfoSize(symbols: []*const ExternSymbol) !u64 {
+    var stream = std.io.countingWriter(std.io.null_writer);
+    var writer = stream.writer();
+    var size: u64 = 0;
 
+    for (symbols) |symbol| {
         size += 1;
+        try leb.writeILEB128(writer, symbol.offset);
 
-        for (self.symbols.items) |symbol| {
-            if (symbol.name) |name| {
-                size += 1;
-                size += name.len;
-                size += 1;
-            }
-
-            size += 1;
-            try leb.writeILEB128(writer, symbol.offset);
-
-            if (symbol.addend) |addend| {
-                size += 1;
-                try leb.writeILEB128(writer, addend);
-            }
-
+        if (symbol.addend) |addend| {
             size += 1;
+            try leb.writeILEB128(writer, addend);
         }
 
-        size += 1 + stream.bytes_written;
-        return size;
-    }
-};
-
-/// Table of lazy binding info entries used to tell the dyld which
-/// symbols to lazily bind at first load of a dylib.
-pub const LazyBindingInfoTable = struct {
-    symbols: std.ArrayListUnmanaged(Symbol) = .{},
-
-    pub const Symbol = struct {
-        /// Symbol name.
-        name: ?[]u8 = null,
-
-        /// Offset of this symbol wrt to the segment id encoded in `segment`.
-        offset: i64,
-
-        /// Id of the dylib where this symbol is expected to reside.
-        /// Positive ordinals point at dylibs imported with LC_LOAD_DYLIB,
-        /// 0 means this binary, -1 the main executable, and -2 flat lookup.
-        dylib_ordinal: i64,
-
-        /// Id of the segment where to bind this symbol to.
-        segment: u8,
-
-        /// Addend value (if any).
-        addend: ?i64 = null,
-    };
-
-    pub fn deinit(self: *LazyBindingInfoTable, allocator: *Allocator) void {
-        for (self.symbols.items) |*symbol| {
-            if (symbol.name) |name| {
-                allocator.free(name);
-            }
+        size += 1;
+        if (symbol.dylib_ordinal > 15) {
+            try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
         }
-        self.symbols.deinit(allocator);
-    }
-
-    /// Parse the binding info table from byte stream.
-    pub fn read(self: *LazyBindingInfoTable, reader: anytype, allocator: *Allocator) !void {
-        var symbol: Symbol = .{
-            .offset = 0,
-            .segment = 0,
-            .dylib_ordinal = 0,
-        };
-
-        var done = false;
-        while (true) {
-            const inst = reader.readByte() catch |err| switch (err) {
-                error.EndOfStream => break,
-                else => return err,
-            };
-            const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK;
-            const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
-
-            switch (opcode) {
-                macho.BIND_OPCODE_DO_BIND => {
-                    try self.symbols.append(allocator, symbol);
-                },
-                macho.BIND_OPCODE_DONE => {
-                    done = true;
-                    symbol = .{
-                        .offset = 0,
-                        .segment = 0,
-                        .dylib_ordinal = 0,
-                    };
-                },
-                macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
-                    var name = std.ArrayList(u8).init(allocator);
-                    var next = try reader.readByte();
-                    while (next != @as(u8, 0)) {
-                        try name.append(next);
-                        next = try reader.readByte();
-                    }
-                    symbol.name = name.toOwnedSlice();
-                },
-                macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
-                    symbol.segment = imm;
-                    symbol.offset = try leb.readILEB128(i64, reader);
-                },
-                macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
-                    symbol.dylib_ordinal = imm;
-                },
-                macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
-                    symbol.dylib_ordinal = try leb.readILEB128(i64, reader);
-                },
-                macho.BIND_OPCODE_SET_ADDEND_SLEB => {
-                    symbol.addend = try leb.readILEB128(i64, reader);
-                },
-                else => {
-                    std.log.warn("unhandled BIND_OPCODE_: 0x{x}", .{opcode});
-                },
-            }
+        if (symbol.name) |name| {
+            size += 1;
+            size += name.len;
+            size += 1;
         }
-        assert(done);
+        size += 2;
     }
 
-    /// Write the binding info table to byte stream.
-    pub fn write(self: LazyBindingInfoTable, writer: anytype) !void {
-        for (self.symbols.items) |symbol| {
-            try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
-            try leb.writeILEB128(writer, symbol.offset);
-
-            if (symbol.addend) |addend| {
-                try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB);
-                try leb.writeILEB128(writer, addend);
-            }
+    size += stream.bytes_written;
+    return size;
+}
 
-            if (symbol.dylib_ordinal > 15) {
-                try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
-                try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
-            } else if (symbol.dylib_ordinal > 0) {
-                try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
-            } else {
-                try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
-            }
+pub fn writeLazyBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void {
+    for (symbols) |symbol| {
+        try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
+        try leb.writeILEB128(writer, symbol.offset);
 
-            if (symbol.name) |name| {
-                try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags.
-                try writer.writeAll(name);
-                try writer.writeByte(0);
-            }
-
-            try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
-            try writer.writeByte(macho.BIND_OPCODE_DONE);
+        if (symbol.addend) |addend| {
+            try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB);
+            try leb.writeILEB128(writer, addend);
         }
-    }
 
-    /// Calculate size in bytes of this binding info table.
-    pub fn calcSize(self: *LazyBindingInfoTable) !u64 {
-        var stream = std.io.countingWriter(std.io.null_writer);
-        var writer = stream.writer();
-        var size: u64 = 0;
-
-        for (self.symbols.items) |symbol| {
-            size += 1;
-            try leb.writeILEB128(writer, symbol.offset);
-
-            if (symbol.addend) |addend| {
-                size += 1;
-                try leb.writeILEB128(writer, addend);
-            }
+        if (symbol.dylib_ordinal > 15) {
+            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
+            try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
+        } else if (symbol.dylib_ordinal > 0) {
+            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
+        } else {
+            try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal)));
+        }
 
-            size += 1;
-            if (symbol.dylib_ordinal > 15) {
-                try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal));
-            }
-            if (symbol.name) |name| {
-                size += 1;
-                size += name.len;
-                size += 1;
-            }
-            size += 2;
+        if (symbol.name) |name| {
+            try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags.
+            try writer.writeAll(name);
+            try writer.writeByte(0);
         }
 
-        size += stream.bytes_written;
-        return size;
+        try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
+        try writer.writeByte(macho.BIND_OPCODE_DONE);
     }
-};
+}
src/link/MachO.zig
@@ -105,16 +105,17 @@ entry_addr: ?u64 = null,
 /// Table of all local symbols
 /// Internally references string table for names (which are optional).
 local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-/// Table of all defined global symbols
+/// Table of all global symbols
 global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-/// Table of all undefined symbols
-undef_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+/// Table of all extern nonlazy symbols, indexed by name.
+extern_nonlazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{},
+/// Table of all extern lazy symbols, indexed by name.
+extern_lazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{},
 
 local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{},
 global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{},
 offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
 
-dyld_stub_binder_index: ?u16 = null,
 stub_helper_stubs_start_off: ?u64 = null,
 
 /// Table of symbol names aka the string table.
@@ -123,13 +124,6 @@ string_table: std.ArrayListUnmanaged(u8) = .{},
 /// Table of trampolines to the actual symbols in __text section.
 offset_table: std.ArrayListUnmanaged(u64) = .{},
 
-/// Table of rebase info entries.
-rebase_info_table: RebaseInfoTable = .{},
-/// Table of binding info entries.
-binding_info_table: BindingInfoTable = .{},
-/// Table of lazy binding info entries.
-lazy_binding_info_table: LazyBindingInfoTable = .{},
-
 error_flags: File.ErrorFlags = File.ErrorFlags{},
 
 offset_table_count_dirty: bool = false,
@@ -167,11 +161,10 @@ last_text_block: ?*TextBlock = null,
 pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{},
 
 stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{},
-externs: std.StringHashMapUnmanaged(u32) = .{},
 
 pub const StubFixup = struct {
     symbol: u32,
-    exists: bool,
+    already_defined: bool,
     start: usize,
     len: usize,
 };
@@ -920,42 +913,42 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                     return error.NoSymbolTableFound;
                 }
 
-                // Parse dyld info
-                try self.parseBindingInfoTable();
-                try self.parseLazyBindingInfoTable();
+                // // Parse dyld info
+                // try self.parseBindingInfoTable();
+                // try self.parseLazyBindingInfoTable();
 
-                // Update the dylib ordinals.
-                self.binding_info_table.dylib_ordinal = next_ordinal;
-                for (self.lazy_binding_info_table.symbols.items) |*symbol| {
-                    symbol.dylib_ordinal = next_ordinal;
-                }
+                // // Update the dylib ordinals.
+                // self.binding_info_table.dylib_ordinal = next_ordinal;
+                // for (self.lazy_binding_info_table.symbols.items) |*symbol| {
+                //     symbol.dylib_ordinal = next_ordinal;
+                // }
 
-                // Write updated dyld info.
-                const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-                {
-                    const size = try self.binding_info_table.calcSize();
-                    assert(dyld_info.bind_size >= size);
+                // // Write updated dyld info.
+                // const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
+                // {
+                //     const size = try self.binding_info_table.calcSize();
+                //     assert(dyld_info.bind_size >= size);
 
-                    var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
-                    defer self.base.allocator.free(buffer);
+                //     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
+                //     defer self.base.allocator.free(buffer);
 
-                    var stream = std.io.fixedBufferStream(buffer);
-                    try self.binding_info_table.write(stream.writer());
+                //     var stream = std.io.fixedBufferStream(buffer);
+                //     try self.binding_info_table.write(stream.writer());
 
-                    try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off);
-                }
-                {
-                    const size = try self.lazy_binding_info_table.calcSize();
-                    assert(dyld_info.lazy_bind_size >= size);
+                //     try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off);
+                // }
+                // {
+                //     const size = try self.lazy_binding_info_table.calcSize();
+                //     assert(dyld_info.lazy_bind_size >= size);
 
-                    var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
-                    defer self.base.allocator.free(buffer);
+                //     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
+                //     defer self.base.allocator.free(buffer);
 
-                    var stream = std.io.fixedBufferStream(buffer);
-                    try self.lazy_binding_info_table.write(stream.writer());
+                //     var stream = std.io.fixedBufferStream(buffer);
+                //     try self.lazy_binding_info_table.write(stream.writer());
 
-                    try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off);
-                }
+                //     try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off);
+                // }
 
                 // Write updated load commands and the header
                 try self.writeLoadCommands();
@@ -1037,14 +1030,13 @@ pub fn deinit(self: *MachO) void {
     if (self.d_sym) |*ds| {
         ds.deinit(self.base.allocator);
     }
-    self.binding_info_table.deinit(self.base.allocator);
-    self.lazy_binding_info_table.deinit(self.base.allocator);
     self.pie_fixups.deinit(self.base.allocator);
     self.text_block_free_list.deinit(self.base.allocator);
     self.offset_table.deinit(self.base.allocator);
     self.offset_table_free_list.deinit(self.base.allocator);
     self.string_table.deinit(self.base.allocator);
-    self.undef_symbols.deinit(self.base.allocator);
+    self.extern_lazy_symbols.deinit(self.base.allocator);
+    self.extern_nonlazy_symbols.deinit(self.base.allocator);
     self.global_symbols.deinit(self.base.allocator);
     self.global_symbol_free_list.deinit(self.base.allocator);
     self.local_symbols.deinit(self.base.allocator);
@@ -1261,59 +1253,28 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     }
 
     // Resolve stubs (if any)
-    const stubs = &text_segment.sections.items[self.stubs_section_index.?];
-    const stub_h = &text_segment.sections.items[self.stub_helper_section_index.?];
-    const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const la_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?];
+    const stubs = text_segment.sections.items[self.stubs_section_index.?];
     for (self.stub_fixups.items) |fixup| {
-        // TODO increment offset for stub writing
         const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2;
         const text_addr = symbol.n_value + fixup.start;
         const displacement = @intCast(u32, stub_addr - text_addr);
         var placeholder = code_buffer.items[fixup.start..][0..fixup.len];
-        mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32());
-
-        if (!fixup.exists) {
-            const stub_off = self.stub_helper_stubs_start_off.? + fixup.symbol * 3 * @sizeOf(u32);
-            const end = stub_h.addr + stub_off - stub_h.offset;
-            var buf: [@sizeOf(u64)]u8 = undefined;
-            mem.writeIntLittle(u64, &buf, end);
-            try self.base.file.?.pwriteAll(&buf, la_ptr.offset + fixup.symbol * @sizeOf(u64));
-
-            const la_ptr_addr = la_ptr.addr + fixup.symbol * @sizeOf(u64);
-            const displacement2 = la_ptr_addr - stub_addr;
-            var ccode: [2 * @sizeOf(u32)]u8 = undefined;
-            mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{
-                .literal = @intCast(u19, displacement2 / 4),
-            }).toU32());
-            mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32());
-            try self.base.file.?.pwriteAll(&ccode, stubs.offset + fixup.symbol * stubs.reserved2);
-
-            const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4);
-            var cccode: [3 * @sizeOf(u32)]u8 = undefined;
-            mem.writeIntLittle(u32, cccode[0..4], aarch64.Instruction.ldr(.w16, .{
-                .literal = 0x2,
-            }).toU32());
-            mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32());
-            mem.writeIntLittle(u32, cccode[8..12], fixup.symbol * 0xd);
-            try self.base.file.?.pwriteAll(&cccode, stub_off);
-
-            try self.rebase_info_table.symbols.append(self.base.allocator, .{
-                .segment = 3,
-                .offset = fixup.symbol * stubs.reserved2,
-            });
+        switch (self.base.options.target.cpu.arch) {
+            .x86_64 => return error.TODOImplementStubFixupsForx86_64,
+            .aarch64 => {
+                mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32());
+            },
+            else => unreachable,
+        }
+        if (!fixup.already_defined) {
+            try self.writeStub(fixup.symbol);
+            try self.writeStubInStubHelper(fixup.symbol);
+            try self.writeLazySymbolPointer(fixup.symbol);
+
+            const extern_sym = &self.extern_lazy_symbols.items()[fixup.symbol].value;
+            extern_sym.segment = self.data_segment_cmd_index.?;
+            extern_sym.offset = fixup.symbol * @sizeOf(u64);
             self.rebase_info_dirty = true;
-
-            const sym = self.undef_symbols.items[fixup.symbol + 1];
-            const name_str = self.getString(sym.n_strx);
-            var name = try self.base.allocator.alloc(u8, name_str.len);
-            mem.copy(u8, name, name_str);
-            try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{
-                .segment = 3,
-                .offset = fixup.symbol * @sizeOf(u64),
-                .dylib_ordinal = 1,
-                .name = name,
-            });
             self.lazy_binding_info_dirty = true;
         }
     }
@@ -2080,51 +2041,51 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         self.header_dirty = true;
         self.load_commands_dirty = true;
     }
-    if (self.dyld_stub_binder_index == null) {
-        self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len);
-        const name = try self.makeString("dyld_stub_binder");
-        try self.undef_symbols.append(self.base.allocator, .{
-            .n_strx = name,
-            .n_type = macho.N_UNDF | macho.N_EXT,
-            .n_sect = 0,
-            .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER,
-            .n_value = 0,
-        });
-
-        self.binding_info_table.dylib_ordinal = 1;
-        const nn = self.getString(name);
-        var n = try self.base.allocator.alloc(u8, nn.len);
-        mem.copy(u8, n, nn);
-        try self.binding_info_table.symbols.append(self.base.allocator, .{
-            .name = n,
-            .segment = 2,
-            .offset = 0,
+    if (!self.extern_nonlazy_symbols.contains("dyld_stub_binder")) {
+        const index = @intCast(u32, self.extern_nonlazy_symbols.items().len);
+        const name = try std.fmt.allocPrint(self.base.allocator, "dyld_stub_binder", .{});
+        try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{
+            .name = name,
+            .dylib_ordinal = 1, // TODO this is currently hardcoded.
+            .index = index,
+            .segment = self.data_const_segment_cmd_index.?,
+            .offset = index * @sizeOf(u64),
         });
         self.binding_info_dirty = true;
     }
     if (self.stub_helper_stubs_start_off == null) {
-        const text = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        const sh = &text.sections.items[self.stub_helper_section_index.?];
-        const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const data_data = &data.sections.items[self.data_section_index.?];
-        const displacement = data_data.addr - sh.addr;
-        var code: [4 * @sizeOf(u32)]u8 = undefined;
-        mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, @intCast(i21, displacement)).toU32());
-        mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp(
-            .x16,
-            .x17,
-            aarch64.Register.sp,
-            aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
-        ).toU32());
-        const dc = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        const got = &dc.sections.items[self.data_got_section_index.?];
-        const displacement2 = got.addr - sh.addr - 2 * @sizeOf(u32);
-        mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{
-            .literal = @intCast(u19, displacement2 / 4),
-        }).toU32());
-        mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32());
-        self.stub_helper_stubs_start_off = sh.offset + 4 * @sizeOf(u32);
-        try self.base.file.?.pwriteAll(&code, sh.offset);
+        const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+        const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?];
+        const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const data = &data_segment.sections.items[self.data_section_index.?];
+        const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+        const got = &data_const_segment.sections.items[self.data_got_section_index.?];
+        switch (self.base.options.target.cpu.arch) {
+            .x86_64 => return error.TODOImplementStubHelperForX86_64,
+            .aarch64 => {
+                var code: [4 * @sizeOf(u32)]u8 = undefined;
+                {
+                    const displacement = data.addr - stub_helper.addr;
+                    mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, @intCast(i21, displacement)).toU32());
+                }
+                mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp(
+                    .x16,
+                    .x17,
+                    aarch64.Register.sp,
+                    aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
+                ).toU32());
+                {
+                    const displacement = got.addr - stub_helper.addr - 2 * @sizeOf(u32);
+                    mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{
+                        .literal = @intCast(u19, displacement / 4),
+                    }).toU32());
+                }
+                mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32());
+                self.stub_helper_stubs_start_off = stub_helper.offset + 4 * @sizeOf(u32);
+                try self.base.file.?.pwriteAll(&code, stub_helper.offset);
+            },
+            else => unreachable,
+        }
     }
 }
 
@@ -2460,11 +2421,73 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void {
     try self.base.file.?.pwriteAll(&code, off);
 }
 
+fn writeLazySymbolPointer(self: *MachO, index: u32) !void {
+    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
+    const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
+
+    const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32);
+    const end = stub_helper.addr + stub_off - stub_helper.offset;
+    var buf: [@sizeOf(u64)]u8 = undefined;
+    mem.writeIntLittle(u64, &buf, end);
+    const off = la_symbol_ptr.offset + index * @sizeOf(u64);
+    log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off });
+    try self.base.file.?.pwriteAll(&buf, off);
+}
+
+fn writeStub(self: *MachO, index: u32) !void {
+    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stubs = text_segment.sections.items[self.stubs_section_index.?];
+    const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
+
+    const stub_off = stubs.offset + index * stubs.reserved2;
+    const stub_addr = stubs.addr + index * stubs.reserved2;
+    const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64);
+    const displacement = la_ptr_addr - stub_addr;
+    log.debug("writing stub at 0x{x}", .{stub_off});
+    switch (self.base.options.target.cpu.arch) {
+        .x86_64 => return error.TODOImplementWritingStubsForx86_64,
+        .aarch64 => {
+            var code: [2 * @sizeOf(u32)]u8 = undefined;
+            mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{
+                .literal = @intCast(u19, displacement / 4),
+            }).toU32());
+            mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32());
+            try self.base.file.?.pwriteAll(&code, stub_off);
+        },
+        else => unreachable,
+    }
+}
+
+fn writeStubInStubHelper(self: *MachO, index: u32) !void {
+    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
+
+    const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32);
+    const end = stub_helper.addr + stub_off - stub_helper.offset;
+    const displacement = @intCast(i64, stub_helper.addr) - @intCast(i64, end + 4);
+    switch (self.base.options.target.cpu.arch) {
+        .x86_64 => return error.TODOImplementWritingStubsInStubHelperForx86_64,
+        .aarch64 => {
+            var code: [3 * @sizeOf(u32)]u8 = undefined;
+            mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{
+                .literal = 0x2,
+            }).toU32());
+            mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(@intCast(i28, displacement)).toU32());
+            mem.writeIntLittle(u32, code[8..12], index * 0xd); // TODO This is the size of lazy binding opcode block.
+            try self.base.file.?.pwriteAll(&code, stub_off);
+        },
+        else => unreachable,
+    }
+}
+
 fn relocateSymbolTable(self: *MachO) !void {
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
     const nlocals = self.local_symbols.items.len;
     const nglobals = self.global_symbols.items.len;
-    const nundefs = self.undef_symbols.items.len;
+    const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len;
     const nsyms = nlocals + nglobals + nundefs;
 
     if (symtab.nsyms < nsyms) {
@@ -2509,7 +2532,31 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void {
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
     const nlocals = self.local_symbols.items.len;
     const nglobals = self.global_symbols.items.len;
-    const nundefs = self.undef_symbols.items.len;
+
+    const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len;
+    var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator);
+    defer undefs.deinit();
+    try undefs.ensureCapacity(nundefs);
+    for (self.extern_lazy_symbols.items()) |entry| {
+        const name = try self.makeString(entry.key);
+        undefs.appendAssumeCapacity(.{
+            .n_strx = name,
+            .n_type = std.macho.N_UNDF | std.macho.N_EXT,
+            .n_sect = 0,
+            .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER,
+            .n_value = 0,
+        });
+    }
+    for (self.extern_nonlazy_symbols.items()) |entry| {
+        const name = try self.makeString(entry.key);
+        undefs.appendAssumeCapacity(.{
+            .n_strx = name,
+            .n_type = std.macho.N_UNDF | std.macho.N_EXT,
+            .n_sect = 0,
+            .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER,
+            .n_value = 0,
+        });
+    }
 
     const locals_off = symtab.symoff;
     const locals_size = nlocals * @sizeOf(macho.nlist_64);
@@ -2521,8 +2568,8 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void {
 
     const undefs_off = globals_off + globals_size;
     const undefs_size = nundefs * @sizeOf(macho.nlist_64);
-    log.debug("writing undef symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off });
-    try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off);
+    log.debug("writing extern symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off });
+    try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off);
 
     // Update dynamic symbol table.
     const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
@@ -2546,42 +2593,33 @@ fn writeIndirectSymbolTable(self: *MachO) !void {
 
     var buf: [@sizeOf(u32)]u8 = undefined;
     var off = dysymtab.indirectsymoff;
-    var idx: u32 = 0;
 
     stubs.reserved1 = 0;
-    for (self.undef_symbols.items) |sym, i| {
-        if (i == self.dyld_stub_binder_index.?) {
-            continue;
-        }
-        const symtab_idx = @intCast(u32, dysymtab.iundefsym + i);
+    for (self.extern_lazy_symbols.items()) |entry| {
+        const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index);
         mem.writeIntLittle(u32, &buf, symtab_idx);
         try self.base.file.?.pwriteAll(&buf, off);
         off += @sizeOf(u32);
         dysymtab.nindirectsyms += 1;
-        idx += 1;
     }
 
-    got.reserved1 = @intCast(u32, self.undef_symbols.items.len - 1);
-    if (self.dyld_stub_binder_index) |i| {
-        const symtab_idx = i + dysymtab.iundefsym;
+    const base_id = @intCast(u32, self.extern_lazy_symbols.items().len);
+    got.reserved1 = base_id;
+    for (self.extern_nonlazy_symbols.items()) |entry| {
+        const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index + base_id);
         mem.writeIntLittle(u32, &buf, symtab_idx);
         try self.base.file.?.pwriteAll(&buf, off);
         off += @sizeOf(u32);
         dysymtab.nindirectsyms += 1;
-        idx += 1;
     }
 
-    la.reserved1 = got.reserved1 + 1;
-    for (self.undef_symbols.items) |sym, i| {
-        if (i == self.dyld_stub_binder_index.?) {
-            continue;
-        }
-        const symtab_idx = @intCast(u32, dysymtab.iundefsym + i);
+    la.reserved1 = got.reserved1 + @intCast(u32, self.extern_nonlazy_symbols.items().len);
+    for (self.extern_lazy_symbols.items()) |entry| {
+        const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index);
         mem.writeIntLittle(u32, &buf, symtab_idx);
         try self.base.file.?.pwriteAll(&buf, off);
         off += @sizeOf(u32);
         dysymtab.nindirectsyms += 1;
-        idx += 1;
     }
 }
 
@@ -2689,12 +2727,19 @@ fn writeRebaseInfoTable(self: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const size = try self.rebase_info_table.calcSize();
+    var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_lazy_symbols.items().len);
+    defer self.base.allocator.free(symbols);
+
+    for (self.extern_lazy_symbols.items()) |*entry, i| {
+        symbols[i] = &entry.value;
+    }
+
+    const size = try rebaseInfoSize(symbols);
     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
     defer self.base.allocator.free(buffer);
 
     var stream = std.io.fixedBufferStream(buffer);
-    try self.rebase_info_table.write(stream.writer());
+    try writeRebaseInfo(symbols, stream.writer());
 
     const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
@@ -2720,12 +2765,19 @@ fn writeBindingInfoTable(self: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const size = try self.binding_info_table.calcSize();
+    var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_nonlazy_symbols.items().len);
+    defer self.base.allocator.free(symbols);
+
+    for (self.extern_nonlazy_symbols.items()) |*entry, i| {
+        symbols[i] = &entry.value;
+    }
+
+    const size = try bindInfoSize(symbols);
     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
     defer self.base.allocator.free(buffer);
 
     var stream = std.io.fixedBufferStream(buffer);
-    try self.binding_info_table.write(stream.writer());
+    try writeBindInfo(symbols, stream.writer());
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
@@ -2748,12 +2800,19 @@ fn writeBindingInfoTable(self: *MachO) !void {
 fn writeLazyBindingInfoTable(self: *MachO) !void {
     if (!self.lazy_binding_info_dirty) return;
 
-    const size = try self.lazy_binding_info_table.calcSize();
+    var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_lazy_symbols.items().len);
+    defer self.base.allocator.free(symbols);
+
+    for (self.extern_lazy_symbols.items()) |*entry, i| {
+        symbols[i] = &entry.value;
+    }
+
+    const size = try lazyBindInfoSize(symbols);
     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
     defer self.base.allocator.free(buffer);
 
     var stream = std.io.fixedBufferStream(buffer);
-    try self.lazy_binding_info_table.write(stream.writer());
+    try writeLazyBindInfo(symbols, stream.writer());
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
@@ -3001,7 +3060,7 @@ fn parseBindingInfoTable(self: *MachO) !void {
     assert(nread == buffer.len);
 
     var stream = std.io.fixedBufferStream(buffer);
-    try self.binding_info_table.read(stream.reader(), self.base.allocator);
+    // try self.binding_info_table.read(stream.reader(), self.base.allocator);
 }
 
 fn parseLazyBindingInfoTable(self: *MachO) !void {
@@ -3012,5 +3071,5 @@ fn parseLazyBindingInfoTable(self: *MachO) !void {
     assert(nread == buffer.len);
 
     var stream = std.io.fixedBufferStream(buffer);
-    try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator);
+    // try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator);
 }
src/codegen.zig
@@ -1861,29 +1861,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         }
                     } else if (func_value.castTag(.extern_fn)) |func_payload| {
                         const decl = func_payload.data;
+                        // We don't free the decl_name immediately unless it already exists.
+                        // If it doesn't, it will get autofreed when we clean up the extern symbol table.
                         const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name});
-                        const exists: bool = macho_file.externs.contains(decl_name);
+                        const already_defined = macho_file.extern_lazy_symbols.contains(decl_name);
                         const symbol: u32 = blk: {
-                            if (macho_file.externs.get(decl_name)) |index| {
+                            if (macho_file.extern_lazy_symbols.get(decl_name)) |sym| {
                                 self.bin_file.allocator.free(decl_name);
-                                break :blk index;
+                                break :blk sym.index;
                             } else {
-                                const extern_index = @intCast(u32, macho_file.undef_symbols.items.len - 1); // TODO
-                                try macho_file.externs.putNoClobber(self.bin_file.allocator, decl_name, extern_index);
-                                const name = try macho_file.makeString(decl_name);
-                                try macho_file.undef_symbols.append(self.bin_file.allocator, .{
-                                    .n_strx = name,
-                                    .n_type = std.macho.N_UNDF | std.macho.N_EXT,
-                                    .n_sect = 0,
-                                    .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER,
-                                    .n_value = 0,
+                                const index = @intCast(u32, macho_file.extern_lazy_symbols.items().len);
+                                try macho_file.extern_lazy_symbols.putNoClobber(self.bin_file.allocator, decl_name, .{
+                                    .name = decl_name,
+                                    .dylib_ordinal = 1, // TODO this is now hardcoded, since we only support libSystem.
+                                    .index = index,
                                 });
-                                break :blk extern_index;
+                                break :blk index;
                             }
                         };
                         try macho_file.stub_fixups.append(self.bin_file.allocator, .{
                             .symbol = symbol,
-                            .exists = exists,
+                            .already_defined = already_defined,
                             .start = self.code.items.len,
                             .len = 4,
                         });