Commit 1beda818e1

Jakub Konka <kubkon@jakubkonka.com>
2021-07-23 11:51:48
macho: re-enable parsing sections into atoms
However, make it default only when building in release modes since it's a prelude to advanced dead code stripping not very useful in debug.
1 parent a4feb97
Changed files (3)
src/link/MachO/Object.zig
@@ -55,7 +55,11 @@ mtime: ?u64 = null,
 
 text_blocks: std.ArrayListUnmanaged(*TextBlock) = .{},
 sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{},
+
+// TODO symbol mapping and its inverse can probably be simple arrays
+// instead of hash maps.
 symbol_mapping: std.AutoHashMapUnmanaged(u32, u32) = .{},
+reverse_symbol_mapping: std.AutoHashMapUnmanaged(u32, u32) = .{},
 
 const DebugInfo = struct {
     inner: dwarf.DwarfInfo,
@@ -164,6 +168,7 @@ pub fn deinit(self: *Object) void {
     self.text_blocks.deinit(self.allocator);
     self.sections_as_symbols.deinit(self.allocator);
     self.symbol_mapping.deinit(self.allocator);
+    self.reverse_symbol_mapping.deinit(self.allocator);
 
     if (self.debug_info) |*db| {
         db.deinit(self.allocator);
@@ -367,7 +372,7 @@ const TextBlockParser = struct {
         } else if (MachO.symbolIsPext(rhs.nlist) or MachO.symbolIsWeakDef(rhs.nlist)) {
             return !MachO.symbolIsExt(lhs.nlist);
         } else {
-            return true;
+            return false;
         }
     }
 
@@ -392,15 +397,7 @@ const TextBlockParser = struct {
         } else null;
 
         for (aliases.items) |*nlist_with_index| {
-            nlist_with_index.index = self.symbol_mapping.get(nlist_with_index.index);
-            const sym = self.object.symbols.items[nlist_with_index.index];
-            if (sym.payload != .regular) {
-                log.err("expected a regular symbol, found {s}", .{sym.payload});
-                log.err("  when remapping {s}", .{self.macho_file.getString(sym.strx)});
-                return error.SymbolIsNotRegular;
-            }
-            assert(sym.payload.regular.local_sym_index != 0); // This means the symbol has not been properly resolved.
-            nlist_with_index.index = sym.payload.regular.local_sym_index;
+            nlist_with_index.index = self.object.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
         }
 
         if (aliases.items.len > 1) {
@@ -409,15 +406,13 @@ const TextBlockParser = struct {
                 NlistWithIndex,
                 aliases.items,
                 SeniorityContext{ .object = self.object },
-                @This().lessThanBySeniority,
+                TextBlockParser.lessThanBySeniority,
             );
         }
 
         const senior_nlist = aliases.pop();
-        const senior_sym = self.macho_file.locals.items[senior_nlist.index];
-        assert(senior_sym.payload == .regular);
-        senior_sym.payload.regular.segment_id = self.match.seg;
-        senior_sym.payload.regular.section_id = self.match.sect;
+        const senior_sym = &self.macho_file.locals.items[senior_nlist.index];
+        senior_sym.n_sect = self.macho_file.section_to_ordinal.get(self.match) orelse unreachable;
 
         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;
@@ -442,33 +437,29 @@ const TextBlockParser = struct {
                     }
                 }
             }
-            if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global;
+            // TODO
+            // if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global;
             break :blk .static;
         } else null;
 
-        const block = try self.allocator.create(TextBlock);
-        errdefer self.allocator.destroy(block);
-
-        block.* = TextBlock.init(self.allocator);
+        const block = try self.macho_file.base.allocator.create(TextBlock);
+        block.* = TextBlock.empty;
         block.local_sym_index = senior_nlist.index;
         block.stab = stab;
-        block.code = try self.allocator.dupe(u8, code);
         block.size = size;
         block.alignment = actual_align;
+        try self.macho_file.managed_blocks.append(self.macho_file.base.allocator, block);
 
-        if (aliases.items.len > 0) {
-            try block.aliases.ensureTotalCapacity(aliases.items.len);
-            for (aliases.items) |alias| {
-                block.aliases.appendAssumeCapacity(alias.index);
+        try block.code.appendSlice(self.macho_file.base.allocator, code);
 
-                const sym = self.macho_file.locals.items[alias.index];
-                const reg = &sym.payload.regular;
-                reg.segment_id = self.match.seg;
-                reg.section_id = self.match.sect;
-            }
+        try block.aliases.ensureTotalCapacity(self.macho_file.base.allocator, aliases.items.len);
+        for (aliases.items) |alias| {
+            block.aliases.appendAssumeCapacity(alias.index);
+            const sym = &self.macho_file.locals.items[alias.index];
+            sym.n_sect = self.macho_file.section_to_ordinal.get(self.match) orelse unreachable;
         }
 
-        try block.parseRelocsFromObject(self.allocator, relocs, object, .{
+        try block.parseRelocsFromObject(self.macho_file.base.allocator, self.relocs, self.object, .{
             .base_addr = start_addr,
             .macho_file = self.macho_file,
         });
@@ -479,7 +470,7 @@ const TextBlockParser = struct {
                 senior_nlist.nlist.n_value,
                 senior_nlist.nlist.n_value + size,
             );
-            try block.dices.ensureTotalCapacity(dices.len);
+            try block.dices.ensureTotalCapacity(self.macho_file.base.allocator, dices.len);
 
             for (dices) |dice| {
                 block.dices.appendAssumeCapacity(.{
@@ -518,10 +509,22 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
 
     sort.sort(NlistWithIndex, sorted_all_nlists.items, {}, NlistWithIndex.lessThan);
 
-    const dysymtab = self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
+    // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
+    // have to infer the start of undef section in the symtab ourselves.
+    const iundefsym = if (self.dysymtab_cmd_index) |cmd_index| blk: {
+        const dysymtab = self.load_commands.items[cmd_index].Dysymtab;
+        break :blk dysymtab.iundefsym;
+    } else blk: {
+        var iundefsym: usize = sorted_all_nlists.items.len;
+        while (iundefsym > 0) : (iundefsym -= 1) {
+            const nlist = sorted_all_nlists.items[iundefsym];
+            if (MachO.symbolIsSect(nlist.nlist)) break;
+        }
+        break :blk iundefsym;
+    };
 
     // We only care about defined symbols, so filter every other out.
-    const sorted_nlists = sorted_all_nlists.items[dysymtab.ilocalsym..dysymtab.iundefsym];
+    const sorted_nlists = sorted_all_nlists.items[0..iundefsym];
 
     for (seg.sections.items) |sect, id| {
         const sect_id = @intCast(u8, id);
@@ -550,11 +553,12 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
         // Symbols within this section only.
         const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect);
 
-        // Is there any padding between symbols within the section?
-        // const is_splittable = self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
-        // TODO is it perhaps worth skip parsing subsections in Debug mode and not worry about
-        // duplicates at all? Need some benchmarks!
-        // const is_splittable = false;
+        // In release mode, if the object file was generated with dead code stripping optimisations,
+        // note it now and parse sections as atoms.
+        const is_splittable = blk: {
+            if (macho_file.base.options.optimize_mode == .Debug) break :blk false;
+            break :blk self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
+        };
 
         macho_file.has_dices = blk: {
             if (self.text_section_index) |index| {
@@ -566,157 +570,152 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
         };
         macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;
 
-        {
-            // next: {
-            // if (is_splittable) blocks: {
-            //     if (filtered_nlists.len == 0) break :blocks;
-
-            //     // If the first nlist does not match the start of the section,
-            //     // then we need encapsulate the memory range [section start, first symbol)
-            //     // as a temporary symbol and insert the matching TextBlock.
-            //     const first_nlist = filtered_nlists[0].nlist;
-            //     if (first_nlist.n_value > sect.addr) {
-            //         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 zld.allocator.create(Symbol);
-            //             symbol.* = .{
-            //                 .strx = try zld.makeString(name),
-            //                 .payload = .{ .undef = .{} },
-            //             };
-            //             try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
-            //             break :symbol symbol;
-            //         };
-
-            //         const local_sym_index = @intCast(u32, zld.locals.items.len);
-            //         symbol.payload = .{
-            //             .regular = .{
-            //                 .linkage = .translation_unit,
-            //                 .address = sect.addr,
-            //                 .segment_id = match.seg,
-            //                 .section_id = match.sect,
-            //                 .file = self,
-            //                 .local_sym_index = local_sym_index,
-            //             },
-            //         };
-            //         try zld.locals.append(zld.allocator, symbol);
-
-            //         const block_code = code[0 .. first_nlist.n_value - sect.addr];
-            //         const block_size = block_code.len;
-
-            //         const block = try self.allocator.create(TextBlock);
-            //         errdefer self.allocator.destroy(block);
-
-            //         block.* = TextBlock.init(self.allocator);
-            //         block.local_sym_index = local_sym_index;
-            //         block.code = try self.allocator.dupe(u8, block_code);
-            //         block.size = block_size;
-            //         block.alignment = sect.@"align";
-
-            //         const block_relocs = filterRelocs(relocs, 0, block_size);
-            //         if (block_relocs.len > 0) {
-            //             try self.parseRelocs(zld, block_relocs, block, 0);
-            //         }
-
-            //         if (zld.has_dices) {
-            //             const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + block_size);
-            //             try block.dices.ensureTotalCapacity(dices.len);
-
-            //             for (dices) |dice| {
-            //                 block.dices.appendAssumeCapacity(.{
-            //                     .offset = dice.offset - try math.cast(u32, sect.addr),
-            //                     .length = dice.length,
-            //                     .kind = dice.kind,
-            //                 });
-            //             }
-            //         }
-
-            //         // Update target section's metadata
-            //         // TODO should we update segment's size here too?
-            //         // How does it tie with incremental space allocs?
-            //         const tseg = &zld.load_commands.items[match.seg].Segment;
-            //         const tsect = &tseg.sections.items[match.sect];
-            //         const new_alignment = math.max(tsect.@"align", block.alignment);
-            //         const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
-            //         const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
-            //         tsect.size = new_size;
-            //         tsect.@"align" = new_alignment;
-
-            //         if (zld.blocks.getPtr(match)) |last| {
-            //             last.*.next = block;
-            //             block.prev = last.*;
-            //             last.* = block;
-            //         } else {
-            //             try zld.blocks.putNoClobber(zld.allocator, match, block);
-            //         }
-
-            //         try self.text_blocks.append(self.allocator, block);
-            //     }
-
-            //     var parser = TextBlockParser{
-            //         .allocator = self.allocator,
-            //         .section = sect,
-            //         .code = code,
-            //         .relocs = relocs,
-            //         .object = self,
-            //         .zld = zld,
-            //         .nlists = filtered_nlists,
-            //         .match = match,
-            //     };
-
-            //     while (try parser.next()) |block| {
-            //         const sym = zld.locals.items[block.local_sym_index];
-            //         const reg = &sym.payload.regular;
-            //         if (reg.file) |file| {
-            //             if (file != self) {
-            //                 log.debug("deduping definition of {s} in {s}", .{ zld.getString(sym.strx), self.name.? });
-            //                 block.deinit();
-            //                 self.allocator.destroy(block);
-            //                 continue;
-            //             }
-            //         }
-
-            //         if (reg.address == sect.addr) {
-            //             if (self.sections_as_symbols.get(sect_id)) |alias| {
-            //                 // Add alias.
-            //                 const local_sym_index = @intCast(u32, zld.locals.items.len);
-            //                 const reg_alias = &alias.payload.regular;
-            //                 reg_alias.segment_id = match.seg;
-            //                 reg_alias.section_id = match.sect;
-            //                 reg_alias.local_sym_index = local_sym_index;
-            //                 try block.aliases.append(local_sym_index);
-            //                 try zld.locals.append(zld.allocator, alias);
-            //             }
-            //         }
-
-            //         // Update target section's metadata
-            //         // TODO should we update segment's size here too?
-            //         // How does it tie with incremental space allocs?
-            //         const tseg = &zld.load_commands.items[match.seg].Segment;
-            //         const tsect = &tseg.sections.items[match.sect];
-            //         const new_alignment = math.max(tsect.@"align", block.alignment);
-            //         const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
-            //         const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
-            //         tsect.size = new_size;
-            //         tsect.@"align" = new_alignment;
-
-            //         if (zld.blocks.getPtr(match)) |last| {
-            //             last.*.next = block;
-            //             block.prev = last.*;
-            //             last.* = block;
-            //         } else {
-            //             try zld.blocks.putNoClobber(zld.allocator, match, block);
-            //         }
-
-            //         try self.text_blocks.append(self.allocator, block);
-            //     }
-
-            //     break :next;
-            // }
+        next: {
+            if (is_splittable) blocks: {
+                if (filtered_nlists.len == 0) break :blocks;
+
+                // If the first nlist does not match the start of the section,
+                // then we need to encapsulate the memory range [section start, first symbol)
+                // as a temporary symbol and insert the matching TextBlock.
+                const first_nlist = filtered_nlists[0].nlist;
+                if (first_nlist.n_value > sect.addr) {
+                    const sym_name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
+                        self.name.?,
+                        segmentName(sect),
+                        sectionName(sect),
+                    });
+                    defer self.allocator.free(sym_name);
+
+                    const block_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
+                        const block_local_sym_index = @intCast(u32, macho_file.locals.items.len);
+                        try macho_file.locals.append(macho_file.base.allocator, .{
+                            .n_strx = try macho_file.makeString(sym_name),
+                            .n_type = macho.N_SECT,
+                            .n_sect = macho_file.section_to_ordinal.get(match) orelse unreachable,
+                            .n_desc = 0,
+                            .n_value = sect.addr,
+                        });
+                        try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, block_local_sym_index);
+                        break :blk block_local_sym_index;
+                    };
+
+                    const block_code = code[0 .. first_nlist.n_value - sect.addr];
+                    const block_size = block_code.len;
+
+                    const block = try macho_file.base.allocator.create(TextBlock);
+                    block.* = TextBlock.empty;
+                    block.local_sym_index = block_local_sym_index;
+                    block.size = block_size;
+                    block.alignment = sect.@"align";
+                    try macho_file.managed_blocks.append(macho_file.base.allocator, block);
+
+                    try block.code.appendSlice(macho_file.base.allocator, block_code);
+
+                    try block.parseRelocsFromObject(self.allocator, relocs, self, .{
+                        .base_addr = 0,
+                        .macho_file = macho_file,
+                    });
+
+                    if (macho_file.has_dices) {
+                        const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + block_size);
+                        try block.dices.ensureTotalCapacity(macho_file.base.allocator, dices.len);
+
+                        for (dices) |dice| {
+                            block.dices.appendAssumeCapacity(.{
+                                .offset = dice.offset - try math.cast(u32, sect.addr),
+                                .length = dice.length,
+                                .kind = dice.kind,
+                            });
+                        }
+                    }
+
+                    // Update target section's metadata
+                    // TODO should we update segment's size here too?
+                    // How does it tie with incremental space allocs?
+                    const tseg = &macho_file.load_commands.items[match.seg].Segment;
+                    const tsect = &tseg.sections.items[match.sect];
+                    const new_alignment = math.max(tsect.@"align", block.alignment);
+                    const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
+                    const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
+                    tsect.size = new_size;
+                    tsect.@"align" = new_alignment;
+
+                    if (macho_file.blocks.getPtr(match)) |last| {
+                        last.*.next = block;
+                        block.prev = last.*;
+                        last.* = block;
+                    } else {
+                        try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
+                    }
+
+                    try self.text_blocks.append(self.allocator, block);
+                }
+
+                var parser = TextBlockParser{
+                    .allocator = self.allocator,
+                    .section = sect,
+                    .code = code,
+                    .relocs = relocs,
+                    .object = self,
+                    .macho_file = macho_file,
+                    .nlists = filtered_nlists,
+                    .match = match,
+                };
+
+                while (try parser.next()) |block| {
+                    const sym = macho_file.locals.items[block.local_sym_index];
+                    const is_ext = blk: {
+                        const orig_sym_id = self.reverse_symbol_mapping.get(block.local_sym_index) orelse unreachable;
+                        break :blk MachO.symbolIsExt(self.symtab.items[orig_sym_id]);
+                    };
+                    if (is_ext) {
+                        if (macho_file.symbol_resolver.get(sym.n_strx)) |resolv| {
+                            assert(resolv.where == .global);
+                            const global_object = macho_file.objects.items[resolv.file];
+                            if (global_object != self) {
+                                log.debug("deduping definition of {s} in {s}", .{
+                                    macho_file.getString(sym.n_strx),
+                                    self.name.?,
+                                });
+                                log.debug("  already defined in {s}", .{global_object.name.?});
+                                continue;
+                            }
+                        }
+                    }
+
+                    if (sym.n_value == sect.addr) {
+                        if (self.sections_as_symbols.get(sect_id)) |alias| {
+                            // In x86_64 relocs, it can so happen that the compiler refers to the same
+                            // atom by both the actual assigned symbol and the start of the section. In this
+                            // case, we need to link the two together so add an alias.
+                            try block.aliases.append(macho_file.base.allocator, alias);
+                        }
+                    }
+
+                    // Update target section's metadata
+                    // TODO should we update segment's size here too?
+                    // How does it tie with incremental space allocs?
+                    const tseg = &macho_file.load_commands.items[match.seg].Segment;
+                    const tsect = &tseg.sections.items[match.sect];
+                    const new_alignment = math.max(tsect.@"align", block.alignment);
+                    const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
+                    const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
+                    tsect.size = new_size;
+                    tsect.@"align" = new_alignment;
+
+                    if (macho_file.blocks.getPtr(match)) |last| {
+                        last.*.next = block;
+                        block.prev = last.*;
+                        last.* = block;
+                    } else {
+                        try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
+                    }
+
+                    try self.text_blocks.append(self.allocator, block);
+                }
+
+                break :next;
+            }
 
             // Since there is no symbol to refer to this block, we create
             // a temp one, unless we already did that when working out the relocations
@@ -757,7 +756,7 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
 
             if (macho_file.has_dices) {
                 const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size);
-                try block.dices.ensureTotalCapacity(self.allocator, dices.len);
+                try block.dices.ensureTotalCapacity(macho_file.base.allocator, dices.len);
 
                 for (dices) |dice| {
                     block.dices.appendAssumeCapacity(.{
@@ -820,7 +819,7 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
                 block.prev = last.*;
                 last.* = block;
             } else {
-                try macho_file.blocks.putNoClobber(self.allocator, match, block);
+                try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
             }
 
             try self.text_blocks.append(self.allocator, block);
src/link/MachO/TextBlock.zig
@@ -75,6 +75,21 @@ pub const SymbolAtOffset = struct {
     local_sym_index: u32,
     offset: u64,
     stab: ?Stab = null,
+
+    pub fn format(
+        self: SymbolAtOffset,
+        comptime fmt: []const u8,
+        options: std.fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        _ = fmt;
+        _ = options;
+        try std.fmt.format(writer, "{{ {d}: .offset = {d}", .{ self.local_sym_index, self.offset });
+        if (self.stab) |stab| {
+            try std.fmt.format(writer, ", .stab = {any}", .{stab});
+        }
+        try std.fmt.format(writer, " }}", .{});
+    }
 };
 
 pub const Stab = union(enum) {
@@ -1150,53 +1165,24 @@ pub fn resolveRelocs(self: *TextBlock, macho_file: *MachO) !void {
     }
 }
 
-pub fn print_this(self: *const TextBlock, macho_file: MachO) void {
-    log.warn("TextBlock", .{});
-    log.warn("  {}: {}", .{ self.local_sym_index, macho_file.locals.items[self.local_sym_index] });
+pub fn format(self: TextBlock, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+    _ = fmt;
+    _ = options;
+    try std.fmt.format(writer, "TextBlock {{ ", .{});
+    try std.fmt.format(writer, ".local_sym_index = {d}, ", .{self.local_sym_index});
+    try std.fmt.format(writer, ".aliases = {any}, ", .{self.aliases.items});
+    try std.fmt.format(writer, ".contained = {any}, ", .{self.contained.items});
+    try std.fmt.format(writer, ".code = {*}, ", .{self.code.items});
+    try std.fmt.format(writer, ".size = {d}, ", .{self.size});
+    try std.fmt.format(writer, ".alignment = {d}, ", .{self.alignment});
+    try std.fmt.format(writer, ".relocs = {any}, ", .{self.relocs.items});
+    try std.fmt.format(writer, ".rebases = {any}, ", .{self.rebases.items});
+    try std.fmt.format(writer, ".bindings = {any}, ", .{self.bindings.items});
+    try std.fmt.format(writer, ".dices = {any}, ", .{self.dices.items});
     if (self.stab) |stab| {
-        log.warn("  stab: {}", .{stab});
-    }
-    if (self.aliases.items.len > 0) {
-        log.warn("  aliases: {any}", .{self.aliases.items});
-    }
-    if (self.references.count() > 0) {
-        log.warn("  references: {any}", .{self.references.keys()});
-    }
-    if (self.contained) |contained| {
-        log.warn("  contained symbols:", .{});
-        for (contained) |sym_at_off| {
-            if (sym_at_off.stab) |stab| {
-                log.warn("    {}: {}, stab: {}", .{ sym_at_off.offset, sym_at_off.local_sym_index, stab });
-            } else {
-                log.warn("    {}: {}", .{ sym_at_off.offset, sym_at_off.local_sym_index });
-            }
-        }
-    }
-    log.warn("  code.len = {}", .{self.code.items.len});
-    if (self.relocs.items.len > 0) {
-        log.warn("  relocations:", .{});
-        for (self.relocs.items) |rel| {
-            log.warn("    {}", .{rel});
-        }
-    }
-    if (self.rebases.items.len > 0) {
-        log.warn("  rebases: {any}", .{self.rebases.items});
-    }
-    if (self.bindings.items.len > 0) {
-        log.warn("  bindings: {any}", .{self.bindings.items});
-    }
-    if (self.dices.items.len > 0) {
-        log.warn("  dices: {any}", .{self.dices.items});
-    }
-    log.warn("  size = {}", .{self.size});
-    log.warn("  align = {}", .{self.alignment});
-}
-
-pub fn print(self: *const TextBlock, macho_file: MachO) void {
-    if (self.prev) |prev| {
-        prev.print(macho_file);
+        try std.fmt.format(writer, ".stab = {any}, ", .{stab});
     }
-    self.print_this(macho_file);
+    try std.fmt.format(writer, "}}", .{});
 }
 
 const RelocIterator = struct {
src/link/MachO.zig
@@ -965,60 +965,7 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
         try self.allocateDataSegment();
         self.allocateLinkeditSegment();
         try self.allocateTextBlocks();
-
-        // log.warn("locals", .{});
-        // for (self.locals.items) |sym, id| {
-        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-        // }
-
-        // log.warn("globals", .{});
-        // for (self.globals.items) |sym, id| {
-        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-        // }
-
-        // log.warn("tentatives", .{});
-        // for (self.tentatives.items) |sym, id| {
-        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-        // }
-
-        // log.warn("undefines", .{});
-        // for (self.undefs.items) |sym, id| {
-        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-        // }
-
-        // log.warn("imports", .{});
-        // for (self.imports.items) |sym, id| {
-        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-        // }
-
-        // log.warn("symbol resolver", .{});
-        // for (self.symbol_resolver.keys()) |key| {
-        //     log.warn("  {s} => {}", .{ key, self.symbol_resolver.get(key).? });
-        // }
-
-        // log.warn("mappings", .{});
-        // for (self.objects.items) |object, id| {
-        //     const object_id = @intCast(u16, id);
-        //     log.warn("  in object {s}", .{object.name.?});
-        //     for (object.symtab.items) |sym, sym_id| {
-        //         if (object.symbol_mapping.get(@intCast(u32, sym_id))) |local_id| {
-        //             log.warn("    | {d} => {d}", .{ sym_id, local_id });
-        //         } else {
-        //             log.warn("    | {d} no local mapping for {s}", .{ sym_id, object.getString(sym.n_strx) });
-        //         }
-        //     }
-        // }
-
-        // var it = self.blocks.iterator();
-        // while (it.next()) |entry| {
-        //     const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
-        //     const sect = seg.sections.items[entry.key_ptr.sect];
-
-        //     log.warn("\n\n{s},{s} contents:", .{ segmentName(sect), sectionName(sect) });
-        //     log.warn("  {}", .{sect});
-        //     entry.value_ptr.*.print(self);
-        // }
-
+        self.printSymtabAndTextBlock();
         try self.flushZld();
     }
 
@@ -2086,6 +2033,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
                 .n_value = sym.n_value,
             });
             try object.symbol_mapping.putNoClobber(self.base.allocator, sym_id, local_sym_index);
+            try object.reverse_symbol_mapping.putNoClobber(self.base.allocator, local_sym_index, sym_id);
 
             // If the symbol's scope is not local aka translation unit, then we need work out
             // if we should save the symbol as a global, or potentially flag the error.
@@ -5916,3 +5864,70 @@ fn createSectionOrdinal(self: *MachO, match: MatchingSection) !void {
     try self.section_ordinals.append(self.base.allocator, match);
     try self.section_to_ordinal.putNoClobber(self.base.allocator, match, ordinal);
 }
+
+fn printSymtabAndTextBlock(self: *MachO) void {
+    log.debug("locals", .{});
+    for (self.locals.items) |sym, id| {
+        log.debug("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+    }
+
+    log.debug("globals", .{});
+    for (self.globals.items) |sym, id| {
+        log.debug("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+    }
+
+    log.debug("tentatives", .{});
+    for (self.tentatives.items) |sym, id| {
+        log.debug("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+    }
+
+    log.debug("undefines", .{});
+    for (self.undefs.items) |sym, id| {
+        log.debug("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+    }
+
+    log.debug("imports", .{});
+    for (self.imports.items) |sym, id| {
+        log.debug("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+    }
+
+    {
+        log.debug("symbol resolver", .{});
+        var it = self.symbol_resolver.keyIterator();
+        while (it.next()) |key_ptr| {
+            const sym_name = self.getString(key_ptr.*);
+            log.debug("  {s} => {}", .{ sym_name, self.symbol_resolver.get(key_ptr.*).? });
+        }
+    }
+
+    log.debug("mappings", .{});
+    for (self.objects.items) |object| {
+        log.debug("  in object {s}", .{object.name.?});
+        for (object.symtab.items) |sym, sym_id| {
+            if (object.symbol_mapping.get(@intCast(u32, sym_id))) |local_id| {
+                log.debug("    | {d} => {d}", .{ sym_id, local_id });
+            } else {
+                log.debug("    | {d} no local mapping for {s}", .{ sym_id, object.getString(sym.n_strx) });
+            }
+        }
+    }
+
+    {
+        var it = self.blocks.iterator();
+        while (it.next()) |entry| {
+            const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
+            const sect = seg.sections.items[entry.key_ptr.sect];
+
+            var block: *TextBlock = entry.value_ptr.*;
+
+            log.debug("\n\n{s},{s} contents:", .{ commands.segmentName(sect), commands.sectionName(sect) });
+            log.debug("{}", .{sect});
+            log.debug("{}", .{block});
+
+            while (block.prev) |prev| {
+                block = prev;
+                log.debug("{}", .{block});
+            }
+        }
+    }
+}