Commit 15b85df3dd

Jakub Konka <kubkon@jakubkonka.com>
2021-07-06 16:31:47
zld: parse relocs per generated TextBlock
1 parent 54888c6
Changed files (5)
src/link/MachO/reloc/aarch64.zig
@@ -9,24 +9,34 @@ const meta = std.meta;
 const reloc = @import("../reloc.zig");
 
 const Allocator = mem.Allocator;
+const Object = @import("../Object.zig");
 const Relocation = reloc.Relocation;
 const Symbol = @import("../Symbol.zig");
+const TextBlock = Zld.TextBlock;
+const Zld = @import("../Zld.zig");
 
 pub const Branch = struct {
     base: Relocation,
     /// Always .UnconditionalBranchImmediate
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .branch_aarch64;
 
-    pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
-        const displacement = try math.cast(i28, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
+    // pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
+    //     const displacement = try math.cast(i28, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
 
-        log.debug("    | displacement 0x{x}", .{displacement});
+    //     log.debug("    | displacement 0x{x}", .{displacement});
 
-        var inst = branch.inst;
-        inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
-        mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
+    //     var inst = branch.inst;
+    //     inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
+    //     mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
+    // }
+
+    pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
@@ -34,24 +44,32 @@ pub const Page = struct {
     base: Relocation,
     addend: ?u32 = null,
     /// Always .PCRelativeAddress
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .page;
 
-    pub fn resolve(page: Page, args: Relocation.ResolveArgs) !void {
-        const target_addr = if (page.addend) |addend| args.target_addr + addend else args.target_addr;
-        const source_page = @intCast(i32, args.source_addr >> 12);
-        const target_page = @intCast(i32, target_addr >> 12);
-        const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
+    // pub fn resolve(page: Page, args: Relocation.ResolveArgs) !void {
+    //     const target_addr = if (page.addend) |addend| args.target_addr + addend else args.target_addr;
+    //     const source_page = @intCast(i32, args.source_addr >> 12);
+    //     const target_page = @intCast(i32, target_addr >> 12);
+    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
+
+    //     log.debug("    | calculated addend 0x{x}", .{page.addend});
+    //     log.debug("    | moving by {} pages", .{pages});
 
-        log.debug("    | calculated addend 0x{x}", .{page.addend});
-        log.debug("    | moving by {} pages", .{pages});
+    //     var inst = page.inst;
+    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
+    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
 
-        var inst = page.inst;
-        inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-        inst.pc_relative_address.immlo = @truncate(u2, pages);
+    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    // }
 
-        mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = fmt;
+        _ = options;
+        if (self.addend) |addend| {
+            try std.fmt.format(writer, ".addend = {}, ", .{addend});
+        }
     }
 };
 
@@ -59,7 +77,7 @@ pub const PageOff = struct {
     base: Relocation,
     addend: ?u32 = null,
     op_kind: OpKind,
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .page_off;
 
@@ -68,76 +86,99 @@ pub const PageOff = struct {
         load_store,
     };
 
-    pub fn resolve(page_off: PageOff, args: Relocation.ResolveArgs) !void {
-        const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
-        const narrowed = @truncate(u12, target_addr);
-
-        log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
-        log.debug("    | {s} opcode", .{page_off.op_kind});
-
-        var inst = page_off.inst;
-        if (page_off.op_kind == .arithmetic) {
-            inst.add_subtract_immediate.imm12 = narrowed;
-        } else {
-            const offset: u12 = blk: {
-                if (inst.load_store_register.size == 0) {
-                    if (inst.load_store_register.v == 1) {
-                        // 128-bit SIMD is scaled by 16.
-                        break :blk try math.divExact(u12, narrowed, 16);
-                    }
-                    // Otherwise, 8-bit SIMD or ldrb.
-                    break :blk narrowed;
-                } else {
-                    const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
-                    break :blk try math.divExact(u12, narrowed, denom);
-                }
-            };
-            inst.load_store_register.offset = offset;
+    // pub fn resolve(page_off: PageOff, args: Relocation.ResolveArgs) !void {
+    //     const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
+    //     const narrowed = @truncate(u12, target_addr);
+
+    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+    //     log.debug("    | {s} opcode", .{page_off.op_kind});
+
+    //     var inst = page_off.inst;
+    //     if (page_off.op_kind == .arithmetic) {
+    //         inst.add_subtract_immediate.imm12 = narrowed;
+    //     } else {
+    //         const offset: u12 = blk: {
+    //             if (inst.load_store_register.size == 0) {
+    //                 if (inst.load_store_register.v == 1) {
+    //                     // 128-bit SIMD is scaled by 16.
+    //                     break :blk try math.divExact(u12, narrowed, 16);
+    //                 }
+    //                 // Otherwise, 8-bit SIMD or ldrb.
+    //                 break :blk narrowed;
+    //             } else {
+    //                 const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
+    //                 break :blk try math.divExact(u12, narrowed, denom);
+    //             }
+    //         };
+    //         inst.load_store_register.offset = offset;
+    //     }
+
+    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+    // }
+
+    pub fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = fmt;
+        _ = options;
+        if (self.addend) |addend| {
+            try std.fmt.format(writer, ".addend = {}, ", .{addend});
         }
-
-        mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+        try std.fmt.format(writer, ".op_kind = {s}, ", .{self.op_kind});
     }
 };
 
 pub const GotPage = struct {
     base: Relocation,
     /// Always .PCRelativeAddress
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .got_page;
 
-    pub fn resolve(page: GotPage, args: Relocation.ResolveArgs) !void {
-        const source_page = @intCast(i32, args.source_addr >> 12);
-        const target_page = @intCast(i32, args.target_addr >> 12);
-        const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
+    // pub fn resolve(page: GotPage, args: Relocation.ResolveArgs) !void {
+    //     const source_page = @intCast(i32, args.source_addr >> 12);
+    //     const target_page = @intCast(i32, args.target_addr >> 12);
+    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
 
-        log.debug("    | moving by {} pages", .{pages});
+    //     log.debug("    | moving by {} pages", .{pages});
 
-        var inst = page.inst;
-        inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-        inst.pc_relative_address.immlo = @truncate(u2, pages);
+    //     var inst = page.inst;
+    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
+    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
 
-        mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    // }
+
+    pub fn format(self: GotPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
 pub const GotPageOff = struct {
     base: Relocation,
     /// Always .LoadStoreRegister with size = 3 for GOT indirection
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .got_page_off;
 
-    pub fn resolve(page_off: GotPageOff, args: Relocation.ResolveArgs) !void {
-        const narrowed = @truncate(u12, args.target_addr);
+    // pub fn resolve(page_off: GotPageOff, args: Relocation.ResolveArgs) !void {
+    //     const narrowed = @truncate(u12, args.target_addr);
+
+    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
 
-        log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+    //     var inst = page_off.inst;
+    //     const offset = try math.divExact(u12, narrowed, 8);
+    //     inst.load_store_register.offset = offset;
 
-        var inst = page_off.inst;
-        const offset = try math.divExact(u12, narrowed, 8);
-        inst.load_store_register.offset = offset;
+    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+    // }
 
-        mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+    pub fn format(self: GotPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
@@ -146,34 +187,48 @@ pub const PointerToGot = struct {
 
     pub const base_type: Relocation.Type = .pointer_to_got;
 
-    pub fn resolve(ptr_to_got: PointerToGot, args: Relocation.ResolveArgs) !void {
-        const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
+    // pub fn resolve(ptr_to_got: PointerToGot, args: Relocation.ResolveArgs) !void {
+    //     const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
+
+    //     log.debug("    | calculated value 0x{x}", .{result});
 
-        log.debug("    | calculated value 0x{x}", .{result});
+    //     mem.writeIntLittle(u32, ptr_to_got.base.code[0..4], @bitCast(u32, result));
+    // }
 
-        mem.writeIntLittle(u32, ptr_to_got.base.code[0..4], @bitCast(u32, result));
+    pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
 pub const TlvpPage = struct {
     base: Relocation,
     /// Always .PCRelativeAddress
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .tlvp_page;
 
-    pub fn resolve(page: TlvpPage, args: Relocation.ResolveArgs) !void {
-        const source_page = @intCast(i32, args.source_addr >> 12);
-        const target_page = @intCast(i32, args.target_addr >> 12);
-        const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
+    // pub fn resolve(page: TlvpPage, args: Relocation.ResolveArgs) !void {
+    //     const source_page = @intCast(i32, args.source_addr >> 12);
+    //     const target_page = @intCast(i32, args.target_addr >> 12);
+    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
 
-        log.debug("    | moving by {} pages", .{pages});
+    //     log.debug("    | moving by {} pages", .{pages});
 
-        var inst = page.inst;
-        inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-        inst.pc_relative_address.immlo = @truncate(u2, pages);
+    //     var inst = page.inst;
+    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
+    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
 
-        mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
+    // }
+
+    pub fn format(self: TlvpPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
@@ -182,82 +237,110 @@ pub const TlvpPageOff = struct {
     /// Always .AddSubtractImmediate regardless of the source instruction.
     /// This means, we always rewrite the instruction to add even if the
     /// source instruction was an ldr.
-    inst: aarch64.Instruction,
+    // inst: aarch64.Instruction,
 
     pub const base_type: Relocation.Type = .tlvp_page_off;
 
-    pub fn resolve(page_off: TlvpPageOff, args: Relocation.ResolveArgs) !void {
-        const narrowed = @truncate(u12, args.target_addr);
+    // pub fn resolve(page_off: TlvpPageOff, args: Relocation.ResolveArgs) !void {
+    //     const narrowed = @truncate(u12, args.target_addr);
+
+    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
 
-        log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+    //     var inst = page_off.inst;
+    //     inst.add_subtract_immediate.imm12 = narrowed;
 
-        var inst = page_off.inst;
-        inst.add_subtract_immediate.imm12 = narrowed;
+    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+    // }
 
-        mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+    pub fn format(self: TlvpPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
 pub const Parser = struct {
-    allocator: *Allocator,
+    object: *Object,
+    zld: *Zld,
     it: *reloc.RelocIterator,
-    code: []u8,
-    parsed: std.ArrayList(*Relocation),
+    block: *TextBlock,
+    base_addr: u64,
     addend: ?u32 = null,
-    subtractor: ?Relocation.Target = null,
+    subtractor: ?*Symbol = null,
 
-    pub fn deinit(parser: *Parser) void {
-        parser.parsed.deinit();
-    }
-
-    pub fn parse(parser: *Parser) !void {
-        while (parser.it.next()) |rel| {
-            switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
-                .ARM64_RELOC_BRANCH26 => {
-                    try parser.parseBranch(rel);
-                },
+    pub fn parse(self: *Parser) !void {
+        while (self.it.next()) |rel| {
+            const out_rel = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
+                .ARM64_RELOC_BRANCH26 => try self.parseBranch(rel),
                 .ARM64_RELOC_SUBTRACTOR => {
-                    try parser.parseSubtractor(rel);
-                },
-                .ARM64_RELOC_UNSIGNED => {
-                    try parser.parseUnsigned(rel);
+                    // Subtractor is not a relocation with effect on the TextBlock, so
+                    // parse it and carry on.
+                    try self.parseSubtractor(rel);
+                    continue;
                 },
+                .ARM64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
                 .ARM64_RELOC_ADDEND => {
-                    try parser.parseAddend(rel);
+                    // Addend is not a relocation with effect on the TextBlock, so
+                    // parse it and carry on.
+                    try self.parseAddend(rel);
+                    continue;
                 },
                 .ARM64_RELOC_PAGE21,
                 .ARM64_RELOC_GOT_LOAD_PAGE21,
                 .ARM64_RELOC_TLVP_LOAD_PAGE21,
-                => {
-                    try parser.parsePage(rel);
-                },
-                .ARM64_RELOC_PAGEOFF12 => {
-                    try parser.parsePageOff(rel);
-                },
-                .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
-                    try parser.parseGotLoadPageOff(rel);
-                },
-                .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
-                    try parser.parseTlvpLoadPageOff(rel);
+                => try self.parsePage(rel),
+                .ARM64_RELOC_PAGEOFF12 => try self.parsePageOff(rel),
+                .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => try self.parseGotLoadPageOff(rel),
+                .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => try self.parseTlvpLoadPageOff(rel),
+                .ARM64_RELOC_POINTER_TO_GOT => try self.parsePointerToGot(rel),
+            };
+            try self.block.relocs.append(out_rel);
+
+            if (out_rel.target.payload == .regular) {
+                try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {});
+            }
+
+            switch (out_rel.@"type") {
+                .got_page, .got_page_off, .pointer_to_got => {
+                    const sym = out_rel.target;
+
+                    if (sym.got_index != null) continue;
+
+                    const index = @intCast(u32, self.zld.got_entries.items.len);
+                    sym.got_index = index;
+                    try self.zld.got_entries.append(self.zld.allocator, sym);
+
+                    log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index });
                 },
-                .ARM64_RELOC_POINTER_TO_GOT => {
-                    try parser.parsePointerToGot(rel);
+                .branch_aarch64 => {
+                    const sym = out_rel.target;
+
+                    if (sym.stubs_index != null) continue;
+                    if (sym.payload != .proxy) continue;
+
+                    const index = @intCast(u32, self.zld.stubs.items.len);
+                    sym.stubs_index = index;
+                    try self.zld.stubs.append(self.zld.allocator, sym);
+
+                    log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
                 },
+                else => {},
             }
         }
     }
 
-    fn parseAddend(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseAddend(self: *Parser, rel: macho.relocation_info) !void {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_ADDEND);
         assert(rel.r_pcrel == 0);
         assert(rel.r_extern == 0);
-        assert(parser.addend == null);
+        assert(self.addend == null);
 
-        parser.addend = rel.r_symbolnum;
+        self.addend = rel.r_symbolnum;
 
         // Verify ADDEND is followed by a load.
-        const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
+        const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
         switch (next) {
             .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
             else => {
@@ -267,127 +350,101 @@ pub const Parser = struct {
         }
     }
 
-    fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseBranch(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_BRANCH26);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const parsed_inst = aarch64.Instruction{ .unconditional_branch_immediate = mem.bytesToValue(
-            meta.TagPayload(
-                aarch64.Instruction,
-                aarch64.Instruction.unconditional_branch_immediate,
-            ),
-            inst,
-        ) };
-
-        var branch = try parser.allocator.create(Branch);
-        errdefer parser.allocator.destroy(branch);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
 
-        const target = Relocation.Target.fromReloc(rel);
+        var branch = try self.object.allocator.create(Branch);
+        errdefer self.object.allocator.destroy(branch);
 
         branch.* = .{
             .base = .{
                 .@"type" = .branch_aarch64,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
-            .inst = parsed_inst,
         };
 
-        log.debug("    | emitting {}", .{branch});
-        try parser.parsed.append(&branch.base);
+        return &branch.base;
     }
 
-    fn parsePage(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parsePage(self: *Parser, rel: macho.relocation_info) !*Relocation {
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        const target = Relocation.Target.fromReloc(rel);
-
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const parsed_inst = aarch64.Instruction{ .pc_relative_address = mem.bytesToValue(meta.TagPayload(
-            aarch64.Instruction,
-            aarch64.Instruction.pc_relative_address,
-        ), inst) };
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
 
         const ptr: *Relocation = ptr: {
             switch (rel_type) {
                 .ARM64_RELOC_PAGE21 => {
                     defer {
                         // Reset parser's addend state
-                        parser.addend = null;
+                        self.addend = null;
                     }
-                    var page = try parser.allocator.create(Page);
-                    errdefer parser.allocator.destroy(page);
+                    var page = try self.object.allocator.create(Page);
+                    errdefer self.object.allocator.destroy(page);
 
                     page.* = .{
                         .base = .{
                             .@"type" = .page,
-                            .code = inst,
                             .offset = offset,
                             .target = target,
+                            .block = self.block,
                         },
-                        .addend = parser.addend,
-                        .inst = parsed_inst,
+                        .addend = self.addend,
                     };
 
-                    log.debug("    | emitting {}", .{page});
-
                     break :ptr &page.base;
                 },
                 .ARM64_RELOC_GOT_LOAD_PAGE21 => {
-                    var page = try parser.allocator.create(GotPage);
-                    errdefer parser.allocator.destroy(page);
+                    var page = try self.object.allocator.create(GotPage);
+                    errdefer self.object.allocator.destroy(page);
 
                     page.* = .{
                         .base = .{
                             .@"type" = .got_page,
-                            .code = inst,
                             .offset = offset,
                             .target = target,
+                            .block = self.block,
                         },
-                        .inst = parsed_inst,
                     };
 
-                    log.debug("    | emitting {}", .{page});
-
                     break :ptr &page.base;
                 },
                 .ARM64_RELOC_TLVP_LOAD_PAGE21 => {
-                    var page = try parser.allocator.create(TlvpPage);
-                    errdefer parser.allocator.destroy(page);
+                    var page = try self.object.allocator.create(TlvpPage);
+                    errdefer self.object.allocator.destroy(page);
 
                     page.* = .{
                         .base = .{
                             .@"type" = .tlvp_page,
-                            .code = inst,
                             .offset = offset,
                             .target = target,
+                            .block = self.block,
                         },
-                        .inst = parsed_inst,
                     };
 
-                    log.debug("    | emitting {}", .{page});
-
                     break :ptr &page.base;
                 },
                 else => unreachable,
             }
         };
 
-        try parser.parsed.append(ptr);
+        return ptr;
     }
 
-    fn parsePageOff(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parsePageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
         defer {
             // Reset parser's addend state
-            parser.addend = null;
+            self.addend = null;
         }
 
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
@@ -395,83 +452,56 @@ pub const Parser = struct {
         assert(rel.r_pcrel == 0);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-
-        var op_kind: PageOff.OpKind = undefined;
-        var parsed_inst: aarch64.Instruction = undefined;
-        if (isArithmeticOp(inst)) {
-            op_kind = .arithmetic;
-            parsed_inst = .{ .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
-                aarch64.Instruction,
-                aarch64.Instruction.add_subtract_immediate,
-            ), inst) };
-        } else {
-            op_kind = .load_store;
-            parsed_inst = .{ .load_store_register = mem.bytesToValue(meta.TagPayload(
-                aarch64.Instruction,
-                aarch64.Instruction.load_store_register,
-            ), inst) };
-        }
-        const target = Relocation.Target.fromReloc(rel);
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const op_kind: PageOff.OpKind = if (isArithmeticOp(self.block.code[offset..][0..4]))
+            .arithmetic
+        else
+            .load_store;
 
-        var page_off = try parser.allocator.create(PageOff);
-        errdefer parser.allocator.destroy(page_off);
+        var page_off = try self.object.allocator.create(PageOff);
+        errdefer self.object.allocator.destroy(page_off);
 
         page_off.* = .{
             .base = .{
                 .@"type" = .page_off,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
             .op_kind = op_kind,
-            .inst = parsed_inst,
-            .addend = parser.addend,
+            .addend = self.addend,
         };
 
-        log.debug("    | emitting {}", .{page_off});
-        try parser.parsed.append(&page_off.base);
+        return &page_off.base;
     }
 
-    fn parseGotLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseGotLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12);
         assert(rel.r_pcrel == 0);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        assert(!isArithmeticOp(inst));
-
-        const parsed_inst = mem.bytesToValue(meta.TagPayload(
-            aarch64.Instruction,
-            aarch64.Instruction.load_store_register,
-        ), inst);
-        assert(parsed_inst.size == 3);
-
-        const target = Relocation.Target.fromReloc(rel);
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        assert(!isArithmeticOp(self.block.code[offset..][0..4]));
 
-        var page_off = try parser.allocator.create(GotPageOff);
-        errdefer parser.allocator.destroy(page_off);
+        var page_off = try self.object.allocator.create(GotPageOff);
+        errdefer self.object.allocator.destroy(page_off);
 
         page_off.* = .{
             .base = .{
                 .@"type" = .got_page_off,
-                .code = inst,
                 .offset = offset,
                 .target = target,
-            },
-            .inst = .{
-                .load_store_register = parsed_inst,
+                .block = self.block,
             },
         };
 
-        log.debug("    | emitting {}", .{page_off});
-        try parser.parsed.append(&page_off.base);
+        return &page_off.base;
     }
 
-    fn parseTlvpLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseTlvpLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
         assert(rel.r_pcrel == 0);
@@ -483,141 +513,102 @@ pub const Parser = struct {
             size: u1,
         };
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const parsed: RegInfo = parsed: {
-            if (isArithmeticOp(inst)) {
-                const parsed_inst = mem.bytesAsValue(meta.TagPayload(
-                    aarch64.Instruction,
-                    aarch64.Instruction.add_subtract_immediate,
-                ), inst);
-                break :parsed .{
-                    .rd = parsed_inst.rd,
-                    .rn = parsed_inst.rn,
-                    .size = parsed_inst.sf,
-                };
-            } else {
-                const parsed_inst = mem.bytesAsValue(meta.TagPayload(
-                    aarch64.Instruction,
-                    aarch64.Instruction.load_store_register,
-                ), inst);
-                break :parsed .{
-                    .rd = parsed_inst.rt,
-                    .rn = parsed_inst.rn,
-                    .size = @truncate(u1, parsed_inst.size),
-                };
-            }
-        };
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
 
-        const target = Relocation.Target.fromReloc(rel);
-
-        var page_off = try parser.allocator.create(TlvpPageOff);
-        errdefer parser.allocator.destroy(page_off);
+        var page_off = try self.object.allocator.create(TlvpPageOff);
+        errdefer self.object.allocator.destroy(page_off);
 
         page_off.* = .{
             .base = .{
                 .@"type" = .tlvp_page_off,
-                .code = inst,
                 .offset = offset,
                 .target = target,
-            },
-            .inst = .{
-                .add_subtract_immediate = .{
-                    .rd = parsed.rd,
-                    .rn = parsed.rn,
-                    .imm12 = 0, // This will be filled when target addresses are known.
-                    .sh = 0,
-                    .s = 0,
-                    .op = 0,
-                    .sf = parsed.size,
-                },
+                .block = self.block,
             },
         };
 
-        log.debug("    | emitting {}", .{page_off});
-        try parser.parsed.append(&page_off.base);
+        return &page_off.base;
     }
 
-    fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_SUBTRACTOR);
         assert(rel.r_pcrel == 0);
-        assert(parser.subtractor == null);
+        assert(self.subtractor == null);
 
-        parser.subtractor = Relocation.Target.fromReloc(rel);
+        self.subtractor = try self.object.symbolFromReloc(rel);
 
         // Verify SUBTRACTOR is followed by UNSIGNED.
-        const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
+        const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
         if (next != .ARM64_RELOC_UNSIGNED) {
             log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
             return error.UnexpectedRelocationType;
         }
     }
 
-    fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
         defer {
             // Reset parser's subtractor state
-            parser.subtractor = null;
+            self.subtractor = null;
         }
 
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_UNSIGNED);
         assert(rel.r_pcrel == 0);
 
-        var unsigned = try parser.allocator.create(reloc.Unsigned);
-        errdefer parser.allocator.destroy(unsigned);
-
-        const target = Relocation.Target.fromReloc(rel);
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
         const is_64bit: bool = switch (rel.r_length) {
             3 => true,
             2 => false,
             else => unreachable,
         };
-        const offset = @intCast(u32, rel.r_address);
         const addend: i64 = if (is_64bit)
-            mem.readIntLittle(i64, parser.code[offset..][0..8])
+            mem.readIntLittle(i64, self.block.code[offset..][0..8])
         else
-            mem.readIntLittle(i32, parser.code[offset..][0..4]);
+            mem.readIntLittle(i32, self.block.code[offset..][0..4]);
+
+        var unsigned = try self.object.allocator.create(reloc.Unsigned);
+        errdefer self.object.allocator.destroy(unsigned);
 
         unsigned.* = .{
             .base = .{
                 .@"type" = .unsigned,
-                .code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
-            .subtractor = parser.subtractor,
+            .subtractor = self.subtractor,
             .is_64bit = is_64bit,
             .addend = addend,
         };
 
-        log.debug("    | emitting {}", .{unsigned});
-        try parser.parsed.append(&unsigned.base);
+        return &unsigned.base;
     }
 
-    fn parsePointerToGot(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parsePointerToGot(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
         assert(rel_type == .ARM64_RELOC_POINTER_TO_GOT);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        var ptr_to_got = try parser.allocator.create(PointerToGot);
-        errdefer parser.allocator.destroy(ptr_to_got);
+        var ptr_to_got = try self.object.allocator.create(PointerToGot);
+        errdefer self.object.allocator.destroy(ptr_to_got);
 
-        const target = Relocation.Target.fromReloc(rel);
-        const offset = @intCast(u32, rel.r_address);
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
 
         ptr_to_got.* = .{
             .base = .{
                 .@"type" = .pointer_to_got,
-                .code = parser.code[offset..][0..4],
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
         };
 
-        log.debug("    | emitting {}", .{ptr_to_got});
-        try parser.parsed.append(&ptr_to_got.base);
+        return &ptr_to_got.base;
     }
 };
 
src/link/MachO/reloc/x86_64.zig
@@ -8,17 +8,28 @@ const meta = std.meta;
 const reloc = @import("../reloc.zig");
 
 const Allocator = mem.Allocator;
+const Object = @import("../Object.zig");
 const Relocation = reloc.Relocation;
+const Symbol = @import("../Symbol.zig");
+const TextBlock = Zld.TextBlock;
+const Zld = @import("../Zld.zig");
 
 pub const Branch = struct {
     base: Relocation,
 
     pub const base_type: Relocation.Type = .branch_x86_64;
 
-    pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
-        const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-        log.debug("    | displacement 0x{x}", .{displacement});
-        mem.writeIntLittle(u32, branch.base.code[0..4], @bitCast(u32, displacement));
+    // pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
+    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
+    //     log.debug("    | displacement 0x{x}", .{displacement});
+    //     mem.writeIntLittle(u32, branch.base.code[0..4], @bitCast(u32, displacement));
+    // }
+
+    pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
@@ -29,25 +40,32 @@ pub const Signed = struct {
 
     pub const base_type: Relocation.Type = .signed;
 
-    pub fn resolve(signed: Signed, args: Relocation.ResolveArgs) !void {
-        const target_addr = target_addr: {
-            if (signed.base.target == .section) {
-                const source_target = @intCast(i64, args.source_source_sect_addr.?) + @intCast(i64, signed.base.offset) + signed.addend + 4;
-                const source_disp = source_target - @intCast(i64, args.source_target_sect_addr.?);
-                break :target_addr @intCast(i64, args.target_addr) + source_disp;
-            }
-            break :target_addr @intCast(i64, args.target_addr) + signed.addend;
-        };
-        const displacement = try math.cast(
-            i32,
-            target_addr - @intCast(i64, args.source_addr) - signed.correction - 4,
-        );
-
-        log.debug("    | addend 0x{x}", .{signed.addend});
-        log.debug("    | correction 0x{x}", .{signed.correction});
-        log.debug("    | displacement 0x{x}", .{displacement});
-
-        mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
+    // pub fn resolve(signed: Signed, args: Relocation.ResolveArgs) !void {
+    //     const target_addr = target_addr: {
+    //         if (signed.base.target == .section) {
+    //             const source_target = @intCast(i64, args.source_source_sect_addr.?) + @intCast(i64, signed.base.offset) + signed.addend + 4;
+    //             const source_disp = source_target - @intCast(i64, args.source_target_sect_addr.?);
+    //             break :target_addr @intCast(i64, args.target_addr) + source_disp;
+    //         }
+    //         break :target_addr @intCast(i64, args.target_addr) + signed.addend;
+    //     };
+    //     const displacement = try math.cast(
+    //         i32,
+    //         target_addr - @intCast(i64, args.source_addr) - signed.correction - 4,
+    //     );
+
+    //     log.debug("    | addend 0x{x}", .{signed.addend});
+    //     log.debug("    | correction 0x{x}", .{signed.correction});
+    //     log.debug("    | displacement 0x{x}", .{displacement});
+
+    //     mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
+    // }
+
+    pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = fmt;
+        _ = options;
+        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
+        try std.fmt.format(writer, ".correction = {}, ", .{self.correction});
     }
 };
 
@@ -56,10 +74,17 @@ pub const GotLoad = struct {
 
     pub const base_type: Relocation.Type = .got_load;
 
-    pub fn resolve(got_load: GotLoad, args: Relocation.ResolveArgs) !void {
-        const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-        log.debug("    | displacement 0x{x}", .{displacement});
-        mem.writeIntLittle(u32, got_load.base.code[0..4], @bitCast(u32, displacement));
+    // pub fn resolve(got_load: GotLoad, args: Relocation.ResolveArgs) !void {
+    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
+    //     log.debug("    | displacement 0x{x}", .{displacement});
+    //     mem.writeIntLittle(u32, got_load.base.code[0..4], @bitCast(u32, displacement));
+    // }
+
+    pub fn format(self: GotLoad, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
@@ -69,113 +94,139 @@ pub const Got = struct {
 
     pub const base_type: Relocation.Type = .got;
 
-    pub fn resolve(got: Got, args: Relocation.ResolveArgs) !void {
-        const displacement = try math.cast(
-            i32,
-            @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + got.addend,
-        );
-        log.debug("    | displacement 0x{x}", .{displacement});
-        mem.writeIntLittle(u32, got.base.code[0..4], @bitCast(u32, displacement));
+    // pub fn resolve(got: Got, args: Relocation.ResolveArgs) !void {
+    //     const displacement = try math.cast(
+    //         i32,
+    //         @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + got.addend,
+    //     );
+    //     log.debug("    | displacement 0x{x}", .{displacement});
+    //     mem.writeIntLittle(u32, got.base.code[0..4], @bitCast(u32, displacement));
+    // }
+
+    pub fn format(self: Got, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = fmt;
+        _ = options;
+        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
     }
 };
 
 pub const Tlv = struct {
     base: Relocation,
-    op: *u8,
 
     pub const base_type: Relocation.Type = .tlv;
 
-    pub fn resolve(tlv: Tlv, args: Relocation.ResolveArgs) !void {
-        // We need to rewrite the opcode from movq to leaq.
-        tlv.op.* = 0x8d;
-        log.debug("    | rewriting op to leaq", .{});
-
-        const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-        log.debug("    | displacement 0x{x}", .{displacement});
-
-        mem.writeIntLittle(u32, tlv.base.code[0..4], @bitCast(u32, displacement));
+    // pub fn resolve(tlv: Tlv, args: Relocation.ResolveArgs) !void {
+    //     // We need to rewrite the opcode from movq to leaq.
+    //     tlv.op.* = 0x8d;
+    //     log.debug("    | rewriting op to leaq", .{});
+
+    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
+    //     log.debug("    | displacement 0x{x}", .{displacement});
+
+    //     mem.writeIntLittle(u32, tlv.base.code[0..4], @bitCast(u32, displacement));
+    // }
+    pub fn format(self: Tlv, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = self;
+        _ = fmt;
+        _ = options;
+        _ = writer;
     }
 };
 
 pub const Parser = struct {
-    allocator: *Allocator,
+    object: *Object,
+    zld: *Zld,
     it: *reloc.RelocIterator,
-    code: []u8,
-    parsed: std.ArrayList(*Relocation),
-    subtractor: ?Relocation.Target = null,
-
-    pub fn deinit(parser: *Parser) void {
-        parser.parsed.deinit();
-    }
-
-    pub fn parse(parser: *Parser) !void {
-        while (parser.it.next()) |rel| {
-            switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
-                .X86_64_RELOC_BRANCH => {
-                    try parser.parseBranch(rel);
-                },
+    block: *TextBlock,
+    base_addr: u64,
+    subtractor: ?*Symbol = null,
+
+    pub fn parse(self: *Parser) !void {
+        while (self.it.next()) |rel| {
+            const out_rel = switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
+                .X86_64_RELOC_BRANCH => try self.parseBranch(rel),
                 .X86_64_RELOC_SUBTRACTOR => {
-                    try parser.parseSubtractor(rel);
-                },
-                .X86_64_RELOC_UNSIGNED => {
-                    try parser.parseUnsigned(rel);
+                    // Subtractor is not a relocation with effect on the TextBlock, so
+                    // parse it and carry on.
+                    try self.parseSubtractor(rel);
+                    continue;
                 },
+                .X86_64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
                 .X86_64_RELOC_SIGNED,
                 .X86_64_RELOC_SIGNED_1,
                 .X86_64_RELOC_SIGNED_2,
                 .X86_64_RELOC_SIGNED_4,
-                => {
-                    try parser.parseSigned(rel);
-                },
-                .X86_64_RELOC_GOT_LOAD => {
-                    try parser.parseGotLoad(rel);
-                },
-                .X86_64_RELOC_GOT => {
-                    try parser.parseGot(rel);
+                => try self.parseSigned(rel),
+                .X86_64_RELOC_GOT_LOAD => try self.parseGotLoad(rel),
+                .X86_64_RELOC_GOT => try self.parseGot(rel),
+                .X86_64_RELOC_TLV => try self.parseTlv(rel),
+            };
+            try self.block.relocs.append(out_rel);
+
+            if (out_rel.target.payload == .regular) {
+                try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {});
+            }
+
+            switch (out_rel.@"type") {
+                .got_load, .got => {
+                    const sym = out_rel.target;
+
+                    if (sym.got_index != null) continue;
+
+                    const index = @intCast(u32, self.zld.got_entries.items.len);
+                    sym.got_index = index;
+                    try self.zld.got_entries.append(self.zld.allocator, sym);
+
+                    log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index });
                 },
-                .X86_64_RELOC_TLV => {
-                    try parser.parseTlv(rel);
+                .branch_x86_64 => {
+                    const sym = out_rel.target;
+
+                    if (sym.stubs_index != null) continue;
+                    if (sym.payload != .proxy) continue;
+
+                    const index = @intCast(u32, self.zld.stubs.items.len);
+                    sym.stubs_index = index;
+                    try self.zld.stubs.append(self.zld.allocator, sym);
+
+                    log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
                 },
+                else => {},
             }
         }
     }
 
-    fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseBranch(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_BRANCH);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-
-        var branch = try parser.allocator.create(Branch);
-        errdefer parser.allocator.destroy(branch);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
 
-        const target = Relocation.Target.fromReloc(rel);
+        var branch = try self.object.allocator.create(Branch);
+        errdefer self.object.allocator.destroy(branch);
 
         branch.* = .{
             .base = .{
                 .@"type" = .branch_x86_64,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
         };
 
-        log.debug("    | emitting {}", .{branch});
-        try parser.parsed.append(&branch.base);
+        return &branch.base;
     }
 
-    fn parseSigned(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseSigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        const target = Relocation.Target.fromReloc(rel);
-
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
+        const target = try self.object.symbolFromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
         const correction: i4 = switch (rel_type) {
             .X86_64_RELOC_SIGNED => 0,
             .X86_64_RELOC_SIGNED_1 => 1,
@@ -183,161 +234,152 @@ pub const Parser = struct {
             .X86_64_RELOC_SIGNED_4 => 4,
             else => unreachable,
         };
-        const addend = mem.readIntLittle(i32, inst) + correction;
+        const addend = mem.readIntLittle(i32, self.block.code[offset..][0..4]) + correction;
 
-        var signed = try parser.allocator.create(Signed);
-        errdefer parser.allocator.destroy(signed);
+        var signed = try self.object.allocator.create(Signed);
+        errdefer self.object.allocator.destroy(signed);
 
         signed.* = .{
             .base = .{
                 .@"type" = .signed,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
             .addend = addend,
             .correction = correction,
         };
 
-        log.debug("    | emitting {}", .{signed});
-        try parser.parsed.append(&signed.base);
+        return &signed.base;
     }
 
-    fn parseGotLoad(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseGotLoad(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_GOT_LOAD);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const target = Relocation.Target.fromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
 
-        var got_load = try parser.allocator.create(GotLoad);
-        errdefer parser.allocator.destroy(got_load);
+        var got_load = try self.object.allocator.create(GotLoad);
+        errdefer self.object.allocator.destroy(got_load);
 
         got_load.* = .{
             .base = .{
                 .@"type" = .got_load,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
         };
 
-        log.debug("    | emitting {}", .{got_load});
-        try parser.parsed.append(&got_load.base);
+        return &got_load.base;
     }
 
-    fn parseGot(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseGot(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_GOT);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const target = Relocation.Target.fromReloc(rel);
-        const addend = mem.readIntLittle(i32, inst);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
+        const addend = mem.readIntLittle(i32, self.block.code[offset..][0..4]);
 
-        var got = try parser.allocator.create(Got);
-        errdefer parser.allocator.destroy(got);
+        var got = try self.object.allocator.create(Got);
+        errdefer self.object.allocator.destroy(got);
 
         got.* = .{
             .base = .{
                 .@"type" = .got,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
             .addend = addend,
         };
 
-        log.debug("    | emitting {}", .{got});
-        try parser.parsed.append(&got.base);
+        return &got.base;
     }
 
-    fn parseTlv(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseTlv(self: *Parser, rel: macho.relocation_info) !*Relocation {
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_TLV);
         assert(rel.r_pcrel == 1);
         assert(rel.r_length == 2);
 
-        const offset = @intCast(u32, rel.r_address);
-        const inst = parser.code[offset..][0..4];
-        const target = Relocation.Target.fromReloc(rel);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
 
-        var tlv = try parser.allocator.create(Tlv);
-        errdefer parser.allocator.destroy(tlv);
+        var tlv = try self.object.allocator.create(Tlv);
+        errdefer self.object.allocator.destroy(tlv);
 
         tlv.* = .{
             .base = .{
                 .@"type" = .tlv,
-                .code = inst,
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
-            .op = &parser.code[offset - 2],
         };
 
-        log.debug("    | emitting {}", .{tlv});
-        try parser.parsed.append(&tlv.base);
+        return &tlv.base;
     }
 
-    fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void {
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_SUBTRACTOR);
         assert(rel.r_pcrel == 0);
-        assert(parser.subtractor == null);
+        assert(self.subtractor == null);
 
-        parser.subtractor = Relocation.Target.fromReloc(rel);
+        self.subtractor = try self.object.symbolFromReloc(rel);
 
         // Verify SUBTRACTOR is followed by UNSIGNED.
-        const next = @intToEnum(macho.reloc_type_x86_64, parser.it.peek().r_type);
+        const next = @intToEnum(macho.reloc_type_x86_64, self.it.peek().r_type);
         if (next != .X86_64_RELOC_UNSIGNED) {
             log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
             return error.UnexpectedRelocationType;
         }
     }
 
-    fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
+    fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
         defer {
             // Reset parser's subtractor state
-            parser.subtractor = null;
+            self.subtractor = null;
         }
 
         const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
         assert(rel_type == .X86_64_RELOC_UNSIGNED);
         assert(rel.r_pcrel == 0);
 
-        var unsigned = try parser.allocator.create(reloc.Unsigned);
-        errdefer parser.allocator.destroy(unsigned);
-
-        const target = Relocation.Target.fromReloc(rel);
+        const target = try self.object.symbolFromReloc(rel);
         const is_64bit: bool = switch (rel.r_length) {
             3 => true,
             2 => false,
             else => unreachable,
         };
-        const offset = @intCast(u32, rel.r_address);
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
         const addend: i64 = if (is_64bit)
-            mem.readIntLittle(i64, parser.code[offset..][0..8])
+            mem.readIntLittle(i64, self.block.code[offset..][0..8])
         else
-            mem.readIntLittle(i32, parser.code[offset..][0..4]);
+            mem.readIntLittle(i32, self.block.code[offset..][0..4]);
+
+        var unsigned = try self.object.allocator.create(reloc.Unsigned);
+        errdefer self.object.allocator.destroy(unsigned);
 
         unsigned.* = .{
             .base = .{
                 .@"type" = .unsigned,
-                .code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
                 .offset = offset,
                 .target = target,
+                .block = self.block,
             },
-            .subtractor = parser.subtractor,
+            .subtractor = self.subtractor,
             .is_64bit = is_64bit,
             .addend = addend,
         };
 
-        log.debug("    | emitting {}", .{unsigned});
-        try parser.parsed.append(&unsigned.base);
+        return &unsigned.base;
     }
 };
src/link/MachO/Object.zig
@@ -53,6 +53,7 @@ initializers: std.ArrayListUnmanaged(u32) = .{},
 data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
 
 symbols: std.ArrayListUnmanaged(*Symbol) = .{},
+sections_as_symbols: std.AutoHashMapUnmanaged(u8, *Symbol) = .{},
 
 const DebugInfo = struct {
     inner: dwarf.DwarfInfo,
@@ -160,6 +161,7 @@ pub fn deinit(self: *Object) void {
     self.symtab.deinit(self.allocator);
     self.strtab.deinit(self.allocator);
     self.symbols.deinit(self.allocator);
+    self.sections_as_symbols.deinit(self.allocator);
 
     if (self.name) |n| {
         self.allocator.free(n);
@@ -312,7 +314,7 @@ fn filterRelocs(relocs: []macho.relocation_info, start: u64, end: u64) []macho.r
 
     while (true) {
         var change = false;
-        if (relocs[start_id].r_address > end) {
+        if (relocs[start_id].r_address >= end) {
             start_id += 1;
             change = true;
         }
@@ -332,6 +334,7 @@ const TextBlockParser = struct {
     allocator: *Allocator,
     section: macho.section_64,
     code: []u8,
+    relocs: []macho.relocation_info,
     object: *Object,
     zld: *Zld,
     nlists: []NlistWithIndex,
@@ -405,6 +408,7 @@ const TextBlockParser = struct {
 
         const start_addr = senior_nlist.nlist.n_value - self.section.addr;
         const end_addr = if (next_nlist) |n| n.nlist.n_value - self.section.addr else self.section.size;
+        log.warn("{} - {}", .{ start_addr, end_addr });
 
         const code = self.code[start_addr..end_addr];
         const size = code.len;
@@ -424,11 +428,18 @@ const TextBlockParser = struct {
         block.* = .{
             .local_sym_index = senior_nlist.index,
             .aliases = alias_only_indices,
-            .code = code,
+            .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
+            .code = try self.allocator.dupe(u8, code),
+            .relocs = std.ArrayList(*Relocation).init(self.allocator),
             .size = size,
             .alignment = self.section.@"align",
         };
 
+        const relocs = filterRelocs(self.relocs, start_addr, end_addr);
+        if (relocs.len > 0) {
+            try self.object.parseRelocs(self.zld, relocs, block, start_addr);
+        }
+
         self.index += 1;
 
         return block;
@@ -457,7 +468,8 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
 
     sort.sort(NlistWithIndex, sorted_nlists.items, {}, NlistWithIndex.lessThan);
 
-    for (seg.sections.items) |sect, sect_id| {
+    for (seg.sections.items) |sect, id| {
+        const sect_id = @intCast(u8, id);
         log.warn("putting section '{s},{s}' as a TextBlock", .{
             segmentName(sect),
             sectionName(sect),
@@ -474,6 +486,12 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
         defer self.allocator.free(code);
         _ = try self.file.?.preadAll(code, sect.offset);
 
+        // Read section's list of relocations
+        var raw_relocs = try self.allocator.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
+        defer self.allocator.free(raw_relocs);
+        _ = try self.file.?.preadAll(raw_relocs, sect.reloff);
+        const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs);
+
         // Is there any padding between symbols within the section?
         const is_padded = self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
 
@@ -481,7 +499,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             if (is_padded) blocks: {
                 const filtered_nlists = NlistWithIndex.filterInSection(
                     sorted_nlists.items,
-                    @intCast(u8, sect_id + 1),
+                    sect_id + 1,
                 );
 
                 if (filtered_nlists.len == 0) break :blocks;
@@ -490,6 +508,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
                     .allocator = self.allocator,
                     .section = sect,
                     .code = code,
+                    .relocs = relocs,
                     .object = self,
                     .zld = zld,
                     .nlists = filtered_nlists,
@@ -518,8 +537,6 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
                         }
                     }
 
-                    // TODO parse relocs
-
                     if (zld.last_text_block) |last| {
                         last.next = block;
                         block.prev = last;
@@ -531,14 +548,19 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             }
 
             // Since there is no symbol to refer to this block, we create
-            // a temp one.
-            const name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
-                self.name.?,
-                segmentName(sect),
-                sectionName(sect),
-            });
-            defer self.allocator.free(name);
-            const symbol = try Symbol.new(self.allocator, name);
+            // a temp one, unless we already did that when working out the relocations
+            // of other text blocks.
+            const symbol = self.sections_as_symbols.get(sect_id) orelse symbol: {
+                const name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
+                    self.name.?,
+                    segmentName(sect),
+                    sectionName(sect),
+                });
+                defer self.allocator.free(name);
+                const symbol = try Symbol.new(self.allocator, name);
+                try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
+                break :symbol symbol;
+            };
             symbol.payload = .{
                 .regular = .{
                     .linkage = .translation_unit,
@@ -555,12 +577,16 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
 
             block.* = .{
                 .local_sym_index = local_sym_index,
-                .code = code,
+                .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
+                .code = try self.allocator.dupe(u8, code),
+                .relocs = std.ArrayList(*Relocation).init(self.allocator),
                 .size = sect.size,
                 .alignment = sect.@"align",
             };
 
-            // TODO parse relocs
+            if (relocs.len > 0) {
+                try self.parseRelocs(zld, relocs, block, 0);
+            }
 
             if (zld.last_text_block) |last| {
                 last.next = block;
@@ -571,6 +597,70 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
     }
 }
 
+fn parseRelocs(
+    self: *Object,
+    zld: *Zld,
+    relocs: []const macho.relocation_info,
+    block: *TextBlock,
+    base_addr: u64,
+) !void {
+    var it = reloc.RelocIterator{
+        .buffer = relocs,
+    };
+
+    switch (self.arch.?) {
+        .aarch64 => {
+            var parser = reloc.aarch64.Parser{
+                .object = self,
+                .zld = zld,
+                .it = &it,
+                .block = block,
+                .base_addr = base_addr,
+            };
+            try parser.parse();
+        },
+        .x86_64 => {
+            var parser = reloc.x86_64.Parser{
+                .object = self,
+                .zld = zld,
+                .it = &it,
+                .block = block,
+                .base_addr = base_addr,
+            };
+            try parser.parse();
+        },
+        else => unreachable,
+    }
+}
+
+pub fn symbolFromReloc(self: *Object, rel: macho.relocation_info) !*Symbol {
+    const symbol = blk: {
+        if (rel.r_extern == 1) {
+            break :blk self.symbols.items[rel.r_symbolnum];
+        } else {
+            const sect_id = @intCast(u8, rel.r_symbolnum - 1);
+            const symbol = self.sections_as_symbols.get(sect_id) orelse symbol: {
+                // We need a valid pointer to Symbol even if there is no symbol, so we create a
+                // dummy symbol upfront which will later be populated when created a TextBlock from
+                // the target section here.
+                const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
+                const sect = seg.sections.items[sect_id];
+                const name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
+                    self.name.?,
+                    segmentName(sect),
+                    sectionName(sect),
+                });
+                defer self.allocator.free(name);
+                const symbol = try Symbol.new(self.allocator, name);
+                try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
+                break :symbol symbol;
+            };
+            break :blk symbol;
+        }
+    };
+    return symbol;
+}
+
 pub fn parseInitializers(self: *Object) !void {
     const index = self.mod_init_func_section_index orelse return;
     const section = self.sections.items[index];
src/link/MachO/reloc.zig
@@ -6,16 +6,18 @@ const math = std.math;
 const mem = std.mem;
 const meta = std.meta;
 
-const aarch64 = @import("reloc/aarch64.zig");
-const x86_64 = @import("reloc/x86_64.zig");
+pub const aarch64 = @import("reloc/aarch64.zig");
+pub const x86_64 = @import("reloc/x86_64.zig");
 
 const Allocator = mem.Allocator;
+const Symbol = @import("Symbol.zig");
+const TextBlock = @import("Zld.zig").TextBlock;
 
 pub const Relocation = struct {
     @"type": Type,
-    code: []u8,
     offset: u32,
-    target: Target,
+    block: *TextBlock,
+    target: *Symbol,
 
     pub fn cast(base: *Relocation, comptime T: type) ?*T {
         if (base.@"type" != T.base_type)
@@ -24,43 +26,24 @@ pub const Relocation = struct {
         return @fieldParentPtr(T, "base", base);
     }
 
-    pub const ResolveArgs = struct {
-        source_addr: u64,
-        target_addr: u64,
-        subtractor: ?u64 = null,
-        source_source_sect_addr: ?u64 = null,
-        source_target_sect_addr: ?u64 = null,
-    };
-
-    pub fn resolve(base: *Relocation, args: ResolveArgs) !void {
-        log.debug("{s}", .{base.@"type"});
-        log.debug("    | offset 0x{x}", .{base.offset});
-        log.debug("    | source address 0x{x}", .{args.source_addr});
-        log.debug("    | target address 0x{x}", .{args.target_addr});
-        if (args.subtractor) |sub|
-            log.debug("    | subtractor address 0x{x}", .{sub});
-        if (args.source_source_sect_addr) |addr|
-            log.debug("    | source source section address 0x{x}", .{addr});
-        if (args.source_target_sect_addr) |addr|
-            log.debug("    | source target section address 0x{x}", .{addr});
-
-        return switch (base.@"type") {
-            .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(args),
-            .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(args),
-            .page => @fieldParentPtr(aarch64.Page, "base", base).resolve(args),
-            .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(args),
-            .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(args),
-            .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(args),
-            .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).resolve(args),
-            .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(args),
-            .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(args),
-            .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(args),
-            .signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(args),
-            .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(args),
-            .got => @fieldParentPtr(x86_64.Got, "base", base).resolve(args),
-            .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(args),
-        };
-    }
+    // pub fn resolve(base: *Relocation) !void {
+    //     return switch (base.@"type") {
+    //         .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(),
+    //         .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(),
+    //         .page => @fieldParentPtr(aarch64.Page, "base", base).resolve(),
+    //         .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(),
+    //         .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(),
+    //         .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(),
+    //         .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).resolve(),
+    //         .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(),
+    //         .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(),
+    //         .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(),
+    //         .signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(),
+    //         .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(),
+    //         .got => @fieldParentPtr(x86_64.Got, "base", base).resolve(),
+    //         .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(),
+    //     };
+    // }
 
     pub const Type = enum {
         branch_aarch64,
@@ -79,23 +62,37 @@ pub const Relocation = struct {
         tlv,
     };
 
-    pub const Target = union(enum) {
-        symbol: u32,
-        section: u16,
+    pub fn format(base: *const Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Relocation {{ ", .{});
+        try std.fmt.format(writer, ".type = {s}, ", .{base.@"type"});
+        try std.fmt.format(writer, ".offset = {}, ", .{base.offset});
+        try std.fmt.format(writer, ".block = {}", .{base.block.local_sym_index});
+        try std.fmt.format(writer, ".target = {}, ", .{base.target});
+
+        try switch (base.@"type") {
+            .unsigned => @fieldParentPtr(Unsigned, "base", base).format(fmt, options, writer),
+            .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).format(fmt, options, writer),
+            .page => @fieldParentPtr(aarch64.Page, "base", base).format(fmt, options, writer),
+            .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).format(fmt, options, writer),
+            .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).format(fmt, options, writer),
+            .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).format(fmt, options, writer),
+            .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).format(fmt, options, writer),
+            .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).format(fmt, options, writer),
+            .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).format(fmt, options, writer),
+            .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).format(fmt, options, writer),
+            .signed => @fieldParentPtr(x86_64.Signed, "base", base).format(fmt, options, writer),
+            .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).format(fmt, options, writer),
+            .got => @fieldParentPtr(x86_64.Got, "base", base).format(fmt, options, writer),
+            .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).format(fmt, options, writer),
+        };
 
-        pub fn fromReloc(reloc: macho.relocation_info) Target {
-            return if (reloc.r_extern == 1) .{
-                .symbol = reloc.r_symbolnum,
-            } else .{
-                .section = @intCast(u16, reloc.r_symbolnum - 1),
-            };
-        }
-    };
+        try std.fmt.format(writer, "}}", .{});
+    }
 };
 
 pub const Unsigned = struct {
     base: Relocation,
-    subtractor: ?Relocation.Target = null,
+    subtractor: ?*Symbol = null,
     /// Addend embedded directly in the relocation slot
     addend: i64,
     /// Extracted from r_length:
@@ -106,75 +103,47 @@ pub const Unsigned = struct {
 
     pub const base_type: Relocation.Type = .unsigned;
 
-    pub fn resolve(unsigned: Unsigned, args: Relocation.ResolveArgs) !void {
-        const addend = if (unsigned.base.target == .section)
-            unsigned.addend - @intCast(i64, args.source_target_sect_addr.?)
-        else
-            unsigned.addend;
-
-        const result = if (args.subtractor) |subtractor|
-            @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
-        else
-            @intCast(i64, args.target_addr) + addend;
-
-        log.debug("    | calculated addend 0x{x}", .{addend});
-        log.debug("    | calculated unsigned value 0x{x}", .{result});
-
-        if (unsigned.is_64bit) {
-            mem.writeIntLittle(
-                u64,
-                unsigned.base.code[0..8],
-                @bitCast(u64, result),
-            );
-        } else {
-            mem.writeIntLittle(
-                u32,
-                unsigned.base.code[0..4],
-                @truncate(u32, @bitCast(u64, result)),
-            );
+    // pub fn resolve(unsigned: Unsigned) !void {
+    //     const addend = if (unsigned.base.target == .section)
+    //         unsigned.addend - @intCast(i64, args.source_target_sect_addr.?)
+    //     else
+    //         unsigned.addend;
+
+    //     const result = if (args.subtractor) |subtractor|
+    //         @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
+    //     else
+    //         @intCast(i64, args.target_addr) + addend;
+
+    //     log.debug("    | calculated addend 0x{x}", .{addend});
+    //     log.debug("    | calculated unsigned value 0x{x}", .{result});
+
+    //     if (unsigned.is_64bit) {
+    //         mem.writeIntLittle(
+    //             u64,
+    //             unsigned.base.code[0..8],
+    //             @bitCast(u64, result),
+    //         );
+    //     } else {
+    //         mem.writeIntLittle(
+    //             u32,
+    //             unsigned.base.code[0..4],
+    //             @truncate(u32, @bitCast(u64, result)),
+    //         );
+    //     }
+    // }
+
+    pub fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = fmt;
+        _ = options;
+        if (self.subtractor) |sub| {
+            try std.fmt.format(writer, ".subtractor = {}, ", .{sub});
         }
+        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
+        const length: usize = if (self.is_64bit) 8 else 4;
+        try std.fmt.format(writer, ".length = {}, ", .{length});
     }
 };
 
-pub fn parse(
-    allocator: *Allocator,
-    arch: std.Target.Cpu.Arch,
-    code: []u8,
-    relocs: []const macho.relocation_info,
-) ![]*Relocation {
-    var it = RelocIterator{
-        .buffer = relocs,
-    };
-
-    switch (arch) {
-        .aarch64 => {
-            var parser = aarch64.Parser{
-                .allocator = allocator,
-                .it = &it,
-                .code = code,
-                .parsed = std.ArrayList(*Relocation).init(allocator),
-            };
-            defer parser.deinit();
-            try parser.parse();
-
-            return parser.parsed.toOwnedSlice();
-        },
-        .x86_64 => {
-            var parser = x86_64.Parser{
-                .allocator = allocator,
-                .it = &it,
-                .code = code,
-                .parsed = std.ArrayList(*Relocation).init(allocator),
-            };
-            defer parser.deinit();
-            try parser.parse();
-
-            return parser.parsed.toOwnedSlice();
-        },
-        else => unreachable,
-    }
-}
-
 pub const RelocIterator = struct {
     buffer: []const macho.relocation_info,
     index: i32 = -1,
@@ -182,15 +151,7 @@ pub const RelocIterator = struct {
     pub fn next(self: *RelocIterator) ?macho.relocation_info {
         self.index += 1;
         if (self.index < self.buffer.len) {
-            const reloc = self.buffer[@intCast(u32, self.index)];
-            log.debug("relocation", .{});
-            log.debug("    | type = {}", .{reloc.r_type});
-            log.debug("    | offset = {}", .{reloc.r_address});
-            log.debug("    | PC = {}", .{reloc.r_pcrel == 1});
-            log.debug("    | length = {}", .{reloc.r_length});
-            log.debug("    | symbolnum = {}", .{reloc.r_symbolnum});
-            log.debug("    | extern = {}", .{reloc.r_extern == 1});
-            return reloc;
+            return self.buffer[@intCast(u32, self.index)];
         }
         return null;
     }
src/link/MachO/Zld.zig
@@ -135,9 +135,9 @@ const TlvOffset = struct {
 pub const TextBlock = struct {
     local_sym_index: u32,
     aliases: ?[]u32 = null,
-    references: ?[]u32 = null,
+    references: std.AutoArrayHashMap(u32, void),
     code: []u8,
-    relocs: ?[]*Relocation = null,
+    relocs: std.ArrayList(*Relocation),
     size: u64,
     alignment: u32,
     next: ?*TextBlock = null,
@@ -147,15 +147,8 @@ pub const TextBlock = struct {
         if (block.aliases) |aliases| {
             allocator.free(aliases);
         }
-        if (block.references) |references| {
-            allocator.free(references);
-        }
-        for (block.relocs.items) |reloc| {
-            allocator.destroy(reloc);
-        }
-        if (block.relocs) |relocs| {
-            allocator.free(relocs);
-        }
+        block.relocs.deinit();
+        block.references.deinit();
         allocator.free(code);
     }
 
@@ -168,12 +161,19 @@ pub const TextBlock = struct {
                 log.warn("    | {}: {}", .{ index, zld.locals.items[index] });
             }
         }
-        if (self.references) |references| {
+        if (self.references.count() > 0) {
             log.warn("  | References:", .{});
-            for (references) |index| {
+            for (self.references.keys()) |index| {
                 log.warn("    | {}: {}", .{ index, zld.locals.items[index] });
             }
         }
+        log.warn("  | code.len = {}", .{self.code.len});
+        if (self.relocs.items.len > 0) {
+            log.warn("Relocations:", .{});
+            for (self.relocs.items) |rel| {
+                log.warn("  | {}", .{rel});
+            }
+        }
         log.warn("  | size = {}", .{self.size});
         log.warn("  | align = {}", .{self.alignment});
     }
@@ -280,7 +280,6 @@ pub fn link(self: *Zld, files: []const []const u8, output: Output, args: LinkArg
     try self.resolveSymbols();
     try self.parseTextBlocks();
     return error.TODO;
-    // try self.resolveStubsAndGotEntries();
     // try self.updateMetadata();
     // try self.sortSections();
     // try self.addRpaths(args.rpaths);
@@ -1603,7 +1602,9 @@ fn resolveSymbols(self: *Zld) !void {
 
                 block.* = .{
                     .local_sym_index = local_sym_index,
+                    .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
                     .code = code,
+                    .relocs = std.ArrayList(*Relocation).init(self.allocator),
                     .size = size,
                     .alignment = alignment,
                 };
@@ -1624,6 +1625,9 @@ fn resolveSymbols(self: *Zld) !void {
     {
         // Put dyld_stub_binder as an undefined special symbol.
         const symbol = try Symbol.new(self.allocator, "dyld_stub_binder");
+        const index = @intCast(u32, self.got_entries.items.len);
+        symbol.got_index = index;
+        try self.got_entries.append(self.allocator, symbol);
         try self.globals.putNoClobber(self.allocator, symbol.name, symbol);
     }
 
@@ -1699,54 +1703,6 @@ fn parseTextBlocks(self: *Zld) !void {
     }
 }
 
-fn resolveStubsAndGotEntries(self: *Zld) !void {
-    for (self.objects.items) |object| {
-        log.debug("resolving stubs and got entries from {s}", .{object.name});
-
-        for (object.sections.items) |sect| {
-            const relocs = sect.relocs orelse continue;
-            for (relocs) |rel| {
-                switch (rel.@"type") {
-                    .unsigned => continue,
-                    .got_page, .got_page_off, .got_load, .got, .pointer_to_got => {
-                        const sym = object.symbols.items[rel.target.symbol];
-                        if (sym.got_index != null) continue;
-
-                        const index = @intCast(u32, self.got_entries.items.len);
-                        sym.got_index = index;
-                        try self.got_entries.append(self.allocator, sym);
-
-                        log.debug("    | found GOT entry {s}: {*}", .{ sym.name, sym });
-                    },
-                    else => {
-                        if (rel.target != .symbol) continue;
-
-                        const sym = object.symbols.items[rel.target.symbol];
-                        assert(sym.payload != .undef);
-
-                        if (sym.stubs_index != null) continue;
-                        if (sym.payload != .proxy) continue;
-
-                        const index = @intCast(u32, self.stubs.items.len);
-                        sym.stubs_index = index;
-                        try self.stubs.append(self.allocator, sym);
-
-                        log.debug("    | found stub {s}: {*}", .{ sym.name, sym });
-                    },
-                }
-            }
-        }
-    }
-
-    // Finally, put dyld_stub_binder as the final GOT entry
-    const sym = self.globals.get("dyld_stub_binder") orelse unreachable;
-    const index = @intCast(u32, self.got_entries.items.len);
-    sym.got_index = index;
-    try self.got_entries.append(self.allocator, sym);
-
-    log.debug("    | found GOT entry {s}: {*}", .{ sym.name, sym });
-}
-
 fn resolveRelocsAndWriteSections(self: *Zld) !void {
     for (self.objects.items) |object| {
         log.debug("relocating object {s}", .{object.name});