zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit e835b864e8e5d2ff33b9f59f9f65c74f86093dfc (tree)
parent 05b15e8bdb0f402fa2acea113ecbbe171f4d02bf
Author: Vadym Prodan <vprodan@proton.me>
Date:   Sat, 10 Jan 2026 01:24:27 +0100

link/MachO: fix eh_frame handling on macOS

Fixes several issues with eh_frame generation and unwind info on macOS:

* Fix FDE address calculation by including atom_offset
* Store and use pc_range (function size) from FDE for coverage checks
* Avoid creating null unwind records for addresses already covered by
  DWARF FDEs, allowing unwinder to properly fall back to DWARF info
* Remove incorrect addend adjustment in personality pointer calculation

Diffstat:
Msrc/link/MachO/Object.zig | 54+++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/link/MachO/eh_frame.zig | 10+++++++---
2 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig @@ -1298,10 +1298,18 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target. superposition.getPtr(addr).?.cu = rec_index; } + const FdeRange = struct { start: u64, end: u64 }; + var fde_ranges = try std.ArrayList(FdeRange).initCapacity(allocator, self.fdes.items.len); + defer fde_ranges.deinit(allocator); + for (self.fdes.items, 0..) |fde, fde_index| { const atom = fde.getAtom(macho_file); const addr = atom.getInputAddress(macho_file) + fde.atom_offset; superposition.getPtr(addr).?.fde = @intCast(fde_index); + + // Build FDE range for coverage check + const pc_range = fde.pc_range; + fde_ranges.appendAssumeCapacity(.{ .start = addr, .end = addr + pc_range }); } for (superposition.keys(), superposition.values()) |addr, meta| { @@ -1333,15 +1341,43 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target. } } } else if (meta.cu == null and meta.fde == null) { - // Create a null record - const rec_index = try self.addUnwindRecord(allocator); - const rec = self.getUnwindRecord(rec_index); - const atom = self.getAtom(meta.atom).?; - try self.unwind_records_indexes.append(allocator, rec_index); - rec.length = @intCast(meta.size); - rec.atom = meta.atom; - rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file)); - rec.file = self.index; + // Check if this address is covered by an existing FDE. + // If so, don't create a null record - let the unwinder fall back to DWARF. + // This is important for local labels within a function that has DWARF unwind info. + const is_covered_by_fde = blk: { + if (fde_ranges.items.len == 0) break :blk false; + + // Binary search: find the last FDE where start <= addr + var left: usize = 0; + var right: usize = fde_ranges.items.len; + while (left < right) { + const mid = left + (right - left) / 2; + if (fde_ranges.items[mid].start <= addr) { + left = mid + 1; + } else { + right = mid; + } + } + + // Check if the FDE before insertion point covers this address + if (left > 0) { + const range = fde_ranges.items[left - 1]; + break :blk addr < range.end; + } + break :blk false; + }; + + if (!is_covered_by_fde) { + // Create a null record only if not covered by DWARF + const rec_index = try self.addUnwindRecord(allocator); + const rec = self.getUnwindRecord(rec_index); + const atom = self.getAtom(meta.atom).?; + try self.unwind_records_indexes.append(allocator, rec_index); + rec.length = @intCast(meta.size); + rec.atom = meta.atom; + rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file)); + rec.file = self.index; + } } } diff --git a/src/link/MachO/eh_frame.zig b/src/link/MachO/eh_frame.zig @@ -116,6 +116,7 @@ pub const Fde = struct { cie: Cie.Index, atom: Atom.Index = 0, atom_offset: u32 = 0, + pc_range: u64 = 0, lsda: Atom.Index = 0, lsda_offset: u32 = 0, lsda_ptr_offset: u32 = 0, @@ -142,6 +143,9 @@ pub const Fde = struct { const atom = fde.getAtom(macho_file); fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file)); + // Parse pc_range (function size) + fde.pc_range = std.mem.readInt(u64, data[16..][0..8], .little); + // Associate with a CIE const cie_ptr = std.mem.readInt(u32, data[4..8], .little); const cie_offset = fde.offset + 4 - cie_ptr; @@ -350,7 +354,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { std.mem.writeInt( i32, buffer[offset..][0..4], - @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), + @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr))), .little, ); } @@ -373,7 +377,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { { const offset = fde.out_offset + 8; const saddr = sect.addr + offset; - const taddr = fde.getAtom(macho_file).getAddress(macho_file); + const taddr = fde.getAtom(macho_file).getAddress(macho_file) + fde.atom_offset; std.mem.writeInt( i64, buffer[offset..][0..8], @@ -460,7 +464,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: []macho.relocation_in { const offset = fde.out_offset + 8; const saddr = sect.addr + offset; - const taddr = fde.getAtom(macho_file).getAddress(macho_file); + const taddr = fde.getAtom(macho_file).getAddress(macho_file) + fde.atom_offset; std.mem.writeInt( i64, code[offset..][0..8],