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:
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],