Commit d6e095de2c

Jakub Konka <kubkon@jakubkonka.com>
2023-08-02 21:45:29
macho: track unwind/dwarf cfi records by symbol rather than atom
This solves the nuance case of compiling hand-crafted assembly files which do not feature `MH_SUBSECTIONS_VIA_SYMBOLS` flag resulting in input `Atom`s encompassing multiple symbols each with unique unwind information.
1 parent 4d7dd16
src/link/MachO/dead_strip.zig
@@ -294,124 +294,131 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
     const unwind_records = object.getUnwindRecords();
 
     for (object.exec_atoms.items) |atom_index| {
+        var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+
         if (!object.hasUnwindRecords()) {
-            if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
-                const ptr = object.eh_frame_relocs_lookup.getPtr(fde_offset).?;
-                if (ptr.dead) continue; // already marked
-                if (!alive.contains(atom_index)) {
-                    // Mark dead and continue.
-                    ptr.dead = true;
-                } else {
-                    // Mark references live and continue.
-                    try markEhFrameRecord(zld, object_id, atom_index, alive);
+            if (alive.contains(atom_index)) {
+                // Mark references live and continue.
+                try markEhFrameRecords(zld, object_id, atom_index, alive);
+            } else {
+                while (inner_syms_it.next()) |sym| {
+                    if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
+                        // Mark dead and continue.
+                        object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
+                    }
                 }
-                continue;
             }
+            continue;
         }
 
-        const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;
-        if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
-        if (!alive.contains(atom_index)) {
-            // Mark the record dead and continue.
-            object.unwind_relocs_lookup[record_id].dead = true;
-            if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
-                object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
+        while (inner_syms_it.next()) |sym| {
+            const record_id = object.unwind_records_lookup.get(sym) orelse continue;
+            if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
+            if (!alive.contains(atom_index)) {
+                // Mark the record dead and continue.
+                object.unwind_relocs_lookup[record_id].dead = true;
+                if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
+                    object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
+                }
+                continue;
             }
-            continue;
-        }
 
-        const record = unwind_records[record_id];
-        if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-            try markEhFrameRecord(zld, object_id, atom_index, alive);
-        } else {
-            if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
-                const target = Atom.parseRelocTarget(zld, .{
-                    .object_id = object_id,
-                    .rel = rel,
-                    .code = mem.asBytes(&record),
-                    .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
-                });
-                const target_sym = zld.getSymbol(target);
-                if (!target_sym.undf()) {
+            const record = unwind_records[record_id];
+            if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
+                try markEhFrameRecords(zld, object_id, atom_index, alive);
+            } else {
+                if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
+                    const target = Atom.parseRelocTarget(zld, .{
+                        .object_id = object_id,
+                        .rel = rel,
+                        .code = mem.asBytes(&record),
+                        .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
+                    });
+                    const target_sym = zld.getSymbol(target);
+                    if (!target_sym.undf()) {
+                        const target_object = zld.objects.items[target.getFile().?];
+                        const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
+                        markLive(zld, target_atom_index, alive);
+                    }
+                }
+
+                if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
+                    const target = Atom.parseRelocTarget(zld, .{
+                        .object_id = object_id,
+                        .rel = rel,
+                        .code = mem.asBytes(&record),
+                        .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
+                    });
                     const target_object = zld.objects.items[target.getFile().?];
                     const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
                     markLive(zld, target_atom_index, alive);
                 }
             }
-
-            if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
-                const target = Atom.parseRelocTarget(zld, .{
-                    .object_id = object_id,
-                    .rel = rel,
-                    .code = mem.asBytes(&record),
-                    .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
-                });
-                const target_object = zld.objects.items[target.getFile().?];
-                const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-                markLive(zld, target_atom_index, alive);
-            }
         }
     }
 }
 
-fn markEhFrameRecord(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
+fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
     const cpu_arch = zld.options.target.cpu.arch;
     const object = &zld.objects.items[object_id];
     var it = object.getEhFrameRecordsIterator();
-
-    const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
-    it.seekTo(fde_offset);
-    const fde = (try it.next()).?;
-
-    const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
-    const cie_offset = fde_offset + 4 - cie_ptr;
-    it.seekTo(cie_offset);
-    const cie = (try it.next()).?;
-
-    switch (cpu_arch) {
-        .aarch64 => {
-            // Mark FDE references which should include any referenced LSDA record
-            const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
-            for (relocs) |rel| {
-                const target = Atom.parseRelocTarget(zld, .{
-                    .object_id = object_id,
-                    .rel = rel,
-                    .code = fde.data,
-                    .base_offset = @as(i32, @intCast(fde_offset)) + 4,
+    var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+
+    while (inner_syms_it.next()) |sym| {
+        const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
+        it.seekTo(fde_offset);
+        const fde = (try it.next()).?;
+
+        const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
+        const cie_offset = fde_offset + 4 - cie_ptr;
+        it.seekTo(cie_offset);
+        const cie = (try it.next()).?;
+
+        switch (cpu_arch) {
+            .aarch64 => {
+                // Mark FDE references which should include any referenced LSDA record
+                const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
+                for (relocs) |rel| {
+                    const target = Atom.parseRelocTarget(zld, .{
+                        .object_id = object_id,
+                        .rel = rel,
+                        .code = fde.data,
+                        .base_offset = @as(i32, @intCast(fde_offset)) + 4,
+                    });
+                    const target_sym = zld.getSymbol(target);
+                    if (!target_sym.undf()) blk: {
+                        const target_object = zld.objects.items[target.getFile().?];
+                        const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
+                            break :blk;
+                        markLive(zld, target_atom_index, alive);
+                    }
+                }
+            },
+            .x86_64 => {
+                const sect = object.getSourceSection(object.eh_frame_sect_id.?);
+                const lsda_ptr = try fde.getLsdaPointer(cie, .{
+                    .base_addr = sect.addr,
+                    .base_offset = fde_offset,
                 });
-                const target_sym = zld.getSymbol(target);
-                if (!target_sym.undf()) blk: {
-                    const target_object = zld.objects.items[target.getFile().?];
-                    const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
-                        break :blk;
+                if (lsda_ptr) |lsda_address| {
+                    // Mark LSDA record as live
+                    const sym_index = object.getSymbolByAddress(lsda_address, null);
+                    const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
                     markLive(zld, target_atom_index, alive);
                 }
-            }
-        },
-        .x86_64 => {
-            const sect = object.getSourceSection(object.eh_frame_sect_id.?);
-            const lsda_ptr = try fde.getLsdaPointer(cie, .{
-                .base_addr = sect.addr,
-                .base_offset = fde_offset,
-            });
-            if (lsda_ptr) |lsda_address| {
-                // Mark LSDA record as live
-                const sym_index = object.getSymbolByAddress(lsda_address, null);
-                const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
+            },
+            else => unreachable,
+        }
+
+        // Mark CIE references which should include any referenced personalities
+        // that are defined locally.
+        if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
+            const target_sym = zld.getSymbol(target);
+            if (!target_sym.undf()) {
+                const target_object = zld.objects.items[target.getFile().?];
+                const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
                 markLive(zld, target_atom_index, alive);
             }
-        },
-        else => unreachable,
-    }
-
-    // Mark CIE references which should include any referenced personalities
-    // that are defined locally.
-    if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
-        const target_sym = zld.getSymbol(target);
-        if (!target_sym.undf()) {
-            const target_object = zld.objects.items[target.getFile().?];
-            const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-            markLive(zld, target_atom_index, alive);
         }
     }
 }
src/link/MachO/eh_frame.zig
@@ -24,19 +24,22 @@ pub fn scanRelocs(zld: *Zld) !void {
         var it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            const fde_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
-            if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
-            it.seekTo(fde_offset);
-            const fde = (try it.next()).?;
-
-            const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
-            const cie_offset = fde_offset + 4 - cie_ptr;
-
-            if (!cies.contains(cie_offset)) {
-                try cies.putNoClobber(cie_offset, {});
-                it.seekTo(cie_offset);
-                const cie = (try it.next()).?;
-                try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            while (inner_syms_it.next()) |sym| {
+                const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
+                if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
+                it.seekTo(fde_offset);
+                const fde = (try it.next()).?;
+
+                const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
+                const cie_offset = fde_offset + 4 - cie_ptr;
+
+                if (!cies.contains(cie_offset)) {
+                    try cies.putNoClobber(cie_offset, {});
+                    it.seekTo(cie_offset);
+                    const cie = (try it.next()).?;
+                    try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
+                }
             }
         }
     }
@@ -59,35 +62,38 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
         var eh_it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            const fde_record_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
-            if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
-
-            const record_id = unwind_info.records_lookup.get(atom_index) orelse continue;
-            const record = unwind_info.records.items[record_id];
-
-            // TODO skip this check if no __compact_unwind is present
-            const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
-            if (!is_dwarf) continue;
-
-            eh_it.seekTo(fde_record_offset);
-            const source_fde_record = (try eh_it.next()).?;
-
-            const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
-            const cie_offset = fde_record_offset + 4 - cie_ptr;
+            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            while (inner_syms_it.next()) |sym| {
+                const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
+                if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
+
+                const record_id = unwind_info.records_lookup.get(sym) orelse continue;
+                const record = unwind_info.records.items[record_id];
+
+                // TODO skip this check if no __compact_unwind is present
+                const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
+                if (!is_dwarf) continue;
+
+                eh_it.seekTo(fde_record_offset);
+                const source_fde_record = (try eh_it.next()).?;
+
+                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
+                const cie_offset = fde_record_offset + 4 - cie_ptr;
+
+                const gop = try cies.getOrPut(cie_offset);
+                if (!gop.found_existing) {
+                    eh_it.seekTo(cie_offset);
+                    const source_cie_record = (try eh_it.next()).?;
+                    gop.value_ptr.* = size;
+                    size += source_cie_record.getSize();
+                }
 
-            const gop = try cies.getOrPut(cie_offset);
-            if (!gop.found_existing) {
-                eh_it.seekTo(cie_offset);
-                const source_cie_record = (try eh_it.next()).?;
-                gop.value_ptr.* = size;
-                size += source_cie_record.getSize();
+                size += source_fde_record.getSize();
             }
-
-            size += source_fde_record.getSize();
         }
-    }
 
-    sect.size = size;
+        sect.size = size;
+    }
 }
 
 pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
@@ -118,97 +124,99 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
         var eh_it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            const fde_record_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
-            if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
-
-            const record_id = unwind_info.records_lookup.get(atom_index) orelse continue;
-            const record = &unwind_info.records.items[record_id];
-
-            // TODO skip this check if no __compact_unwind is present
-            const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
-            if (!is_dwarf) continue;
-
-            eh_it.seekTo(fde_record_offset);
-            const source_fde_record = (try eh_it.next()).?;
-
-            const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
-            const cie_offset = fde_record_offset + 4 - cie_ptr;
+            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            while (inner_syms_it.next()) |target| {
+                const fde_record_offset = object.eh_frame_records_lookup.get(target) orelse continue;
+                if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
+
+                const record_id = unwind_info.records_lookup.get(target) orelse continue;
+                const record = &unwind_info.records.items[record_id];
+
+                // TODO skip this check if no __compact_unwind is present
+                const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
+                if (!is_dwarf) continue;
+
+                eh_it.seekTo(fde_record_offset);
+                const source_fde_record = (try eh_it.next()).?;
+
+                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
+                const cie_offset = fde_record_offset + 4 - cie_ptr;
+
+                const gop = try cies.getOrPut(cie_offset);
+                if (!gop.found_existing) {
+                    eh_it.seekTo(cie_offset);
+                    const source_cie_record = (try eh_it.next()).?;
+                    var cie_record = try source_cie_record.toOwned(gpa);
+                    try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
+                        .source_offset = cie_offset,
+                        .out_offset = eh_frame_offset,
+                        .sect_addr = sect.addr,
+                    });
+                    eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
+                    gop.value_ptr.* = eh_frame_offset;
+                    eh_frame_offset += cie_record.getSize();
+                }
 
-            const gop = try cies.getOrPut(cie_offset);
-            if (!gop.found_existing) {
-                eh_it.seekTo(cie_offset);
-                const source_cie_record = (try eh_it.next()).?;
-                var cie_record = try source_cie_record.toOwned(gpa);
-                try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
-                    .source_offset = cie_offset,
+                var fde_record = try source_fde_record.toOwned(gpa);
+                try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
+                    .source_offset = fde_record_offset,
                     .out_offset = eh_frame_offset,
                     .sect_addr = sect.addr,
                 });
-                eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
-                gop.value_ptr.* = eh_frame_offset;
-                eh_frame_offset += cie_record.getSize();
-            }
-
-            var fde_record = try source_fde_record.toOwned(gpa);
-            try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
-                .source_offset = fde_record_offset,
-                .out_offset = eh_frame_offset,
-                .sect_addr = sect.addr,
-            });
-            fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
-
-            switch (cpu_arch) {
-                .aarch64 => {}, // relocs take care of LSDA pointers
-                .x86_64 => {
-                    // We need to relocate target symbol address ourselves.
-                    const atom = zld.getAtom(atom_index);
-                    const atom_sym = zld.getSymbol(atom.getSymbolWithLoc());
-                    try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
-                        .base_addr = sect.addr,
-                        .base_offset = eh_frame_offset,
-                    });
+                fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
 
-                    // We need to parse LSDA pointer and relocate ourselves.
-                    const cie_record = eh_records.get(
-                        eh_frame_offset + 4 - fde_record.getCiePointer(),
-                    ).?;
-                    const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
-                    const source_lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
-                        .base_addr = eh_frame_sect.addr,
-                        .base_offset = fde_record_offset,
-                    });
-                    if (source_lsda_ptr) |ptr| {
-                        const sym_index = object.getSymbolByAddress(ptr, null);
-                        const sym = object.symtab[sym_index];
-                        try fde_record.setLsdaPointer(cie_record, sym.n_value, .{
+                switch (cpu_arch) {
+                    .aarch64 => {}, // relocs take care of LSDA pointers
+                    .x86_64 => {
+                        // We need to relocate target symbol address ourselves.
+                        const atom_sym = zld.getSymbol(target);
+                        try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
                             .base_addr = sect.addr,
                             .base_offset = eh_frame_offset,
                         });
-                    }
-                },
-                else => unreachable,
-            }
 
-            eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
-
-            UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
-                &record.compactUnwindEncoding,
-                cpu_arch,
-                @as(u24, @intCast(eh_frame_offset)),
-            );
-
-            const cie_record = eh_records.get(
-                eh_frame_offset + 4 - fde_record.getCiePointer(),
-            ).?;
-            const lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
-                .base_addr = sect.addr,
-                .base_offset = eh_frame_offset,
-            });
-            if (lsda_ptr) |ptr| {
-                record.lsda = ptr - seg.vmaddr;
-            }
+                        // We need to parse LSDA pointer and relocate ourselves.
+                        const cie_record = eh_records.get(
+                            eh_frame_offset + 4 - fde_record.getCiePointer(),
+                        ).?;
+                        const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
+                        const source_lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
+                            .base_addr = eh_frame_sect.addr,
+                            .base_offset = fde_record_offset,
+                        });
+                        if (source_lsda_ptr) |ptr| {
+                            const sym_index = object.getSymbolByAddress(ptr, null);
+                            const sym = object.symtab[sym_index];
+                            try fde_record.setLsdaPointer(cie_record, sym.n_value, .{
+                                .base_addr = sect.addr,
+                                .base_offset = eh_frame_offset,
+                            });
+                        }
+                    },
+                    else => unreachable,
+                }
+
+                eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
+
+                UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
+                    &record.compactUnwindEncoding,
+                    cpu_arch,
+                    @as(u24, @intCast(eh_frame_offset)),
+                );
 
-            eh_frame_offset += fde_record.getSize();
+                const cie_record = eh_records.get(
+                    eh_frame_offset + 4 - fde_record.getCiePointer(),
+                ).?;
+                const lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
+                    .base_addr = sect.addr,
+                    .base_offset = eh_frame_offset,
+                });
+                if (lsda_ptr) |ptr| {
+                    record.lsda = ptr - seg.vmaddr;
+                }
+
+                eh_frame_offset += fde_record.getSize();
+            }
         }
     }
 
src/link/MachO/Object.zig
@@ -75,11 +75,11 @@ exec_atoms: std.ArrayListUnmanaged(AtomIndex) = .{},
 
 eh_frame_sect_id: ?u8 = null,
 eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
-eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(AtomIndex, u32) = .{},
+eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
 
 unwind_info_sect_id: ?u8 = null,
 unwind_relocs_lookup: []Record = undefined,
-unwind_records_lookup: std.AutoHashMapUnmanaged(AtomIndex, u32) = .{},
+unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
 
 const Entry = struct {
     start: u32 = 0,
@@ -274,11 +274,11 @@ const SymbolAtIndex = struct {
         const sym = self.getSymbol(ctx);
         if (!sym.ext()) {
             const sym_name = self.getSymbolName(ctx);
-            if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 0;
-            return 1;
+            if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3;
+            return 2;
         }
-        if (sym.weakDef() or sym.pext()) return 2;
-        return 3;
+        if (sym.weakDef() or sym.pext()) return 1;
+        return 0;
     }
 
     /// Performs lexicographic-like check.
@@ -423,8 +423,8 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                 zld,
                 object_id,
                 sym_index,
-                0,
-                0,
+                sym_index,
+                1,
                 sect.size,
                 sect.@"align",
                 out_sect_id,
@@ -502,8 +502,8 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                     zld,
                     object_id,
                     sym_index,
-                    0,
-                    0,
+                    sym_index,
+                    1,
                     atom_size,
                     sect.@"align",
                     out_sect_id,
@@ -521,7 +521,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                 const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1);
                 assert(atom_loc.len > 0);
                 const atom_sym_index = atom_loc.index + next_sym_index;
-                const nsyms_trailing = atom_loc.len - 1;
+                const nsyms_trailing = atom_loc.len;
                 next_sym_index += atom_loc.len;
 
                 const atom_size = if (next_sym_index < sect_start_index + sect_loc.len)
@@ -538,7 +538,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                     zld,
                     object_id,
                     atom_sym_index,
-                    atom_sym_index + 1,
+                    atom_sym_index,
                     nsyms_trailing,
                     atom_size,
                     atom_align,
@@ -772,8 +772,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
             if (target.getFile() != object_id) {
                 self.eh_frame_relocs_lookup.getPtr(offset).?.dead = true;
             } else {
-                const atom_index = self.getAtomIndexForSymbol(target.sym_index).?;
-                self.eh_frame_records_lookup.putAssumeCapacityNoClobber(atom_index, offset);
+                self.eh_frame_records_lookup.putAssumeCapacityNoClobber(target, offset);
             }
         }
     }
@@ -802,10 +801,10 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
         _ = try zld.initSection("__TEXT", "__unwind_info", .{});
     }
 
-    try self.unwind_records_lookup.ensureTotalCapacity(gpa, @as(u32, @intCast(self.exec_atoms.items.len)));
-
     const unwind_records = self.getUnwindRecords();
 
+    try self.unwind_records_lookup.ensureTotalCapacity(gpa, @as(u32, @intCast(unwind_records.len)));
+
     const needs_eh_frame = for (unwind_records) |record| {
         if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;
     } else false;
@@ -844,8 +843,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
         if (target.getFile() != object_id) {
             self.unwind_relocs_lookup[record_id].dead = true;
         } else {
-            const atom_index = self.getAtomIndexForSymbol(target.sym_index).?;
-            self.unwind_records_lookup.putAssumeCapacityNoClobber(atom_index, @as(u32, @intCast(record_id)));
+            self.unwind_records_lookup.putAssumeCapacityNoClobber(target, @as(u32, @intCast(record_id)));
         }
     }
 }
@@ -1012,7 +1010,13 @@ pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
                 Predicate{ .addr = @as(i64, @intCast(addr)) },
             );
             if (target_sym_index > 0) {
-                return @as(u32, @intCast(lookup.start + target_sym_index - 1));
+                // Hone in on the most senior alias of the target symbol.
+                // See SymbolAtIndex.lessThan for more context.
+                var start = target_sym_index - 1;
+                while (start > 0 and
+                    self.source_address_lookup[lookup.start..][start - 1] == addr) : (start -= 1)
+                {}
+                return @as(u32, @intCast(lookup.start + start));
             }
         }
         return self.getSectionAliasSymbolIndex(sect_id);
src/link/MachO/UnwindInfo.zig
@@ -26,7 +26,7 @@ gpa: Allocator,
 /// List of all unwind records gathered from all objects and sorted
 /// by source function address.
 records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{},
-records_lookup: std.AutoHashMapUnmanaged(AtomIndex, RecordIndex) = .{},
+records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{},
 
 /// List of all personalities referenced by either unwind info entries
 /// or __eh_frame entries.
@@ -211,23 +211,22 @@ pub fn scanRelocs(zld: *Zld) !void {
     for (zld.objects.items, 0..) |*object, object_id| {
         const unwind_records = object.getUnwindRecords();
         for (object.exec_atoms.items) |atom_index| {
-            const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;
-            if (object.unwind_relocs_lookup[record_id].dead) continue;
-            const record = unwind_records[record_id];
-            if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-                if (getPersonalityFunctionReloc(
-                    zld,
-                    @as(u32, @intCast(object_id)),
-                    record_id,
-                )) |rel| {
-                    // Personality function; add GOT pointer.
-                    const target = Atom.parseRelocTarget(zld, .{
-                        .object_id = @as(u32, @intCast(object_id)),
-                        .rel = rel,
-                        .code = mem.asBytes(&record),
-                        .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
-                    });
-                    try Atom.addGotEntry(zld, target);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            while (inner_syms_it.next()) |sym| {
+                const record_id = object.unwind_records_lookup.get(sym) orelse continue;
+                if (object.unwind_relocs_lookup[record_id].dead) continue;
+                const record = unwind_records[record_id];
+                if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
+                    if (getPersonalityFunctionReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
+                        // Personality function; add GOT pointer.
+                        const target = Atom.parseRelocTarget(zld, .{
+                            .object_id = @as(u32, @intCast(object_id)),
+                            .rel = rel,
+                            .code = mem.asBytes(&record),
+                            .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
+                        });
+                        try Atom.addGotEntry(zld, target);
+                    }
                 }
             }
         }
@@ -242,8 +241,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
     var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
     defer records.deinit();
 
-    var atom_indexes = std.ArrayList(AtomIndex).init(info.gpa);
-    defer atom_indexes.deinit();
+    var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa);
+    defer sym_indexes.deinit();
 
     // TODO handle dead stripping
     for (zld.objects.items, 0..) |*object, object_id| {
@@ -253,80 +252,101 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
         // Contents of unwind records does not have to cover all symbol in executable section
         // so we need insert them ourselves.
         try records.ensureUnusedCapacity(object.exec_atoms.items.len);
-        try atom_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
+        try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
 
         for (object.exec_atoms.items) |atom_index| {
-            var record = if (object.unwind_records_lookup.get(atom_index)) |record_id| blk: {
-                if (object.unwind_relocs_lookup[record_id].dead) continue;
-                var record = unwind_records[record_id];
+            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var prev_symbol: ?SymbolWithLoc = null;
+            while (inner_syms_it.next()) |symbol| {
+                var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
+                    if (object.unwind_relocs_lookup[record_id].dead) continue;
+                    var record = unwind_records[record_id];
+
+                    if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
+                        try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
+                    } else {
+                        if (getPersonalityFunctionReloc(
+                            zld,
+                            @as(u32, @intCast(object_id)),
+                            record_id,
+                        )) |rel| {
+                            const target = Atom.parseRelocTarget(zld, .{
+                                .object_id = @as(u32, @intCast(object_id)),
+                                .rel = rel,
+                                .code = mem.asBytes(&record),
+                                .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
+                            });
+                            const personality_index = info.getPersonalityFunction(target) orelse inner: {
+                                const personality_index = info.personalities_count;
+                                info.personalities[personality_index] = target;
+                                info.personalities_count += 1;
+                                break :inner personality_index;
+                            };
+
+                            record.personalityFunction = personality_index + 1;
+                            UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
+                        }
 
-                if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-                    try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), atom_index, &record);
-                } else {
-                    if (getPersonalityFunctionReloc(
-                        zld,
-                        @as(u32, @intCast(object_id)),
-                        record_id,
-                    )) |rel| {
-                        const target = Atom.parseRelocTarget(zld, .{
-                            .object_id = @as(u32, @intCast(object_id)),
-                            .rel = rel,
-                            .code = mem.asBytes(&record),
-                            .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
-                        });
-                        const personality_index = info.getPersonalityFunction(target) orelse inner: {
-                            const personality_index = info.personalities_count;
-                            info.personalities[personality_index] = target;
-                            info.personalities_count += 1;
-                            break :inner personality_index;
-                        };
-
-                        record.personalityFunction = personality_index + 1;
-                        UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
+                        if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
+                            const target = Atom.parseRelocTarget(zld, .{
+                                .object_id = @as(u32, @intCast(object_id)),
+                                .rel = rel,
+                                .code = mem.asBytes(&record),
+                                .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
+                            });
+                            record.lsda = @as(u64, @bitCast(target));
+                        }
                     }
-
-                    if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
-                        const target = Atom.parseRelocTarget(zld, .{
-                            .object_id = @as(u32, @intCast(object_id)),
-                            .rel = rel,
-                            .code = mem.asBytes(&record),
-                            .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
-                        });
-                        record.lsda = @as(u64, @bitCast(target));
+                    break :blk record;
+                } else blk: {
+                    const sym = zld.getSymbol(symbol);
+                    if (sym.n_desc == N_DEAD) continue;
+                    if (prev_symbol) |prev_sym| {
+                        const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
+                        const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
+                        if (prev_addr == curr_addr) continue;
                     }
-                }
-                break :blk record;
-            } else blk: {
-                const atom = zld.getAtom(atom_index);
-                const sym = zld.getSymbol(atom.getSymbolWithLoc());
-                if (sym.n_desc == N_DEAD) continue;
-
-                if (!object.hasUnwindRecords()) {
-                    if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
-                        if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
-                        var record = nullRecord();
-                        try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), atom_index, &record);
-                        switch (cpu_arch) {
-                            .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
-                            .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
-                            else => unreachable,
+
+                    if (!object.hasUnwindRecords()) {
+                        if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
+                            if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
+                            var record = nullRecord();
+                            try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
+                            switch (cpu_arch) {
+                                .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
+                                .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
+                                else => unreachable,
+                            }
+                            break :blk record;
                         }
-                        break :blk record;
                     }
-                }
-
-                break :blk nullRecord();
-            };
 
-            const atom = zld.getAtom(atom_index);
-            const sym_loc = atom.getSymbolWithLoc();
-            const sym = zld.getSymbol(sym_loc);
-            assert(sym.n_desc != N_DEAD);
-            record.rangeStart = sym.n_value;
-            record.rangeLength = @as(u32, @intCast(atom.size));
+                    break :blk nullRecord();
+                };
 
-            records.appendAssumeCapacity(record);
-            atom_indexes.appendAssumeCapacity(atom_index);
+                const atom = zld.getAtom(atom_index);
+                const sym = zld.getSymbol(symbol);
+                assert(sym.n_desc != N_DEAD);
+                const size = if (inner_syms_it.next()) |next_sym| blk: {
+                    // All this trouble to account for symbol aliases.
+                    // TODO I think that remodelling the linker so that a Symbol references an Atom
+                    // is the way to go, kinda like we do for ELF. We might also want to perhaps tag
+                    // symbol aliases somehow so that they are excluded from everything except relocation
+                    // resolution.
+                    defer inner_syms_it.pos -= 1;
+                    const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
+                    const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
+                    if (next_addr > curr_addr) break :blk next_addr - curr_addr;
+                    break :blk zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
+                } else zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
+                record.rangeStart = sym.n_value;
+                record.rangeLength = @as(u32, @intCast(size));
+
+                try records.append(record);
+                try sym_indexes.append(symbol);
+
+                prev_symbol = symbol;
+            }
         }
     }
 
@@ -339,7 +359,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
 
     // Fold records
     try info.records.ensureTotalCapacity(info.gpa, records.items.len);
-    try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(atom_indexes.items.len)));
+    try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len)));
 
     var maybe_prev: ?macho.compact_unwind_entry = null;
     for (records.items, 0..) |record, i| {
@@ -365,7 +385,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                 break :blk record_id;
             }
         };
-        info.records_lookup.putAssumeCapacityNoClobber(atom_indexes.items[i], record_id);
+        info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id);
     }
 
     // Calculate common encodings
@@ -501,12 +521,12 @@ fn collectPersonalityFromDwarf(
     info: *UnwindInfo,
     zld: *Zld,
     object_id: u32,
-    atom_index: u32,
+    sym_loc: SymbolWithLoc,
     record: *macho.compact_unwind_entry,
 ) !void {
     const object = &zld.objects.items[object_id];
     var it = object.getEhFrameRecordsIterator();
-    const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
+    const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
     it.seekTo(fde_offset);
     const fde = (try it.next()).?;
     const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
src/link/MachO/zld.zig
@@ -1492,6 +1492,42 @@ pub const Zld = struct {
                 try thunks.createThunks(self, @as(u8, @intCast(sect_id)));
             }
         }
+
+        // Update offsets of all symbols contained within each Atom.
+        // We need to do this since our unwind info synthesiser relies on
+        // traversing the symbols when synthesising unwind info and DWARF CFI records.
+        for (slice.items(.first_atom_index)) |first_atom_index| {
+            if (first_atom_index == 0) continue;
+            var atom_index = first_atom_index;
+
+            while (true) {
+                const atom = self.getAtom(atom_index);
+                const sym = self.getSymbol(atom.getSymbolWithLoc());
+
+                if (atom.getFile() != null) {
+                    // Update each symbol contained within the atom
+                    var it = Atom.getInnerSymbolsIterator(self, atom_index);
+                    while (it.next()) |sym_loc| {
+                        const inner_sym = self.getSymbolPtr(sym_loc);
+                        inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
+                            self,
+                            atom_index,
+                            sym_loc.sym_index,
+                        );
+                    }
+
+                    // If there is a section alias, update it now too
+                    if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
+                        const alias = self.getSymbolPtr(sym_loc);
+                        alias.n_value = sym.n_value;
+                    }
+                }
+
+                if (atom.next_index) |next_index| {
+                    atom_index = next_index;
+                } else break;
+            }
+        }
     }
 
     fn allocateSegments(self: *Zld) !void {
src/link/MachO/ZldAtom.zig
@@ -84,14 +84,14 @@ pub inline fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
 
 const InnerSymIterator = struct {
     sym_index: u32,
-    count: u32,
+    nsyms: u32,
     file: u32,
+    pos: u32 = 0,
 
     pub fn next(it: *@This()) ?SymbolWithLoc {
-        if (it.count == 0) return null;
-        const res = SymbolWithLoc{ .sym_index = it.sym_index, .file = it.file };
-        it.sym_index += 1;
-        it.count -= 1;
+        if (it.pos == it.nsyms) return null;
+        const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file };
+        it.pos += 1;
         return res;
     }
 };
@@ -103,7 +103,7 @@ pub fn getInnerSymbolsIterator(zld: *Zld, atom_index: AtomIndex) InnerSymIterato
     assert(atom.getFile() != null);
     return .{
         .sym_index = atom.inner_sym_index,
-        .count = atom.inner_nsyms_trailing,
+        .nsyms = atom.inner_nsyms_trailing,
         .file = atom.file,
     };
 }
@@ -228,11 +228,7 @@ pub fn parseRelocTarget(zld: *Zld, ctx: struct {
 
         // Find containing atom
         log.debug("  | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id });
-        const candidate = object.getSymbolByAddress(address_in_section, sect_id);
-        // Make sure we are not dealing with a local alias.
-        const atom_index = object.getAtomIndexForSymbol(candidate) orelse break :sym_index candidate;
-        const atom = zld.getAtom(atom_index);
-        break :sym_index atom.sym_index;
+        break :sym_index object.getSymbolByAddress(address_in_section, sect_id);
     } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
 
     const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };