Commit f40fbdb3b3

mlugg <mlugg@mlugg.co.uk>
2025-09-08 16:17:20
link.Elf: restore eh_frame_hdr search table building
At least, when there's not a ZigObject. The old behavior was incorrect in the presence of a ZigObject, and this doesn't really mix nicely with incremental compilation anyway; but when the objects are all external, we may as well build the search table.
1 parent d9661e9
Changed files (1)
src
link
src/link/Elf/eh_frame.zig
@@ -234,7 +234,14 @@ pub fn calcEhFrameSize(elf_file: *Elf) !usize {
     return offset;
 }
 
+fn haveEhFrameHdrSearchTable(elf_file: *Elf) bool {
+    // Seach table generation is not implemented for the ZigObject. Also, it would be wasteful to
+    // re-do this work on every single incremental update.
+    return elf_file.zigObjectPtr() == null;
+}
+
 pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
+    if (!haveEhFrameHdrSearchTable(elf_file)) return 8;
     var count: usize = 0;
     for (elf_file.objects.items) |index| {
         for (elf_file.file(index).?.object.fdes.items) |fde| {
@@ -242,7 +249,7 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
             count += 1;
         }
     }
-    return eh_frame_hdr_header_size + count * 8;
+    return 12 + count * 8;
 }
 
 pub fn calcEhFrameRelocs(elf_file: *Elf) usize {
@@ -455,15 +462,23 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
 }
 
 pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
+    const endian = elf_file.getTarget().cpu.arch.endian();
+    const have_table = haveEhFrameHdrSearchTable(elf_file);
+
     try writer.writeByte(1); // version
     try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .pcrel }))); // eh_frame_ptr_enc
-    // Building the lookup table would be expensive work on every `flush` -- omit it.
-    try writer.writeByte(@bitCast(DW_EH_PE.omit)); // fde_count_enc
-    try writer.writeByte(@bitCast(DW_EH_PE.omit)); // table_enc
+    if (have_table) {
+        try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .udata4, .rel = .abs }))); // fde_count_enc
+        try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .datarel }))); // table_enc
+    } else {
+        try writer.writeByte(@bitCast(DW_EH_PE.omit)); // fde_count_enc
+        try writer.writeByte(@bitCast(DW_EH_PE.omit)); // table_enc
+    }
 
     const shdrs = elf_file.sections.items(.shdr);
     const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
     const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];
+    // eh_frame_ptr
     try writer.writeInt(
         u32,
         @as(u32, @bitCast(@as(
@@ -472,9 +487,51 @@ pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
         ))),
         .little,
     );
-}
 
-const eh_frame_hdr_header_size: usize = 12;
+    if (!have_table) return;
+
+    const gpa = elf_file.base.comp.gpa;
+
+    // This must be an `extern struct` because we will write the bytes directly to the file.
+    const Entry = extern struct {
+        first_pc_rel: i32,
+        fde_addr_rel: i32,
+        fn lessThan(_: void, lhs: @This(), rhs: @This()) bool {
+            return lhs.first_pc_rel < rhs.first_pc_rel;
+        }
+    };
+    // The number of entries was already computed by `calcEhFrameHdrSize`.
+    const num_fdes: u32 = @intCast(@divExact(eh_frame_hdr_shdr.sh_size - 12, 8));
+    try writer.writeInt(u32, num_fdes, endian);
+
+    var entries: std.ArrayList(Entry) = try .initCapacity(gpa, num_fdes);
+    defer entries.deinit(gpa);
+    for (elf_file.objects.items) |file_index| {
+        const object = elf_file.file(file_index).?.object;
+        for (object.fdes.items) |fde| {
+            if (!fde.alive) continue;
+            const relocs = fde.relocs(object);
+            // Should `relocs.len == 0` be an error? Things are completely broken anyhow in that case...
+            const rel = relocs[0];
+            const ref = object.resolveSymbol(rel.r_sym(), elf_file);
+            const sym = elf_file.symbol(ref).?;
+            const fde_addr_abs: i64 = @intCast(fde.address(elf_file));
+            const fde_addr_rel: i64 = fde_addr_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr));
+            const first_pc_abs: i64 = @as(i64, @intCast(sym.address(.{}, elf_file))) + rel.r_addend;
+            const first_pc_rel: i64 = first_pc_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr));
+            entries.appendAssumeCapacity(.{
+                .first_pc_rel = @truncate(first_pc_rel),
+                .fde_addr_rel = @truncate(fde_addr_rel),
+            });
+        }
+    }
+    assert(entries.items.len == num_fdes);
+    std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
+    if (endian != builtin.cpu.arch.endian()) {
+        std.mem.byteSwapAllElements(Entry, entries.items);
+    }
+    try writer.writeAll(@ptrCast(entries.items));
+}
 
 const x86_64 = struct {
     fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void {
@@ -538,3 +595,5 @@ const DW_EH_PE = std.dwarf.EH.PE;
 const Elf = @import("../Elf.zig");
 const Object = @import("Object.zig");
 const Symbol = @import("Symbol.zig");
+
+const builtin = @import("builtin");