lib/tldr/src/formats/elf/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const sys = @import("sys");
   3 const tldr = @import("../../root.zig");
   4 const elf = @import("root.zig");
   5 const link = @import("link.zig");
   6 const metadata = @import("metadata.zig");
   7 
   8 pub const address_suite = @import("address/test.zig");
   9 pub const archive_suite = @import("archive/test.zig");
  10 pub const ehframe_suite = @import("ehframe/test.zig");
  11 pub const group_suite = @import("group/test.zig");
  12 pub const icf_suite = @import("icf/test.zig");
  13 pub const image_suite = @import("image/test.zig");
  14 pub const layout_suite = @import("layout/test.zig");
  15 pub const liveness_suite = @import("liveness/test.zig");
  16 pub const merge_suite = @import("merge/test.zig");
  17 pub const object_suite = @import("object/test.zig");
  18 pub const relocation_suite = @import("relocation/test.zig");
  19 
  20 const archive = tldr.archive;
  21 const incremental = tldr.incremental;
  22 const model = tldr.model;
  23 const parallel = tldr.parallel;
  24 const format = elf.format;
  25 const build_id_note = elf.note;
  26 const ifunc = elf.ifunc;
  27 const payload = elf.payload;
  28 const symbol_table = elf.symbol_table;
  29 
  30 const ehdr_size = format.ehdr_size;
  31 const phdr_size = format.phdr_size;
  32 const eh_frame_hdr_fixed_size = format.eh_frame_hdr_fixed_size;
  33 const eh_frame_hdr_entry_size = format.eh_frame_hdr_entry_size;
  34 const dwarf_eh_pe_udata4 = format.dwarf_eh_pe_udata4;
  35 const dwarf_eh_pe_sdata4 = format.dwarf_eh_pe_sdata4;
  36 const dwarf_eh_pe_pcrel = format.dwarf_eh_pe_pcrel;
  37 const dwarf_eh_pe_datarel = format.dwarf_eh_pe_datarel;
  38 const buildIdNoteSize = build_id_note.size;
  39 const alignForward = format.alignForward;
  40 const alignForwardU64 = format.alignForwardU64;
  41 const hashU64 = format.hashU64;
  42 const readU16 = format.readU16;
  43 const readU32 = format.readU32;
  44 const readU64 = format.readU64;
  45 
  46 const Allocator = std.mem.Allocator;
  47 const linkExecutable = elf.linkExecutable;
  48 const patchIncrementalMetadata = elf.patchIncrementalMetadata;
  49 const object_writer = elf.object;
  50 const Section = object_writer.Section;
  51 const Symbol = object_writer.Symbol;
  52 const Relocation = object_writer.Relocation;
  53 const groupBytes = object_writer.groupBytes;
  54 const buildObject = object_writer.build;
  55 
  56 fn coffHeaderBytes() [@sizeOf(std.coff.Header)]u8 {
  57     var bytes = @as([@sizeOf(std.coff.Header)]u8, @splat(0));
  58     std.mem.writeInt(u16, bytes[0..2], @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64), .little);
  59     return bytes;
  60 }
  61 
  62 fn expectAllZeroes(bytes: []const u8) !void {
  63     for (bytes) |byte| try std.testing.expectEqual(@as(u8, 0), byte);
  64 }
  65 
  66 test "ELF linker emits executable with incremental section manifest" {
  67     const allocator = std.testing.allocator;
  68     const text = [_]u8{
  69         0xb8, 0x3c, 0x00, 0x00, 0x00,
  70         0x31, 0xff, 0x0f, 0x05,
  71     };
  72     const text_index: u16 = 1;
  73     const object = try buildObject(allocator, .{
  74         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
  75         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
  76     });
  77     defer allocator.free(object);
  78 
  79     var linked = try linkExecutable(allocator, &.{.{ .name = "start.o", .bytes = object }}, .{});
  80     defer linked.deinit(allocator);
  81 
  82     try std.testing.expectEqualSlices(u8, std.elf.MAGIC, linked.bytes[0..4]);
  83     try std.testing.expectEqual(@as(u16, @backingInt(std.elf.ET.EXEC)), readU16(linked.bytes, 16));
  84     try std.testing.expectEqual(@as(u16, 2), readU16(linked.bytes, 56));
  85     try std.testing.expect(linked.manifest.canReuseFor(.{}, &.{.{ .name = "start.o", .bytes = object }}));
  86     try std.testing.expectEqualStrings(".text", linked.manifest.string(linked.manifest.sections[0].name_id));
  87     try std.testing.expect(linked.manifest.sections[0].reserved_size > linked.manifest.sections[0].size);
  88 
  89     const text_contribution = metadata.manifestContribution(linked.manifest, "start.o", .section, ".text", text_index) orelse return error.MissingSection;
  90     try std.testing.expectEqualStrings(".text", linked.manifest.string(text_contribution.output_section_name_id));
  91     try std.testing.expectEqual(linked.manifest.sections[0].address, text_contribution.address);
  92     try std.testing.expectEqual(linked.manifest.sections[0].file_offset, text_contribution.file_offset);
  93     try std.testing.expectEqual(@as(u64, text.len), text_contribution.size);
  94     try std.testing.expect(text_contribution.reserved_size > text_contribution.size);
  95     _ = try metadata.programHeaderByType(linked.bytes, std.elf.PT_PHDR);
  96     _ = try metadata.loadProgramHeaderByAddress(linked.bytes, text_contribution.address);
  97 
  98     const plan = linked.manifest.planContributionReplacement(&.{
  99         .{ .input_name = "start.o", .input_index = 0, .kind = .section, .name = ".text", .ordinal = text_index, .size = text.len + 1, .alignment = 16 },
 100     });
 101     try std.testing.expectEqual(incremental.PatchDecision.in_place, plan.decision);
 102 }
 103 
 104 test "ELF linker mapped output matches heap output" {
 105     const allocator = std.testing.allocator;
 106     const text = [_]u8{
 107         0xb8, 0x3c, 0x00, 0x00, 0x00,
 108         0x31, 0xff, 0x0f, 0x05,
 109     };
 110     const text_index: u16 = 1;
 111     const object = try buildObject(allocator, .{
 112         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
 113         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
 114     });
 115     defer allocator.free(object);
 116     const inputs = [_]model.Input{.{ .name = "start.o", .bytes = object }};
 117 
 118     var heap_linked = try linkExecutable(allocator, &inputs, .{});
 119     defer heap_linked.deinit(allocator);
 120     try std.testing.expect(!heap_linked.isMaterialized());
 121 
 122     var tmp = std.testing.tmpDir(.{});
 123     defer tmp.cleanup();
 124     var output_file = try tmp.dir.createFile(sys.fs.debugIo(), "mapped.out", .{ .read = true });
 125     defer output_file.close(sys.fs.debugIo());
 126 
 127     var mapped_linked = try linkExecutable(allocator, &inputs, .{
 128         .mapped_output_file = output_file,
 129         .mapped_output_min_size = 0,
 130     });
 131     try std.testing.expect(mapped_linked.isMaterialized());
 132     try std.testing.expectEqualSlices(u8, heap_linked.bytes, mapped_linked.bytes);
 133     const mapped_len = mapped_linked.bytes.len;
 134     mapped_linked.deinit(allocator);
 135 
 136     const file_bytes = try tmp.dir.readFileAlloc(sys.fs.debugIo(), "mapped.out", allocator, .limited(mapped_len + 1));
 137     defer allocator.free(file_bytes);
 138     try std.testing.expectEqualSlices(u8, heap_linked.bytes, file_bytes);
 139 }
 140 
 141 test "ELF linker emits GNU SHA1 build id note" {
 142     const allocator = std.testing.allocator;
 143     const text = [_]u8{0xc3};
 144     const text_index: u16 = 1;
 145     const object = try buildObject(allocator, .{
 146         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
 147         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
 148     });
 149     defer allocator.free(object);
 150 
 151     var linked = try linkExecutable(allocator, &.{.{ .name = "build-id.o", .bytes = object }}, .{
 152         .incremental_mode = .off,
 153         .build_id = .sha1,
 154     });
 155     defer linked.deinit(allocator);
 156 
 157     const build_note = try metadata.linkedSectionByName(linked.bytes, build_id_note.section_name);
 158     try std.testing.expectEqual(std.elf.SHT_NOTE, build_note.section_type);
 159     try std.testing.expectEqual(std.elf.SHF_ALLOC, build_note.flags);
 160     try std.testing.expectEqual(buildIdNoteSize(.sha1), build_note.size);
 161     try std.testing.expectEqual(@as(u64, 4), build_note.alignment);
 162 
 163     const note_program = try metadata.programHeaderByType(linked.bytes, std.elf.PT_NOTE);
 164     try std.testing.expectEqual(build_note.offset, note_program.file_offset);
 165     try std.testing.expectEqual(build_note.address, note_program.virtual_address);
 166     try std.testing.expectEqual(build_note.size, note_program.file_size);
 167     try std.testing.expectEqual(build_note.size, note_program.memory_size);
 168 
 169     const start: usize = @intCast(build_note.offset);
 170     try std.testing.expectEqual(@as(u32, 4), readU32(linked.bytes, start + 0));
 171     try std.testing.expectEqual(@as(u32, build_id_note.sha1_desc_size), readU32(linked.bytes, start + 4));
 172     try std.testing.expectEqual(@as(u32, std.elf.NT_GNU_BUILD_ID), readU32(linked.bytes, start + 8));
 173     try std.testing.expectEqualSlices(u8, "GNU\x00", linked.bytes[start + 12 .. start + 16]);
 174 
 175     const comparable = try allocator.dupe(u8, linked.bytes);
 176     defer allocator.free(comparable);
 177     @memset(comparable[start + build_id_note.desc_offset ..][0..build_id_note.sha1_desc_size], 0);
 178     var expected: [build_id_note.sha1_desc_size]u8 = undefined;
 179     std.crypto.hash.Sha1.hash(comparable, &expected, .{});
 180     try std.testing.expectEqualSlices(u8, &expected, linked.bytes[start + build_id_note.desc_offset ..][0..build_id_note.sha1_desc_size]);
 181 
 182     const note_record = metadata.manifestSection(linked.manifest, build_id_note.section_name) orelse return error.MissingSection;
 183     try std.testing.expectEqual(build_note.offset, note_record.file_offset);
 184 }
 185 
 186 test "ELF linker emits fast GNU build id note" {
 187     const allocator = std.testing.allocator;
 188     const text = [_]u8{0xc3};
 189     const text_index: u16 = 1;
 190     const object = try buildObject(allocator, .{
 191         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
 192         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
 193     });
 194     defer allocator.free(object);
 195 
 196     var linked = try linkExecutable(allocator, &.{.{ .name = "build-id-fast.o", .bytes = object }}, .{
 197         .incremental_mode = .off,
 198         .build_id = .fast,
 199     });
 200     defer linked.deinit(allocator);
 201 
 202     const build_note = try metadata.linkedSectionByName(linked.bytes, build_id_note.section_name);
 203     try std.testing.expectEqual(std.elf.SHT_NOTE, build_note.section_type);
 204     try std.testing.expectEqual(std.elf.SHF_ALLOC, build_note.flags);
 205     try std.testing.expectEqual(buildIdNoteSize(.fast), build_note.size);
 206 
 207     const start: usize = @intCast(build_note.offset);
 208     try std.testing.expectEqual(@as(u32, 4), readU32(linked.bytes, start + 0));
 209     try std.testing.expectEqual(@as(u32, build_id_note.fast_desc_size), readU32(linked.bytes, start + 4));
 210     try std.testing.expectEqual(@as(u32, std.elf.NT_GNU_BUILD_ID), readU32(linked.bytes, start + 8));
 211     try std.testing.expectEqualSlices(u8, "GNU\x00", linked.bytes[start + 12 .. start + 16]);
 212 
 213     const comparable = try allocator.dupe(u8, linked.bytes);
 214     defer allocator.free(comparable);
 215     @memset(comparable[start + build_id_note.desc_offset ..][0..build_id_note.fast_desc_size], 0);
 216     var hasher = std.hash.XxHash64.init(0);
 217     hasher.update(comparable);
 218     try std.testing.expectEqual(hasher.final(), readU64(linked.bytes, start + build_id_note.desc_offset));
 219 }
 220 
 221 test "ELF linker zeroes executable padding without clearing payloads" {
 222     const allocator = std.testing.allocator;
 223     const start_text = [_]u8{0xc3};
 224     const helper_text = [_]u8{ 0x90, 0xc3 };
 225     const start_index: u16 = 1;
 226     const helper_index: u16 = 2;
 227     const object = try buildObject(allocator, .{
 228         .sections = &.{
 229             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 1),
 230             Section.progbits(".text.helper", &helper_text, std.elf.SHF_EXECINSTR, 16),
 231         },
 232         .symbols = &.{
 233             Symbol.section(start_index),
 234             Symbol.section(helper_index),
 235             Symbol.function("_start", start_index, 0, start_text.len),
 236             Symbol.function("helper", helper_index, 0, helper_text.len),
 237         },
 238     });
 239     defer allocator.free(object);
 240 
 241     var linked = try linkExecutable(allocator, &.{.{ .name = "padding.o", .bytes = object }}, .{});
 242     defer linked.deinit(allocator);
 243 
 244     try expectAllZeroes(linked.bytes[8..16]);
 245 
 246     const start_contribution = metadata.manifestContribution(linked.manifest, "padding.o", .section, ".text.start", start_index) orelse return error.MissingSection;
 247     const helper_contribution = metadata.manifestContribution(linked.manifest, "padding.o", .section, ".text.helper", helper_index) orelse return error.MissingSection;
 248     try std.testing.expectEqualSlices(u8, &start_text, linked.bytes[@intCast(start_contribution.file_offset)..][0..start_text.len]);
 249     try std.testing.expectEqualSlices(u8, &helper_text, linked.bytes[@intCast(helper_contribution.file_offset)..][0..helper_text.len]);
 250     try expectAllZeroes(linked.bytes[@intCast(start_contribution.file_offset + start_contribution.size)..@intCast(helper_contribution.file_offset)]);
 251     try expectAllZeroes(linked.bytes[@intCast(helper_contribution.file_offset + helper_contribution.size)..@intCast(helper_contribution.file_offset + helper_contribution.reserved_size)]);
 252 }
 253 
 254 test "ELF linker keeps loadable file ranges compact and page-congruent" {
 255     const allocator = std.testing.allocator;
 256     const text = [_]u8{0xc3};
 257     const rodata = [_]u8{ 1, 2, 3, 4 };
 258     const text_index: u16 = 1;
 259     const object = try buildObject(allocator, .{
 260         .sections = &.{
 261             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
 262             Section.progbits(".rodata", &rodata, 0, 16),
 263         },
 264         .symbols = &.{
 265             Symbol.section(text_index),
 266             Symbol.section(2),
 267             Symbol.function("_start", text_index, 0, text.len),
 268         },
 269     });
 270     defer allocator.free(object);
 271 
 272     const options = model.LinkOptions{};
 273     var linked = try linkExecutable(allocator, &.{.{ .name = "compact.o", .bytes = object }}, options);
 274     defer linked.deinit(allocator);
 275 
 276     const text_section = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
 277     const rodata_section = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
 278     try std.testing.expect(text_section.file_offset < options.page_size);
 279     try std.testing.expect(rodata_section.file_offset < options.page_size);
 280     try std.testing.expect(rodata_section.address >= alignForwardU64(text_section.address + text_section.reserved_size, options.page_size));
 281 
 282     const text_program = try metadata.loadProgramHeaderByAddress(linked.bytes, text_section.address);
 283     const rodata_program = try metadata.loadProgramHeaderByAddress(linked.bytes, rodata_section.address);
 284     const phdr_program = try metadata.programHeaderByType(linked.bytes, std.elf.PT_PHDR);
 285     try std.testing.expectEqual(@as(u64, ehdr_size), phdr_program.file_offset);
 286     try std.testing.expectEqual(@as(u64, readU16(linked.bytes, 56)) * phdr_size, phdr_program.file_size);
 287     try std.testing.expectEqual(@as(u64, 0), text_program.file_offset);
 288     try std.testing.expectEqual(rodata_section.file_offset, rodata_program.file_offset);
 289     try std.testing.expectEqual(text_program.file_offset % options.page_size, text_program.virtual_address % options.page_size);
 290     try std.testing.expectEqual(rodata_program.file_offset % options.page_size, rodata_program.virtual_address % options.page_size);
 291     try std.testing.expectEqual(text_section.file_offset - text_program.file_offset, text_section.address - text_program.virtual_address);
 292 }
 293 
 294 test "ELF linker records duplicate section names with distinct ordinals" {
 295     const allocator = std.testing.allocator;
 296     const start_text = [_]u8{0xc3};
 297     const helper_text = [_]u8{ 0x90, 0xc3 };
 298     const start_index: u16 = 1;
 299     const helper_index: u16 = 2;
 300     const object = try buildObject(allocator, .{
 301         .sections = &.{
 302             Section.progbits(".text", &start_text, std.elf.SHF_EXECINSTR, 16),
 303             Section.progbits(".text", &helper_text, std.elf.SHF_EXECINSTR, 16),
 304         },
 305         .symbols = &.{
 306             Symbol.section(start_index),
 307             Symbol.section(helper_index),
 308             Symbol.function("_start", start_index, 0, start_text.len),
 309             Symbol.function("helper", helper_index, 0, helper_text.len),
 310         },
 311     });
 312     defer allocator.free(object);
 313 
 314     var linked = try linkExecutable(allocator, &.{.{ .name = "dup.o", .bytes = object }}, .{});
 315     defer linked.deinit(allocator);
 316 
 317     const start_contribution = metadata.manifestContribution(linked.manifest, "dup.o", .section, ".text", start_index) orelse return error.MissingSection;
 318     const helper_contribution = metadata.manifestContribution(linked.manifest, "dup.o", .section, ".text", helper_index) orelse return error.MissingSection;
 319     try std.testing.expectEqual(@as(u64, start_text.len), start_contribution.size);
 320     try std.testing.expectEqual(@as(u64, helper_text.len), helper_contribution.size);
 321     try std.testing.expect(helper_contribution.address > start_contribution.address);
 322 
 323     const helper_plan = linked.manifest.planContributionReplacement(&.{
 324         .{ .input_name = "dup.o", .input_index = 0, .kind = .section, .name = ".text", .ordinal = helper_index, .size = helper_text.len + 1, .alignment = 16 },
 325     });
 326     try std.testing.expectEqual(incremental.PatchDecision.in_place, helper_plan.decision);
 327 }
 328 
 329 test "ELF linker applies PC-relative relocations across objects" {
 330     const allocator = std.testing.allocator;
 331 
 332     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
 333     const caller_text_index: u16 = 1;
 334     const callee_symbol: u32 = 3;
 335     const caller_object = try buildObject(allocator, .{
 336         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
 337         .symbols = &.{
 338             Symbol.section(caller_text_index),
 339             Symbol.function("_start", caller_text_index, 0, caller_text.len),
 340             Symbol.undefinedFunction("callee"),
 341         },
 342         .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
 343     });
 344     defer allocator.free(caller_object);
 345 
 346     const callee_text = [_]u8{0xc3};
 347     const callee_text_index: u16 = 1;
 348     const callee_object = try buildObject(allocator, .{
 349         .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
 350         .symbols = &.{
 351             Symbol.section(callee_text_index),
 352             Symbol.function("callee", callee_text_index, 0, callee_text.len),
 353         },
 354     });
 355     defer allocator.free(callee_object);
 356 
 357     var linked = try linkExecutable(allocator, &.{
 358         .{ .name = "caller.o", .bytes = caller_object },
 359         .{ .name = "callee.o", .bytes = callee_object },
 360     }, .{ .incremental_mode = .off });
 361     defer linked.deinit(allocator);
 362 
 363     const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
 364     const patched = std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little);
 365     try std.testing.expectEqual(@as(i32, 11), patched);
 366 }
 367 
 368 test "ELF incremental candidate relink patches changed relocation payloads" {
 369     const allocator = std.testing.allocator;
 370     const options = model.LinkOptions{};
 371 
 372     const old_caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
 373     const old_caller_text_index: u16 = 1;
 374     const old_callee_symbol: u32 = 3;
 375     const old_caller_object = try buildObject(allocator, .{
 376         .sections = &.{Section.progbits(".text", &old_caller_text, std.elf.SHF_EXECINSTR, 16)},
 377         .symbols = &.{
 378             Symbol.section(old_caller_text_index),
 379             Symbol.function("_start", old_caller_text_index, 0, old_caller_text.len),
 380             Symbol.undefinedFunction("callee"),
 381         },
 382         .relocations = &.{Relocation.x86_64(1, 1, old_callee_symbol, .PLT32, -4)},
 383     });
 384     defer allocator.free(old_caller_object);
 385 
 386     const new_caller_text = [_]u8{ 0x90, 0xe8, 0, 0, 0, 0 };
 387     const new_caller_text_index: u16 = 1;
 388     const new_callee_symbol: u32 = 3;
 389     const new_caller_object = try buildObject(allocator, .{
 390         .sections = &.{Section.progbits(".text", &new_caller_text, std.elf.SHF_EXECINSTR, 16)},
 391         .symbols = &.{
 392             Symbol.section(new_caller_text_index),
 393             Symbol.function("_start", new_caller_text_index, 0, new_caller_text.len),
 394             Symbol.undefinedFunction("callee"),
 395         },
 396         .relocations = &.{Relocation.x86_64(1, 2, new_callee_symbol, .PLT32, -4)},
 397     });
 398     defer allocator.free(new_caller_object);
 399 
 400     const callee_text = [_]u8{0xc3};
 401     const callee_text_index: u16 = 1;
 402     const callee_object = try buildObject(allocator, .{
 403         .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
 404         .symbols = &.{
 405             Symbol.section(callee_text_index),
 406             Symbol.function("callee", callee_text_index, 0, callee_text.len),
 407         },
 408     });
 409     defer allocator.free(callee_object);
 410 
 411     const old_inputs = [_]model.Input{
 412         .{ .name = "caller.o", .bytes = old_caller_object },
 413         .{ .name = "callee.o", .bytes = callee_object },
 414     };
 415     const new_inputs = [_]model.Input{
 416         .{ .name = "caller.o", .bytes = new_caller_object },
 417         .{ .name = "callee.o", .bytes = callee_object },
 418     };
 419 
 420     var old_linked = try linkExecutable(allocator, &old_inputs, options);
 421     defer old_linked.deinit(allocator);
 422     var state = try old_linked.prepareIncrementalState(allocator);
 423     defer state.deinit(allocator);
 424 
 425     var candidate = try linkExecutable(allocator, &new_inputs, options);
 426     defer candidate.deinit(allocator);
 427 
 428     const input_change_limits = incremental.InputChangeStorage.Limits.inspect(
 429         incremental.RecordedInputs.fromManifest(&state.manifest),
 430         &new_inputs,
 431     );
 432     const input_change_capacity = try incremental.InputChangeStorage.Capacity.derive(
 433         input_change_limits,
 434     );
 435     const input_change_bytes = try allocator.alignedAlloc(
 436         u8,
 437         .fromByteUnits(incremental.InputChangeStorage.storage_alignment),
 438         input_change_capacity.storage_bytes,
 439     );
 440     defer allocator.free(input_change_bytes);
 441     var input_change_storage = try incremental.InputChangeStorage.init(
 442         input_change_bytes,
 443         input_change_limits,
 444     );
 445     defer _ = input_change_storage.deinit();
 446     input_change_storage.activate();
 447     const application = try state.applyChangedInputRelinkFromCandidate(
 448         allocator,
 449         &input_change_storage,
 450         old_linked.bytes,
 451         options,
 452         &new_inputs,
 453         candidate.bytes,
 454         candidate.manifest,
 455     );
 456 
 457     try std.testing.expectEqual(incremental.RelinkDecision.in_place, application.plan.decision);
 458     try std.testing.expectEqual(@as(usize, 1), application.contributions_written);
 459     try std.testing.expectEqualSlices(u8, candidate.bytes, old_linked.bytes);
 460 }
 461 
 462 test "ELF incremental candidate relink patches metadata after contribution growth" {
 463     const allocator = std.testing.allocator;
 464     const options = model.LinkOptions{ .build_id = .fast };
 465 
 466     const old_text = [_]u8{0xc3};
 467     const old_text_index: u16 = 1;
 468     const old_object = try buildObject(allocator, .{
 469         .sections = &.{Section.progbits(".text", &old_text, std.elf.SHF_EXECINSTR, 16)},
 470         .symbols = &.{ Symbol.section(old_text_index), Symbol.function("_start", old_text_index, 0, old_text.len) },
 471     });
 472     defer allocator.free(old_object);
 473 
 474     const new_text = [_]u8{ 0x90, 0xc3 };
 475     const new_text_index: u16 = 1;
 476     const new_object = try buildObject(allocator, .{
 477         .sections = &.{Section.progbits(".text", &new_text, std.elf.SHF_EXECINSTR, 16)},
 478         .symbols = &.{ Symbol.section(new_text_index), Symbol.function("_start", new_text_index, 0, new_text.len) },
 479     });
 480     defer allocator.free(new_object);
 481 
 482     const old_inputs = [_]model.Input{.{ .name = "start.o", .bytes = old_object }};
 483     const new_inputs = [_]model.Input{.{ .name = "start.o", .bytes = new_object }};
 484 
 485     var old_linked = try linkExecutable(allocator, &old_inputs, options);
 486     defer old_linked.deinit(allocator);
 487     var state = try old_linked.prepareIncrementalState(allocator);
 488     defer state.deinit(allocator);
 489 
 490     var candidate = try linkExecutable(allocator, &new_inputs, options);
 491     defer candidate.deinit(allocator);
 492 
 493     const input_change_limits = incremental.InputChangeStorage.Limits.inspect(
 494         incremental.RecordedInputs.fromManifest(&state.manifest),
 495         &new_inputs,
 496     );
 497     const input_change_capacity = try incremental.InputChangeStorage.Capacity.derive(
 498         input_change_limits,
 499     );
 500     const input_change_bytes = try allocator.alignedAlloc(
 501         u8,
 502         .fromByteUnits(incremental.InputChangeStorage.storage_alignment),
 503         input_change_capacity.storage_bytes,
 504     );
 505     defer allocator.free(input_change_bytes);
 506     var input_change_storage = try incremental.InputChangeStorage.init(
 507         input_change_bytes,
 508         input_change_limits,
 509     );
 510     defer _ = input_change_storage.deinit();
 511     input_change_storage.activate();
 512     const input_changes = try state.classifyInputs(
 513         &input_change_storage,
 514         &new_inputs,
 515     );
 516     const replacements = try incremental.replacementContributionsFromImage(
 517         allocator,
 518         candidate.bytes,
 519         candidate.manifest,
 520         input_changes,
 521     );
 522     defer allocator.free(replacements);
 523 
 524     const application = try state.applyChangedInputRelinkFromInputChanges(
 525         options,
 526         input_changes,
 527         old_linked.bytes,
 528         replacements,
 529     );
 530     try std.testing.expectEqual(incremental.RelinkDecision.in_place, application.plan.decision);
 531     try std.testing.expect(!std.mem.eql(u8, candidate.bytes, old_linked.bytes));
 532 
 533     try patchIncrementalMetadata(old_linked.bytes, candidate.bytes);
 534     try state.updateManifestForCandidateRelinkFromInputChanges(
 535         allocator,
 536         &new_inputs,
 537         input_changes,
 538         replacements,
 539         candidate.manifest,
 540     );
 541 
 542     const text_header = try metadata.linkedSectionByName(old_linked.bytes, ".text");
 543     const candidate_text_header = try metadata.linkedSectionByName(candidate.bytes, ".text");
 544     try std.testing.expectEqual(candidate_text_header.size, text_header.size);
 545     try std.testing.expectEqual(candidate.manifest.sections[0].size, state.manifest.sections[0].size);
 546     try std.testing.expectEqual(@as(u64, new_text.len), state.manifest.contributions[0].size);
 547     try std.testing.expectEqualSlices(u8, candidate.bytes, old_linked.bytes);
 548 }
 549 
 550 test "ELF linker applies repeated relocations to one global symbol" {
 551     const allocator = std.testing.allocator;
 552 
 553     const caller_text = [_]u8{
 554         0xe8, 0, 0, 0, 0,
 555         0xe8, 0, 0, 0, 0,
 556         0xe8, 0, 0, 0, 0,
 557         0xc3,
 558     };
 559     const caller_text_index: u16 = 1;
 560     const callee_symbol: u32 = 3;
 561     const caller_object = try buildObject(allocator, .{
 562         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
 563         .symbols = &.{
 564             Symbol.section(caller_text_index),
 565             Symbol.function("_start", caller_text_index, 0, caller_text.len),
 566             Symbol.undefinedFunction("callee"),
 567         },
 568         .relocations = &.{
 569             Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4),
 570             Relocation.x86_64(1, 6, callee_symbol, .PLT32, -4),
 571             Relocation.x86_64(1, 11, callee_symbol, .PLT32, -4),
 572         },
 573     });
 574     defer allocator.free(caller_object);
 575 
 576     const callee_text = [_]u8{0xc3};
 577     const callee_text_index: u16 = 1;
 578     const callee_object = try buildObject(allocator, .{
 579         .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
 580         .symbols = &.{
 581             Symbol.section(callee_text_index),
 582             Symbol.function("callee", callee_text_index, 0, callee_text.len),
 583         },
 584     });
 585     defer allocator.free(callee_object);
 586 
 587     var linked = try linkExecutable(allocator, &.{
 588         .{ .name = "caller.o", .bytes = caller_object },
 589         .{ .name = "callee.o", .bytes = callee_object },
 590     }, .{ .incremental_mode = .off });
 591     defer linked.deinit(allocator);
 592 
 593     const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
 594     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
 595     try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
 596     try std.testing.expectEqual(@as(i32, 1), std.mem.readInt(i32, linked.bytes[text_file_offset + 11 ..][0..4], .little));
 597 }
 598 
 599 test "ELF linker resolves absolute symbol relocations" {
 600     const allocator = std.testing.allocator;
 601 
 602     const text = [_]u8{
 603         0x48, 0xb8,
 604         0,    0,
 605         0,    0,
 606         0,    0,
 607         0,    0,
 608         0xc3,
 609     };
 610     const text_index: u16 = 1;
 611     const absolute_symbol: u32 = 3;
 612     const caller_object = try buildObject(allocator, .{
 613         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
 614         .symbols = &.{
 615             Symbol.section(text_index),
 616             Symbol.function("_start", text_index, 0, text.len),
 617             Symbol.undefinedObject("absolute_value"),
 618         },
 619         .relocations = &.{Relocation.x86_64(text_index, 2, absolute_symbol, .@"64", 5)},
 620     });
 621     defer allocator.free(caller_object);
 622 
 623     const absolute_object = try buildObject(allocator, .{
 624         .sections = &.{},
 625         .symbols = &.{Symbol.absoluteObject("absolute_value", 37, 0)},
 626     });
 627     defer allocator.free(absolute_object);
 628 
 629     var linked = try linkExecutable(
 630         allocator,
 631         &.{
 632             .{ .name = "caller.o", .bytes = caller_object },
 633             .{ .name = "absolute.o", .bytes = absolute_object },
 634         },
 635         .{
 636             .incremental_mode = .off,
 637             .gc_sections = true,
 638         },
 639     );
 640     defer linked.deinit(allocator);
 641 
 642     const text_section = try metadata.linkedSectionByName(linked.bytes, ".text");
 643     try std.testing.expectEqual(@as(u64, 42), readU64(linked.bytes, @intCast(text_section.offset + 2)));
 644 }
 645 
 646 test "ELF linker resolves signed absolute relocations" {
 647     const allocator = std.testing.allocator;
 648 
 649     const text = [_]u8{0xc3};
 650     const data = @as([12]u8, @splat(0));
 651     const text_index: u16 = 1;
 652     const data_index: u16 = 2;
 653     const neg_symbol: u32 = 4;
 654     const caller_object = try buildObject(allocator, .{
 655         .sections = &.{
 656             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
 657             Section.progbits(".data.signed", &data, std.elf.SHF_WRITE, 8),
 658         },
 659         .symbols = &.{
 660             Symbol.section(text_index),
 661             Symbol.section(data_index),
 662             Symbol.function("_start", text_index, 0, text.len),
 663             Symbol.undefinedObject("negative_absolute"),
 664         },
 665         .relocations = &.{
 666             Relocation.x86_64(data_index, 0, neg_symbol, .@"32S", 0),
 667             Relocation.x86_64(data_index, 4, neg_symbol, .@"64", 0),
 668         },
 669     });
 670     defer allocator.free(caller_object);
 671 
 672     const absolute_object = try buildObject(allocator, .{
 673         .sections = &.{},
 674         .symbols = &.{Symbol.absoluteObject("negative_absolute", std.math.maxInt(u64), 0)},
 675     });
 676     defer allocator.free(absolute_object);
 677 
 678     var linked = try linkExecutable(
 679         allocator,
 680         &.{
 681             .{ .name = "caller.o", .bytes = caller_object },
 682             .{ .name = "absolute.o", .bytes = absolute_object },
 683         },
 684         .{ .incremental_mode = .off },
 685     );
 686     defer linked.deinit(allocator);
 687 
 688     const data_section = try metadata.linkedSectionByName(linked.bytes, ".data");
 689     const offset: usize = @intCast(data_section.offset);
 690     try std.testing.expectEqual(@as(i32, -1), std.mem.readInt(i32, linked.bytes[offset..][0..4], .little));
 691     try std.testing.expectEqual(std.math.maxInt(u64), std.mem.readInt(u64, linked.bytes[offset + 4 ..][0..8], .little));
 692 }
 693 
 694 test "ELF linker resolves narrow absolute relocations" {
 695     const allocator = std.testing.allocator;
 696 
 697     const text = [_]u8{0xc3};
 698     const data = [_]u8{ 0, 0, 0, 0 };
 699     const text_index: u16 = 1;
 700     const data_index: u16 = 2;
 701     const abs16_symbol: u32 = 4;
 702     const abs8_symbol: u32 = 5;
 703     const caller_object = try buildObject(allocator, .{
 704         .sections = &.{
 705             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
 706             Section.progbits(".data.narrow", &data, std.elf.SHF_WRITE, 2),
 707         },
 708         .symbols = &.{
 709             Symbol.section(text_index),
 710             Symbol.section(data_index),
 711             Symbol.function("_start", text_index, 0, text.len),
 712             Symbol.undefinedObject("abs16"),
 713             Symbol.undefinedObject("abs8"),
 714         },
 715         .relocations = &.{
 716             Relocation.x86_64(data_index, 0, abs16_symbol, .@"16", 0),
 717             Relocation.x86_64(data_index, 2, abs8_symbol, .@"8", 0),
 718             Relocation.x86_64(data_index, 3, abs8_symbol, .@"8", -91),
 719         },
 720     });
 721     defer allocator.free(caller_object);
 722 
 723     const absolute_object = try buildObject(allocator, .{
 724         .sections = &.{},
 725         .symbols = &.{ Symbol.absoluteObject("abs16", 0x1234, 0), Symbol.absoluteObject("abs8", 0x5a, 0) },
 726     });
 727     defer allocator.free(absolute_object);
 728 
 729     var linked = try linkExecutable(
 730         allocator,
 731         &.{
 732             .{ .name = "caller.o", .bytes = caller_object },
 733             .{ .name = "absolute.o", .bytes = absolute_object },
 734         },
 735         .{ .incremental_mode = .off },
 736     );
 737     defer linked.deinit(allocator);
 738 
 739     const data_section = try metadata.linkedSectionByName(linked.bytes, ".data");
 740     const offset: usize = @intCast(data_section.offset);
 741     try std.testing.expectEqual(@as(u16, 0x1234), std.mem.readInt(u16, linked.bytes[offset..][0..2], .little));
 742     try std.testing.expectEqual(@as(u8, 0x5a), linked.bytes[offset + 2]);
 743     try std.testing.expectEqual(@as(u8, 0xff), linked.bytes[offset + 3]);
 744 }
 745 
 746 test "ELF linker resolves narrow PC-relative relocations" {
 747     const allocator = std.testing.allocator;
 748 
 749     const text = [_]u8{0xc3};
 750     const data = [_]u8{ 0, 0, 0, 0 };
 751     const text_index: u16 = 1;
 752     const data_index: u16 = 2;
 753     const target_symbol: u32 = 4;
 754     const object = try buildObject(allocator, .{
 755         .sections = &.{
 756             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
 757             Section.progbits(".data.pc", &data, std.elf.SHF_WRITE, 2),
 758         },
 759         .symbols = &.{
 760             Symbol.section(text_index),
 761             Symbol.section(data_index),
 762             Symbol.function("_start", text_index, 0, text.len),
 763             Symbol.object("pc_target", data_index, 3, 1),
 764         },
 765         .relocations = &.{
 766             Relocation.x86_64(data_index, 0, target_symbol, .PC16, 0),
 767             Relocation.x86_64(data_index, 2, target_symbol, .PC8, 0),
 768         },
 769     });
 770     defer allocator.free(object);
 771 
 772     var linked = try linkExecutable(allocator, &.{.{ .name = "pc.o", .bytes = object }}, .{ .incremental_mode = .off });
 773     defer linked.deinit(allocator);
 774 
 775     const data_record = metadata.manifestSection(linked.manifest, ".data") orelse return error.MissingSection;
 776     const offset: usize = @intCast(data_record.file_offset);
 777     try std.testing.expectEqual(@as(i16, 3), std.mem.readInt(i16, linked.bytes[offset..][0..2], .little));
 778     try std.testing.expectEqual(@as(u8, 1), linked.bytes[offset + 2]);
 779     try std.testing.expectEqual(@as(u8, 0), linked.bytes[offset + 3]);
 780 }
 781 
 782 test "ELF linker discards duplicate COMDAT groups by signature" {
 783     const allocator = std.testing.allocator;
 784 
 785     const retained_text = [_]u8{0xc3};
 786     const retained_text_index: u16 = 1;
 787     const retained_symbol: u32 = 2;
 788     var retained_group_payload: [(1 + 1) * 4]u8 = undefined;
 789     const retained_group_bytes = groupBytes(&retained_group_payload, &.{retained_text_index});
 790     const retained_object = try buildObject(allocator, .{
 791         .sections = &.{
 792             Section.progbits(".text.inline", &retained_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
 793             Section.group(".group", retained_group_bytes, retained_symbol),
 794         },
 795         .symbols = &.{
 796             Symbol.section(retained_text_index),
 797             Symbol.function("inline_fn", retained_text_index, 0, retained_text.len),
 798         },
 799     });
 800     defer allocator.free(retained_object);
 801 
 802     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
 803     const caller_text_index: u16 = 1;
 804     const duplicate_text = [_]u8{0xcc};
 805     const duplicate_text_index: u16 = 2;
 806     const duplicate_symbol: u32 = 4;
 807     var caller_group_payload: [(1 + 1) * 4]u8 = undefined;
 808     const caller_group_bytes = groupBytes(&caller_group_payload, &.{duplicate_text_index});
 809     const caller_object = try buildObject(allocator, .{
 810         .sections = &.{
 811             Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16),
 812             Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
 813             Section.group(".group", caller_group_bytes, duplicate_symbol),
 814         },
 815         .symbols = &.{
 816             Symbol.section(caller_text_index),
 817             Symbol.section(duplicate_text_index),
 818             Symbol.function("_start", caller_text_index, 0, caller_text.len),
 819             Symbol.function("inline_fn", duplicate_text_index, 0, duplicate_text.len),
 820         },
 821         .relocations = &.{Relocation.x86_64(1, 1, duplicate_symbol, .PLT32, -4)},
 822     });
 823     defer allocator.free(caller_object);
 824 
 825     const inputs = [_]model.Input{
 826         .{ .name = "retained.o", .bytes = retained_object },
 827         .{ .name = "caller.o", .bytes = caller_object },
 828     };
 829 
 830     var linked = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
 831     defer linked.deinit(allocator);
 832 
 833     var manifest_linked = try linkExecutable(allocator, &inputs, .{});
 834     defer manifest_linked.deinit(allocator);
 835 
 836     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
 837     try std.testing.expectEqual(@as(u64, 22), text_record.size);
 838     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retained.o", .section, ".text.inline", retained_text_index) != null);
 839     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "caller.o", .section, ".text.inline", duplicate_text_index) == null);
 840     const discarded_duplicate = metadata.manifestDiscardedContribution(manifest_linked.manifest, "caller.o", ".text.inline", duplicate_text_index) orelse return error.MissingSection;
 841     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_duplicate.reason);
 842 
 843     const text_file_offset: usize = @intCast(text_record.file_offset);
 844     try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_file_offset]);
 845     try std.testing.expectEqual(@as(i32, -21), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
 846 }
 847 
 848 test "ELF linker uses section names for unnamed COMDAT section signatures" {
 849     const allocator = std.testing.allocator;
 850 
 851     const start_text = [_]u8{0xc3};
 852     const start_text_index: u16 = 1;
 853     const start_object = try buildObject(allocator, .{
 854         .sections = &.{Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16)},
 855         .symbols = &.{
 856             Symbol.section(start_text_index),
 857             Symbol.function("_start", start_text_index, 0, start_text.len),
 858         },
 859     });
 860     defer allocator.free(start_object);
 861 
 862     const first_text = [_]u8{0x90};
 863     const first_text_index: u16 = 1;
 864     const first_section_symbol: u32 = 1;
 865     var first_group_payload: [(1 + 1) * 4]u8 = undefined;
 866     const first_group_bytes = groupBytes(&first_group_payload, &.{first_text_index});
 867     const first_object = try buildObject(allocator, .{
 868         .sections = &.{
 869             Section.progbits(".text.inline", &first_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
 870             Section.group(".group", first_group_bytes, first_section_symbol),
 871         },
 872         .symbols = &.{Symbol.section(first_text_index)},
 873     });
 874     defer allocator.free(first_object);
 875 
 876     const duplicate_text = [_]u8{0xcc};
 877     const duplicate_text_index: u16 = 1;
 878     const duplicate_section_symbol: u32 = 1;
 879     var duplicate_group_payload: [(1 + 1) * 4]u8 = undefined;
 880     const duplicate_group_bytes = groupBytes(&duplicate_group_payload, &.{duplicate_text_index});
 881     const duplicate_object = try buildObject(allocator, .{
 882         .sections = &.{
 883             Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
 884             Section.group(".group", duplicate_group_bytes, duplicate_section_symbol),
 885         },
 886         .symbols = &.{Symbol.section(duplicate_text_index)},
 887     });
 888     defer allocator.free(duplicate_object);
 889 
 890     var linked = try linkExecutable(allocator, &.{
 891         .{ .name = "start.o", .bytes = start_object },
 892         .{ .name = "first.o", .bytes = first_object },
 893         .{ .name = "duplicate.o", .bytes = duplicate_object },
 894     }, .{});
 895     defer linked.deinit(allocator);
 896 
 897     try std.testing.expect(metadata.manifestContribution(linked.manifest, "first.o", .section, ".text.inline", first_text_index) != null);
 898     try std.testing.expect(metadata.manifestContribution(linked.manifest, "duplicate.o", .section, ".text.inline", duplicate_text_index) == null);
 899     const discarded_duplicate = metadata.manifestDiscardedContribution(linked.manifest, "duplicate.o", ".text.inline", duplicate_text_index) orelse return error.MissingSection;
 900     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_duplicate.reason);
 901 }
 902 
 903 test "ELF linker garbage collects unreferenced alloc sections" {
 904     const allocator = std.testing.allocator;
 905 
 906     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
 907     const helper_text = [_]u8{0xc3};
 908     const dead_text = [_]u8{0xcc};
 909     const start_index: u16 = 1;
 910     const helper_index: u16 = 2;
 911     const dead_index: u16 = 3;
 912     const helper_symbol: u32 = 5;
 913     const object = try buildObject(allocator, .{
 914         .sections = &.{
 915             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
 916             Section.progbits(".text.helper", &helper_text, std.elf.SHF_EXECINSTR, 16),
 917             Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
 918         },
 919         .symbols = &.{
 920             Symbol.section(start_index),
 921             Symbol.section(helper_index),
 922             Symbol.section(dead_index),
 923             Symbol.function("_start", start_index, 0, start_text.len),
 924             Symbol.function("helper", helper_index, 0, helper_text.len),
 925             Symbol.function("dead", dead_index, 0, dead_text.len),
 926         },
 927         .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .PLT32, -4)},
 928     });
 929     defer allocator.free(object);
 930 
 931     const inputs = [_]model.Input{.{ .name = "gc.o", .bytes = object }};
 932     var linked = try linkExecutable(allocator, &inputs, .{
 933         .incremental_mode = .off,
 934         .gc_sections = true,
 935     });
 936     defer linked.deinit(allocator);
 937 
 938     var manifest_linked = try linkExecutable(allocator, &inputs, .{
 939         .gc_sections = true,
 940     });
 941     defer manifest_linked.deinit(allocator);
 942 
 943     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
 944     try std.testing.expectEqual(@as(u64, 17), text_record.size);
 945     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.start", start_index) != null);
 946     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.helper", helper_index) != null);
 947     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.dead", dead_index) == null);
 948     const discarded_dead = metadata.manifestDiscardedContribution(manifest_linked.manifest, "gc.o", ".text.dead", dead_index) orelse return error.MissingSection;
 949     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
 950 
 951     const text_file_offset: usize = @intCast(text_record.file_offset);
 952     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
 953 }
 954 
 955 test "ELF linker keeps GNU retain sections during GC" {
 956     const allocator = std.testing.allocator;
 957 
 958     const start_text = [_]u8{0xc3};
 959     const retained_data = [_]u8{ 0x6d, 0x25, 0xe3, 0x91, 0x4a, 0xbf, 0x70, 0x0d };
 960     const dead_data = [_]u8{ 0xd0, 0x5c, 0xaa, 0x11 };
 961     const start_index: u16 = 1;
 962     const retained_index: u16 = 2;
 963     const dead_index: u16 = 3;
 964     const object = try buildObject(allocator, .{
 965         .sections = &.{
 966             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
 967             Section.progbits(".rodata.retain", &retained_data, std.elf.SHF_GNU_RETAIN, 8),
 968             Section.progbits(".rodata.dead", &dead_data, 0, 4),
 969         },
 970         .symbols = &.{
 971             Symbol.section(start_index),
 972             Symbol.section(retained_index),
 973             Symbol.section(dead_index),
 974             Symbol.function("_start", start_index, 0, start_text.len),
 975             Symbol.object("retained_probe", retained_index, 0, retained_data.len),
 976             Symbol.object("dead_probe", dead_index, 0, dead_data.len),
 977         },
 978     });
 979     defer allocator.free(object);
 980 
 981     const inputs = [_]model.Input{.{ .name = "retain.o", .bytes = object }};
 982     var linked = try linkExecutable(allocator, &inputs, .{
 983         .incremental_mode = .off,
 984         .gc_sections = true,
 985     });
 986     defer linked.deinit(allocator);
 987 
 988     var manifest_linked = try linkExecutable(allocator, &inputs, .{
 989         .gc_sections = true,
 990     });
 991     defer manifest_linked.deinit(allocator);
 992 
 993     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".text.start", start_index) != null);
 994     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".rodata.retain", retained_index) != null);
 995     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".rodata.dead", dead_index) == null);
 996     const discarded_dead = metadata.manifestDiscardedContribution(manifest_linked.manifest, "retain.o", ".rodata.dead", dead_index) orelse return error.MissingSection;
 997     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
 998 }
 999 
1000 test "ELF linker keeps init arrays during GC" {
1001     const allocator = std.testing.allocator;
1002 
1003     const start_text = [_]u8{0xc3};
1004     const ctor_text = [_]u8{ 0xb8, 0x5d, 0x00, 0x00, 0x00, 0xc3 };
1005     const init_array = @as([8]u8, @splat(0));
1006     const dead_text = [_]u8{0xcc};
1007     const start_index: u16 = 1;
1008     const ctor_index: u16 = 2;
1009     const init_index: u16 = 3;
1010     const dead_index: u16 = 4;
1011     const ctor_symbol: u32 = 6;
1012     const object = try buildObject(allocator, .{
1013         .sections = &.{
1014             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1015             Section.progbits(".text.ctor", &ctor_text, std.elf.SHF_EXECINSTR, 16),
1016             .{
1017                 .name = ".init_array",
1018                 .section_type = std.elf.SHT_INIT_ARRAY,
1019                 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1020                 .alignment = 8,
1021                 .bytes = &init_array,
1022             },
1023             Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
1024         },
1025         .symbols = &.{
1026             Symbol.section(start_index),
1027             Symbol.section(ctor_index),
1028             Symbol.section(init_index),
1029             Symbol.section(dead_index),
1030             Symbol.function("_start", start_index, 0, start_text.len),
1031             Symbol.function("ctor", ctor_index, 0, ctor_text.len),
1032             Symbol.function("dead", dead_index, 0, dead_text.len),
1033         },
1034         .relocations = &.{Relocation.x86_64(init_index, 0, ctor_symbol, .@"64", 0)},
1035     });
1036     defer allocator.free(object);
1037 
1038     var linked = try linkExecutable(allocator, &.{.{ .name = "init.o", .bytes = object }}, .{
1039         .gc_sections = true,
1040     });
1041     defer linked.deinit(allocator);
1042 
1043     const init_contribution = metadata.manifestContribution(linked.manifest, "init.o", .section, ".init_array", init_index) orelse return error.MissingSection;
1044     const ctor_contribution = metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.ctor", ctor_index) orelse return error.MissingSection;
1045     try std.testing.expect(metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.start", start_index) != null);
1046     try std.testing.expect(metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.dead", dead_index) == null);
1047     const discarded_dead = metadata.manifestDiscardedContribution(linked.manifest, "init.o", ".text.dead", dead_index) orelse return error.MissingSection;
1048     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
1049 
1050     const init_file_offset: usize = @intCast(init_contribution.file_offset);
1051     try std.testing.expectEqual(ctor_contribution.address, std.mem.readInt(u64, linked.bytes[init_file_offset..][0..8], .little));
1052 }
1053 
1054 test "ELF linker keeps allocated note sections during GC" {
1055     const allocator = std.testing.allocator;
1056 
1057     const start_text = [_]u8{0xc3};
1058     const note_payload = [_]u8{
1059         4,    0,    0,    0,
1060         4,    0,    0,    0,
1061         0x52, 0x44, 0x4c, 0x54,
1062         'T',  'L',  'D',  'R',
1063         0x79, 0x41, 0x6e, 0x23,
1064     };
1065     const dead_text = [_]u8{0xcc};
1066     const start_index: u16 = 1;
1067     const note_index: u16 = 2;
1068     const dead_index: u16 = 3;
1069     const object = try buildObject(allocator, .{
1070         .sections = &.{
1071             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1072             .{
1073                 .name = ".note.tldr",
1074                 .section_type = std.elf.SHT_NOTE,
1075                 .flags = std.elf.SHF_ALLOC,
1076                 .alignment = 4,
1077                 .bytes = &note_payload,
1078             },
1079             Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
1080         },
1081         .symbols = &.{
1082             Symbol.section(start_index),
1083             Symbol.section(note_index),
1084             Symbol.section(dead_index),
1085             Symbol.function("_start", start_index, 0, start_text.len),
1086             Symbol.function("dead", dead_index, 0, dead_text.len),
1087         },
1088     });
1089     defer allocator.free(object);
1090 
1091     var linked = try linkExecutable(allocator, &.{.{ .name = "note.o", .bytes = object }}, .{
1092         .gc_sections = true,
1093     });
1094     defer linked.deinit(allocator);
1095 
1096     const note_contribution = metadata.manifestContribution(linked.manifest, "note.o", .section, ".note.tldr", note_index) orelse return error.MissingSection;
1097     try std.testing.expect(metadata.manifestContribution(linked.manifest, "note.o", .section, ".text.start", start_index) != null);
1098     try std.testing.expect(metadata.manifestContribution(linked.manifest, "note.o", .section, ".text.dead", dead_index) == null);
1099     const discarded_dead = metadata.manifestDiscardedContribution(linked.manifest, "note.o", ".text.dead", dead_index) orelse return error.MissingSection;
1100     try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
1101 
1102     const note_file_offset: usize = @intCast(note_contribution.file_offset);
1103     try std.testing.expectEqualSlices(u8, &note_payload, linked.bytes[note_file_offset..][0..note_payload.len]);
1104 }
1105 
1106 test "ELF linker ignores no-op relocations during GC" {
1107     const allocator = std.testing.allocator;
1108 
1109     const text = [_]u8{0xc3};
1110     const text_index: u16 = 1;
1111     const object = try buildObject(allocator, .{
1112         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1113         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1114         .relocations = &.{Relocation.x86_64(text_index, 0, 0, .NONE, 0)},
1115     });
1116     defer allocator.free(object);
1117 
1118     var linked = try linkExecutable(allocator, &.{.{ .name = "none-gc.o", .bytes = object }}, .{
1119         .incremental_mode = .off,
1120         .gc_sections = true,
1121     });
1122     defer linked.deinit(allocator);
1123 
1124     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1125     try std.testing.expectEqual(@as(u64, text.len), text_record.size);
1126 }
1127 
1128 test "ELF linker resolves start and stop section boundary symbols" {
1129     const allocator = std.testing.allocator;
1130 
1131     const text = [_]u8{
1132         0x48, 0xb8,
1133         0,    0,
1134         0,    0,
1135         0,    0,
1136         0,    0,
1137         0x48, 0xbb,
1138         0,    0,
1139         0,    0,
1140         0,    0,
1141         0,    0,
1142         0xc3,
1143     };
1144     const caller_text_index: u16 = 1;
1145     const start_symbol: u32 = 3;
1146     const stop_symbol: u32 = 4;
1147     const caller_object = try buildObject(allocator, .{
1148         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1149         .symbols = &.{
1150             Symbol.section(caller_text_index),
1151             Symbol.function("_start", caller_text_index, 0, text.len),
1152             Symbol.undefinedObject("__start_meta"),
1153             Symbol.undefinedObject("__stop_meta"),
1154         },
1155         .relocations = &.{
1156             Relocation.x86_64(caller_text_index, 2, start_symbol, .@"64", 0),
1157             Relocation.x86_64(caller_text_index, 12, stop_symbol, .@"64", 0),
1158         },
1159     });
1160     defer allocator.free(caller_object);
1161 
1162     const meta = [_]u8{ 0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55 };
1163     const meta_object = try buildObject(allocator, .{
1164         .sections = &.{Section.progbits("meta", &meta, 0, 1)},
1165         .symbols = &.{Symbol.section(1)},
1166     });
1167     defer allocator.free(meta_object);
1168 
1169     var linked = try linkExecutable(
1170         allocator,
1171         &.{
1172             .{ .name = "caller.o", .bytes = caller_object },
1173             .{ .name = "meta.o", .bytes = meta_object },
1174         },
1175         .{
1176             .incremental_mode = .off,
1177             .gc_sections = true,
1178         },
1179     );
1180     defer linked.deinit(allocator);
1181 
1182     const text_section = try metadata.linkedSectionByName(linked.bytes, ".text");
1183     const rodata_section = try metadata.linkedSectionByName(linked.bytes, ".rodata");
1184     try std.testing.expectEqual(@as(u64, meta.len), rodata_section.size);
1185     try std.testing.expectEqual(rodata_section.address, readU64(linked.bytes, @intCast(text_section.offset + 2)));
1186     try std.testing.expectEqual(rodata_section.address + meta.len, readU64(linked.bytes, @intCast(text_section.offset + 12)));
1187 }
1188 
1189 test "ELF linker resolves runtime array boundary symbols" {
1190     const allocator = std.testing.allocator;
1191 
1192     const text = [_]u8{
1193         0x48, 0xb8,
1194         0,    0,
1195         0,    0,
1196         0,    0,
1197         0,    0,
1198         0x48, 0xbb,
1199         0,    0,
1200         0,    0,
1201         0,    0,
1202         0,    0,
1203         0xc3,
1204     };
1205     const caller_text_index: u16 = 1;
1206     const start_symbol: u32 = 3;
1207     const end_symbol: u32 = 4;
1208     const caller_object = try buildObject(allocator, .{
1209         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1210         .symbols = &.{
1211             Symbol.section(caller_text_index),
1212             Symbol.function("_start", caller_text_index, 0, text.len),
1213             Symbol.undefinedObject("__init_array_start"),
1214             Symbol.undefinedObject("__init_array_end"),
1215         },
1216         .relocations = &.{
1217             Relocation.x86_64(caller_text_index, 2, start_symbol, .@"64", 0),
1218             Relocation.x86_64(caller_text_index, 12, end_symbol, .@"64", 0),
1219         },
1220     });
1221     defer allocator.free(caller_object);
1222 
1223     const first = @as([8]u8, @splat(0x11));
1224     const second = @as([8]u8, @splat(0x22));
1225     const first_index: u16 = 1;
1226     const second_index: u16 = 2;
1227     const arrays_object = try buildObject(allocator, .{
1228         .sections = &.{
1229             .{
1230                 .name = ".init_array",
1231                 .section_type = std.elf.SHT_INIT_ARRAY,
1232                 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1233                 .alignment = 8,
1234                 .bytes = &first,
1235             },
1236             .{
1237                 .name = ".init_array.100",
1238                 .section_type = std.elf.SHT_INIT_ARRAY,
1239                 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1240                 .alignment = 8,
1241                 .bytes = &second,
1242             },
1243         },
1244         .symbols = &.{ Symbol.section(first_index), Symbol.section(second_index) },
1245     });
1246     defer allocator.free(arrays_object);
1247 
1248     var linked = try linkExecutable(
1249         allocator,
1250         &.{
1251             .{ .name = "caller.o", .bytes = caller_object },
1252             .{ .name = "arrays.o", .bytes = arrays_object },
1253         },
1254         .{ .gc_sections = true },
1255     );
1256     defer linked.deinit(allocator);
1257 
1258     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1259     const first_contribution = metadata.manifestContribution(linked.manifest, "arrays.o", .section, ".init_array", first_index) orelse return error.MissingSection;
1260     const second_contribution = metadata.manifestContribution(linked.manifest, "arrays.o", .section, ".init_array.100", second_index) orelse return error.MissingSection;
1261     const expected_start = @min(first_contribution.address, second_contribution.address);
1262     const expected_end = @max(first_contribution.address + first_contribution.size, second_contribution.address + second_contribution.size);
1263     const text_offset: usize = @intCast(text_record.file_offset);
1264     try std.testing.expectEqual(expected_start, readU64(linked.bytes, text_offset + 2));
1265     try std.testing.expectEqual(expected_end, readU64(linked.bytes, text_offset + 12));
1266 }
1267 
1268 test "ELF linker resolves absent runtime array boundaries to equal addresses" {
1269     const allocator = std.testing.allocator;
1270 
1271     const text = [_]u8{
1272         0x48, 0xb8,
1273         0,    0,
1274         0,    0,
1275         0,    0,
1276         0,    0,
1277         0x48, 0xbb,
1278         0,    0,
1279         0,    0,
1280         0,    0,
1281         0,    0,
1282         0xc3,
1283     };
1284     const text_index: u16 = 1;
1285     const start_symbol: u32 = 3;
1286     const end_symbol: u32 = 4;
1287     const object = try buildObject(allocator, .{
1288         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1289         .symbols = &.{
1290             Symbol.section(text_index),
1291             Symbol.function("_start", text_index, 0, text.len),
1292             Symbol.undefinedObject("__preinit_array_start"),
1293             Symbol.undefinedObject("__preinit_array_end"),
1294         },
1295         .relocations = &.{
1296             Relocation.x86_64(text_index, 2, start_symbol, .@"64", 0),
1297             Relocation.x86_64(text_index, 12, end_symbol, .@"64", 0),
1298         },
1299     });
1300     defer allocator.free(object);
1301 
1302     var linked = try linkExecutable(allocator, &.{.{ .name = "preinit.o", .bytes = object }}, .{});
1303     defer linked.deinit(allocator);
1304 
1305     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1306     const text_offset: usize = @intCast(text_record.file_offset);
1307     try std.testing.expectEqual(readU64(linked.bytes, text_offset + 2), readU64(linked.bytes, text_offset + 12));
1308 }
1309 
1310 test "ELF linker resolves ELF header start symbol" {
1311     const allocator = std.testing.allocator;
1312 
1313     const text = [_]u8{
1314         0x48, 0xb8,
1315         0,    0,
1316         0,    0,
1317         0,    0,
1318         0,    0,
1319         0xc3,
1320     };
1321     const text_index: u16 = 1;
1322     const ehdr_start: u32 = 3;
1323     const object = try buildObject(allocator, .{
1324         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1325         .symbols = &.{
1326             Symbol.section(text_index),
1327             Symbol.function("_start", text_index, 0, text.len),
1328             Symbol.undefinedObject("__ehdr_start"),
1329         },
1330         .relocations = &.{Relocation.x86_64(text_index, 2, ehdr_start, .@"64", 0)},
1331     });
1332     defer allocator.free(object);
1333 
1334     var linked = try linkExecutable(allocator, &.{.{ .name = "ehdr.o", .bytes = object }}, .{});
1335     defer linked.deinit(allocator);
1336 
1337     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1338     const text_offset: usize = @intCast(text_record.file_offset);
1339     try std.testing.expectEqual(@as(u64, 0x400000), readU64(linked.bytes, text_offset + 2));
1340 }
1341 
1342 test "ELF linker records undefined relocation symbol diagnostics" {
1343     const allocator = std.testing.allocator;
1344 
1345     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1346     const start_index: u16 = 1;
1347     const missing_symbol: u32 = 3;
1348     const second_missing_symbol: u32 = 4;
1349     const object = try buildObject(allocator, .{
1350         .sections = &.{Section.progbits(".text", &start_text, std.elf.SHF_EXECINSTR, 16)},
1351         .symbols = &.{
1352             Symbol.section(start_index),
1353             Symbol.function("_start", start_index, 0, start_text.len),
1354             Symbol.undefinedFunction("missing_runtime_symbol"),
1355             Symbol.undefinedFunction("second_missing_runtime_symbol"),
1356         },
1357         .relocations = &.{
1358             Relocation.x86_64(1, 1, missing_symbol, .PLT32, -4),
1359             Relocation.x86_64(1, 6, second_missing_symbol, .PLT32, -4),
1360         },
1361     });
1362     defer allocator.free(object);
1363 
1364     var diagnostics: model.Diagnostics = .{};
1365     try std.testing.expectError(error.UndefinedSymbol, linkExecutable(
1366         allocator,
1367         &.{.{ .name = "missing.o", .bytes = object }},
1368         .{
1369             .incremental_mode = .off,
1370             .diagnostics = &diagnostics,
1371         },
1372     ));
1373 
1374     const undefined_symbol = diagnostics.first_undefined_symbol orelse return error.ExpectedUndefinedSymbolDiagnostic;
1375     try std.testing.expectEqualStrings("missing_runtime_symbol", undefined_symbol.name);
1376     try std.testing.expectEqualStrings("missing.o", undefined_symbol.input_name);
1377     const undefined_symbols = diagnostics.recordedUndefinedSymbols();
1378     try std.testing.expectEqual(@as(usize, 2), undefined_symbols.len);
1379     try std.testing.expectEqualStrings("missing_runtime_symbol", undefined_symbols[0].name);
1380     try std.testing.expectEqualStrings("second_missing_runtime_symbol", undefined_symbols[1].name);
1381 }
1382 
1383 test "ELF linker records unsupported relocation diagnostics" {
1384     const allocator = std.testing.allocator;
1385 
1386     const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1387     const text_index: u16 = 1;
1388     const helper_symbol: u32 = 3;
1389     const object = try buildObject(allocator, .{
1390         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1391         .symbols = &.{
1392             Symbol.section(text_index),
1393             Symbol.function("_start", text_index, 0, text.len),
1394             Symbol.function("helper", text_index, 0, text.len),
1395         },
1396         .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .GOT64, -4)},
1397     });
1398     defer allocator.free(object);
1399 
1400     var diagnostics: model.Diagnostics = .{};
1401     try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
1402         allocator,
1403         &.{.{ .name = "unsupported.o", .bytes = object }},
1404         .{
1405             .incremental_mode = .off,
1406             .diagnostics = &diagnostics,
1407         },
1408     ));
1409 
1410     const unsupported = diagnostics.first_unsupported_relocation orelse return error.ExpectedUnsupportedRelocationDiagnostic;
1411     try std.testing.expectEqualStrings("unsupported.o", unsupported.input_name);
1412     try std.testing.expectEqualStrings(".text", unsupported.section_name);
1413     try std.testing.expectEqualStrings("helper", unsupported.symbol_name);
1414     try std.testing.expectEqual(@backingInt(std.elf.R_X86_64.GOT64), unsupported.relocation_type);
1415 }
1416 
1417 const CommitWitness = struct {
1418     count: usize = 0,
1419 
1420     fn observer(self: *CommitWitness) model.LinkCommitObserver {
1421         return .{ .context = self, .committed = committed };
1422     }
1423 
1424     fn committed(context: *anyopaque) void {
1425         const self: *CommitWitness = @ptrCast(@alignCast(context));
1426         self.count += 1;
1427     }
1428 };
1429 
1430 fn startObjectAlloc(allocator: Allocator) ![]u8 {
1431     const text = [_]u8{
1432         0xb8, 0x3c, 0x00, 0x00, 0x00,
1433         0x31, 0xff, 0x0f, 0x05,
1434     };
1435     const text_index: u16 = 1;
1436     return try buildObject(allocator, .{
1437         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1438         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1439     });
1440 }
1441 
1442 test "ELF linker notifies the commit observer once on success" {
1443     const allocator = std.testing.allocator;
1444     const object = try startObjectAlloc(allocator);
1445     defer allocator.free(object);
1446 
1447     var witness: CommitWitness = .{};
1448     var linked = try linkExecutable(
1449         allocator,
1450         &.{.{ .name = "start.o", .bytes = object }},
1451         .{ .commit_observer = witness.observer() },
1452     );
1453     defer linked.deinit(allocator);
1454 
1455     try std.testing.expectEqual(@as(usize, 1), witness.count);
1456 }
1457 
1458 test "ELF linker withholds commit until resolution failures are impossible" {
1459     const allocator = std.testing.allocator;
1460 
1461     const call = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1462     const undefined_text: u16 = 1;
1463     const missing_symbol: u32 = 3;
1464     const undefined_object = try buildObject(allocator, .{
1465         .sections = &.{Section.progbits(".text", &call, std.elf.SHF_EXECINSTR, 16)},
1466         .symbols = &.{
1467             Symbol.section(undefined_text),
1468             Symbol.function("_start", undefined_text, 0, call.len),
1469             Symbol.undefinedFunction("missing"),
1470         },
1471         .relocations = &.{Relocation.x86_64(1, 1, missing_symbol, .PLT32, -4)},
1472     });
1473     defer allocator.free(undefined_object);
1474 
1475     var undefined_witness: CommitWitness = .{};
1476     try std.testing.expectError(error.UndefinedSymbol, linkExecutable(
1477         allocator,
1478         &.{.{ .name = "undefined.o", .bytes = undefined_object }},
1479         .{ .commit_observer = undefined_witness.observer() },
1480     ));
1481     try std.testing.expectEqual(@as(usize, 0), undefined_witness.count);
1482 
1483     const unsupported_text: u16 = 1;
1484     const helper_symbol: u32 = 3;
1485     const unsupported_object = try buildObject(allocator, .{
1486         .sections = &.{Section.progbits(".text", &call, std.elf.SHF_EXECINSTR, 16)},
1487         .symbols = &.{
1488             Symbol.section(unsupported_text),
1489             Symbol.function("_start", unsupported_text, 0, call.len),
1490             Symbol.function("helper", unsupported_text, 0, call.len),
1491         },
1492         .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .GOT64, -4)},
1493     });
1494     defer allocator.free(unsupported_object);
1495 
1496     var unsupported_witness: CommitWitness = .{};
1497     try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
1498         allocator,
1499         &.{.{ .name = "unsupported.o", .bytes = unsupported_object }},
1500         .{ .commit_observer = unsupported_witness.observer() },
1501     ));
1502     try std.testing.expectEqual(@as(usize, 0), unsupported_witness.count);
1503 }
1504 
1505 test "ELF linker fails missing entry symbols before commit" {
1506     const allocator = std.testing.allocator;
1507     const object = try startObjectAlloc(allocator);
1508     defer allocator.free(object);
1509 
1510     var witness: CommitWitness = .{};
1511     try std.testing.expectError(error.MissingEntrySymbol, linkExecutable(
1512         allocator,
1513         &.{.{ .name = "start.o", .bytes = object }},
1514         .{
1515             .entry_symbol = "absent_entry",
1516             .commit_observer = witness.observer(),
1517         },
1518     ));
1519     try std.testing.expectEqual(@as(usize, 0), witness.count);
1520 }
1521 
1522 test "ELF linker applies no-op pc64 and size relocations" {
1523     const allocator = std.testing.allocator;
1524 
1525     const text = [_]u8{0xc3};
1526     const payload_bytes = [_]u8{ 'p', 'a', 'y', 'l', 'o', 'a', 'd' };
1527     var data = @as([32]u8, @splat(0xaa));
1528     const text_index: u16 = 1;
1529     const payload_index: u16 = 2;
1530     const data_index: u16 = 3;
1531     const start_symbol: u32 = 4;
1532     const payload_symbol: u32 = 5;
1533     const object = try buildObject(allocator, .{
1534         .sections = &.{
1535             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1536             Section.progbits(".rodata.payload", &payload_bytes, 0, 1),
1537             Section.progbits(".data.relocs", &data, std.elf.SHF_WRITE, 8),
1538         },
1539         .symbols = &.{
1540             Symbol.section(text_index),
1541             Symbol.section(payload_index),
1542             Symbol.section(data_index),
1543             Symbol.function("_start", text_index, 0, text.len),
1544             Symbol.object("payload", payload_index, 0, payload_bytes.len),
1545         },
1546         .relocations = &.{
1547             Relocation.x86_64(data_index, 0, 0, .NONE, 0),
1548             Relocation.x86_64(data_index, 8, payload_symbol, .SIZE64, 5),
1549             Relocation.x86_64(data_index, 16, payload_symbol, .SIZE32, -1),
1550             Relocation.x86_64(data_index, 24, start_symbol, .PC64, 0),
1551         },
1552     });
1553     defer allocator.free(object);
1554 
1555     var linked = try linkExecutable(allocator, &.{.{ .name = "standard-relocs.o", .bytes = object }}, .{ .incremental_mode = .off });
1556     defer linked.deinit(allocator);
1557 
1558     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1559     const data_record = metadata.manifestSection(linked.manifest, ".data") orelse return error.MissingSection;
1560     const data_offset: usize = @intCast(data_record.file_offset);
1561     try std.testing.expectEqualSlices(u8, data[0..8], linked.bytes[data_offset..][0..8]);
1562     try std.testing.expectEqual(@as(u64, payload_bytes.len + 5), std.mem.readInt(u64, linked.bytes[data_offset + 8 ..][0..8], .little));
1563     try std.testing.expectEqual(@as(u32, payload_bytes.len - 1), std.mem.readInt(u32, linked.bytes[data_offset + 16 ..][0..4], .little));
1564 
1565     const pc64 = std.mem.readInt(i64, linked.bytes[data_offset + 24 ..][0..8], .little);
1566     const expected_pc64: i64 = @intCast(@as(i128, text_record.address) - @as(i128, data_record.address + 24));
1567     try std.testing.expectEqual(expected_pc64, pc64);
1568 }
1569 
1570 test "ELF linker applies relocations in mergeable constant sections" {
1571     const allocator = std.testing.allocator;
1572 
1573     const text = [_]u8{0xc3};
1574     var constants = @as([16]u8, @splat(0));
1575     const text_index: u16 = 1;
1576     const constants_index: u16 = 2;
1577     const start_symbol: u32 = 3;
1578     const object = try buildObject(allocator, .{
1579         .sections = &.{
1580             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1581             Section.mergeable(".rodata.cst8", &constants, 0, 8, 8),
1582         },
1583         .symbols = &.{
1584             Symbol.section(text_index),
1585             Symbol.section(constants_index),
1586             Symbol.function("_start", text_index, 0, text.len),
1587         },
1588         .relocations = &.{Relocation.x86_64(constants_index, 0, start_symbol, .@"64", 0)},
1589     });
1590     defer allocator.free(object);
1591 
1592     var linked = try linkExecutable(allocator, &.{.{ .name = "merge-reloc.o", .bytes = object }}, .{ .incremental_mode = .off });
1593     defer linked.deinit(allocator);
1594 
1595     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1596     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
1597     const rodata_offset: usize = @intCast(rodata_record.file_offset);
1598     try std.testing.expectEqual(@as(u64, 16), rodata_record.size);
1599     try std.testing.expectEqual(text_record.address, std.mem.readInt(u64, linked.bytes[rodata_offset..][0..8], .little));
1600     try std.testing.expectEqual(@as(u64, 0), std.mem.readInt(u64, linked.bytes[rodata_offset + 8 ..][0..8], .little));
1601 }
1602 
1603 test "ELF linker reports direct 32-bit relocation overflow" {
1604     const allocator = std.testing.allocator;
1605 
1606     const text = [_]u8{0xc3};
1607     const data = @as([4]u8, @splat(0));
1608     const text_index: u16 = 1;
1609     const data_index: u16 = 2;
1610     const too_large_symbol: u32 = 4;
1611     const object = try buildObject(allocator, .{
1612         .sections = &.{
1613             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1614             Section.progbits(".data", &data, std.elf.SHF_WRITE, 4),
1615         },
1616         .symbols = &.{
1617             Symbol.section(text_index),
1618             Symbol.section(data_index),
1619             Symbol.function("_start", text_index, 0, text.len),
1620             Symbol.absoluteObject("too_large", @as(u64, std.math.maxInt(u32)) + 1, 0),
1621         },
1622         .relocations = &.{Relocation.x86_64(data_index, 0, too_large_symbol, .@"32", 0)},
1623     });
1624     defer allocator.free(object);
1625 
1626     try std.testing.expectError(error.RelocationOverflow, linkExecutable(
1627         allocator,
1628         &.{.{ .name = "overflow.o", .bytes = object }},
1629         .{ .incremental_mode = .off },
1630     ));
1631 }
1632 
1633 test "ELF linker rejects relocations that extend past section bytes" {
1634     const allocator = std.testing.allocator;
1635 
1636     const text = [_]u8{ 0xe8, 0, 0 };
1637     const text_index: u16 = 1;
1638     const start_symbol: u32 = 2;
1639     const object = try buildObject(allocator, .{
1640         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1641         .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1642         .relocations = &.{Relocation.x86_64(text_index, 1, start_symbol, .PC32, -4)},
1643     });
1644     defer allocator.free(object);
1645 
1646     try std.testing.expectError(error.InvalidRange, linkExecutable(
1647         allocator,
1648         &.{.{ .name = "truncated-reloc.o", .bytes = object }},
1649         .{ .incremental_mode = .off },
1650     ));
1651 }
1652 
1653 test "ELF linker follows relocation chains across sections during GC" {
1654     const allocator = std.testing.allocator;
1655 
1656     const call_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1657     const leaf_text = [_]u8{0xc3};
1658     const start_index: u16 = 1;
1659     const first_index: u16 = 2;
1660     const second_index: u16 = 3;
1661     const first_symbol: u32 = 5;
1662     const second_symbol: u32 = 6;
1663     const object = try buildObject(allocator, .{
1664         .sections = &.{
1665             Section.progbits(".text.start", &call_text, std.elf.SHF_EXECINSTR, 16),
1666             Section.progbits(".text.first", &call_text, std.elf.SHF_EXECINSTR, 16),
1667             Section.progbits(".text.second", &leaf_text, std.elf.SHF_EXECINSTR, 16),
1668         },
1669         .symbols = &.{
1670             Symbol.section(start_index),
1671             Symbol.section(first_index),
1672             Symbol.section(second_index),
1673             Symbol.function("_start", start_index, 0, call_text.len),
1674             Symbol.function("first", first_index, 0, call_text.len),
1675             Symbol.function("second", second_index, 0, leaf_text.len),
1676         },
1677         .relocations = &.{
1678             Relocation.x86_64(start_index, 1, first_symbol, .PLT32, -4),
1679             Relocation.x86_64(first_index, 1, second_symbol, .PLT32, -4),
1680         },
1681     });
1682     defer allocator.free(object);
1683 
1684     const inputs = [_]model.Input{.{ .name = "chain.o", .bytes = object }};
1685     var linked = try linkExecutable(allocator, &inputs, .{
1686         .incremental_mode = .off,
1687         .gc_sections = true,
1688     });
1689     defer linked.deinit(allocator);
1690 
1691     var manifest_linked = try linkExecutable(allocator, &inputs, .{
1692         .gc_sections = true,
1693     });
1694     defer manifest_linked.deinit(allocator);
1695 
1696     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1697     try std.testing.expectEqual(@as(u64, 33), text_record.size);
1698     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.start", start_index) != null);
1699     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.first", first_index) != null);
1700     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.second", second_index) != null);
1701 
1702     const text_file_offset: usize = @intCast(text_record.file_offset);
1703     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
1704     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
1705 }
1706 
1707 test "ELF linker keeps COMDAT group members live during garbage collection" {
1708     const allocator = std.testing.allocator;
1709 
1710     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1711     const caller_text_index: u16 = 1;
1712     const grouped_symbol: u32 = 3;
1713     const caller_object = try buildObject(allocator, .{
1714         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
1715         .symbols = &.{
1716             Symbol.section(caller_text_index),
1717             Symbol.function("_start", caller_text_index, 0, caller_text.len),
1718             Symbol.undefinedFunction("grouped"),
1719         },
1720         .relocations = &.{Relocation.x86_64(1, 1, grouped_symbol, .PLT32, -4)},
1721     });
1722     defer allocator.free(caller_object);
1723 
1724     const grouped_text = [_]u8{0xc3};
1725     const grouped_data = [_]u8{ 1, 2, 3, 4 };
1726     const grouped_text_index: u16 = 1;
1727     const grouped_data_index: u16 = 2;
1728     const grouped_definition: u32 = 3;
1729     var comdat_payload: [(2 + 1) * 4]u8 = undefined;
1730     const comdat_bytes = groupBytes(&comdat_payload, &.{ grouped_text_index, grouped_data_index });
1731     const group_object = try buildObject(allocator, .{
1732         .sections = &.{
1733             Section.progbits(".text.grouped", &grouped_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
1734             Section.progbits(".rodata.grouped", &grouped_data, std.elf.SHF_GROUP, 1),
1735             Section.group(".group", comdat_bytes, grouped_definition),
1736         },
1737         .symbols = &.{
1738             Symbol.section(grouped_text_index),
1739             Symbol.section(grouped_data_index),
1740             Symbol.function("grouped", grouped_text_index, 0, grouped_text.len),
1741         },
1742     });
1743     defer allocator.free(group_object);
1744 
1745     var linked = try linkExecutable(allocator, &.{
1746         .{ .name = "caller.o", .bytes = caller_object },
1747         .{ .name = "group.o", .bytes = group_object },
1748     }, .{
1749         .gc_sections = true,
1750     });
1751     defer linked.deinit(allocator);
1752 
1753     try std.testing.expect(metadata.manifestContribution(linked.manifest, "group.o", .section, ".text.grouped", grouped_text_index) != null);
1754     try std.testing.expect(metadata.manifestContribution(linked.manifest, "group.o", .section, ".rodata.grouped", grouped_data_index) != null);
1755 }
1756 
1757 test "ELF linker folds identical code sections when ICF is enabled" {
1758     const allocator = std.testing.allocator;
1759 
1760     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1761     const function_text = [_]u8{0xc3};
1762     const start_index: u16 = 1;
1763     const first_index: u16 = 2;
1764     const second_index: u16 = 3;
1765     const first_symbol: u32 = 5;
1766     const second_symbol: u32 = 6;
1767     const object = try buildObject(allocator, .{
1768         .sections = &.{
1769             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1770             Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1771             Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1772         },
1773         .symbols = &.{
1774             Symbol.section(start_index),
1775             Symbol.section(first_index),
1776             Symbol.section(second_index),
1777             Symbol.function("_start", start_index, 0, start_text.len),
1778             Symbol.function("first", first_index, 0, function_text.len),
1779             Symbol.function("second", second_index, 0, function_text.len),
1780         },
1781         .relocations = &.{
1782             Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1783             Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1784         },
1785     });
1786     defer allocator.free(object);
1787 
1788     const inputs = [_]model.Input{.{ .name = "icf.o", .bytes = object }};
1789     var retained = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
1790     defer retained.deinit(allocator);
1791     var retained_manifest = try linkExecutable(allocator, &inputs, .{});
1792     defer retained_manifest.deinit(allocator);
1793     const retained_text = metadata.manifestSection(retained.manifest, ".text") orelse return error.MissingSection;
1794     try std.testing.expectEqual(@as(u64, 33), retained_text.size);
1795     try std.testing.expect(metadata.manifestContribution(retained_manifest.manifest, "icf.o", .section, ".text.first", first_index) != null);
1796     try std.testing.expect(metadata.manifestContribution(retained_manifest.manifest, "icf.o", .section, ".text.second", second_index) != null);
1797 
1798     var folded = try linkExecutable(allocator, &inputs, .{
1799         .incremental_mode = .off,
1800         .icf = .all,
1801     });
1802     defer folded.deinit(allocator);
1803     var folded_manifest = try linkExecutable(allocator, &inputs, .{
1804         .icf = .all,
1805     });
1806     defer folded_manifest.deinit(allocator);
1807     const folded_text = metadata.manifestSection(folded.manifest, ".text") orelse return error.MissingSection;
1808     try std.testing.expectEqual(@as(u64, 17), folded_text.size);
1809     try std.testing.expect(metadata.manifestContribution(folded_manifest.manifest, "icf.o", .section, ".text.first", first_index) != null);
1810     try std.testing.expect(metadata.manifestContribution(folded_manifest.manifest, "icf.o", .section, ".text.second", second_index) == null);
1811     const folded_second = metadata.manifestDiscardedContribution(folded_manifest.manifest, "icf.o", ".text.second", second_index) orelse return error.MissingSection;
1812     try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1813 
1814     const text_file_offset: usize = @intCast(folded_text.file_offset);
1815     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, folded.bytes[text_file_offset + 1 ..][0..4], .little));
1816     try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, folded.bytes[text_file_offset + 6 ..][0..4], .little));
1817 }
1818 
1819 test "ELF ICF ignores no-op relocations" {
1820     const allocator = std.testing.allocator;
1821 
1822     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1823     const function_text = [_]u8{0xc3};
1824     const start_index: u16 = 1;
1825     const first_index: u16 = 2;
1826     const second_index: u16 = 3;
1827     const first_symbol: u32 = 5;
1828     const second_symbol: u32 = 6;
1829     const object = try buildObject(allocator, .{
1830         .sections = &.{
1831             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1832             Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1833             Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1834         },
1835         .symbols = &.{
1836             Symbol.section(start_index),
1837             Symbol.section(first_index),
1838             Symbol.section(second_index),
1839             Symbol.function("_start", start_index, 0, start_text.len),
1840             Symbol.function("first", first_index, 0, function_text.len),
1841             Symbol.function("second", second_index, 0, function_text.len),
1842         },
1843         .relocations = &.{
1844             Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1845             Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1846             Relocation.x86_64(second_index, 0, 0, .NONE, 0),
1847         },
1848     });
1849     defer allocator.free(object);
1850 
1851     const inputs = [_]model.Input{.{ .name = "icf-none.o", .bytes = object }};
1852     var linked = try linkExecutable(allocator, &inputs, .{
1853         .incremental_mode = .off,
1854         .icf = .all,
1855     });
1856     defer linked.deinit(allocator);
1857 
1858     var manifest_linked = try linkExecutable(allocator, &inputs, .{
1859         .icf = .all,
1860     });
1861     defer manifest_linked.deinit(allocator);
1862 
1863     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1864     try std.testing.expectEqual(@as(u64, 17), text_record.size);
1865     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "icf-none.o", .section, ".text.first", first_index) != null);
1866     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "icf-none.o", .section, ".text.second", second_index) == null);
1867     const folded_second = metadata.manifestDiscardedContribution(manifest_linked.manifest, "icf-none.o", ".text.second", second_index) orelse return error.MissingSection;
1868     try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1869 }
1870 
1871 test "ELF linker folds relocated code sections with equivalent targets" {
1872     const allocator = std.testing.allocator;
1873 
1874     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1875     const start_index: u16 = 1;
1876     const first_symbol: u32 = 3;
1877     const second_symbol: u32 = 4;
1878     const caller_object = try buildObject(allocator, .{
1879         .sections = &.{Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16)},
1880         .symbols = &.{
1881             Symbol.section(start_index),
1882             Symbol.function("_start", start_index, 0, start_text.len),
1883             Symbol.undefinedFunction("first"),
1884             Symbol.undefinedFunction("second"),
1885         },
1886         .relocations = &.{
1887             Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1888             Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1889         },
1890     });
1891     defer allocator.free(caller_object);
1892 
1893     const function_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1894 
1895     const first_index: u16 = 1;
1896     const first_target_symbol: u32 = 3;
1897     const first_object = try buildObject(allocator, .{
1898         .sections = &.{Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16)},
1899         .symbols = &.{
1900             Symbol.section(first_index),
1901             Symbol.function("first", first_index, 0, function_text.len),
1902             Symbol.undefinedFunction("target"),
1903         },
1904         .relocations = &.{Relocation.x86_64(1, 1, first_target_symbol, .PLT32, -4)},
1905     });
1906     defer allocator.free(first_object);
1907 
1908     const second_index: u16 = 1;
1909     const second_target_symbol: u32 = 3;
1910     const second_object = try buildObject(allocator, .{
1911         .sections = &.{Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16)},
1912         .symbols = &.{
1913             Symbol.section(second_index),
1914             Symbol.function("second", second_index, 0, function_text.len),
1915             Symbol.undefinedFunction("target"),
1916         },
1917         .relocations = &.{Relocation.x86_64(1, 1, second_target_symbol, .PLT32, -4)},
1918     });
1919     defer allocator.free(second_object);
1920 
1921     const target_text = [_]u8{0xc3};
1922     const target_index: u16 = 1;
1923     const target_object = try buildObject(allocator, .{
1924         .sections = &.{Section.progbits(".text.target", &target_text, std.elf.SHF_EXECINSTR, 16)},
1925         .symbols = &.{ Symbol.section(target_index), Symbol.function("target", target_index, 0, target_text.len) },
1926     });
1927     defer allocator.free(target_object);
1928 
1929     const inputs = [_]model.Input{
1930         .{ .name = "caller.o", .bytes = caller_object },
1931         .{ .name = "first.o", .bytes = first_object },
1932         .{ .name = "second.o", .bytes = second_object },
1933         .{ .name = "target.o", .bytes = target_object },
1934     };
1935 
1936     var linked = try linkExecutable(allocator, &inputs, .{
1937         .incremental_mode = .off,
1938         .icf = .all,
1939     });
1940     defer linked.deinit(allocator);
1941 
1942     var manifest_linked = try linkExecutable(allocator, &inputs, .{
1943         .icf = .all,
1944     });
1945     defer manifest_linked.deinit(allocator);
1946 
1947     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1948     try std.testing.expectEqual(@as(u64, 33), text.size);
1949     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "first.o", .section, ".text.first", first_index) != null);
1950     try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "second.o", .section, ".text.second", second_index) == null);
1951     const folded_second = metadata.manifestDiscardedContribution(manifest_linked.manifest, "second.o", ".text.second", second_index) orelse return error.MissingSection;
1952     try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1953 
1954     const text_file_offset: usize = @intCast(text.file_offset);
1955     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
1956     try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
1957     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
1958 }
1959 
1960 test "ELF linker keeps relocated code sections with different targets distinct in ICF" {
1961     const allocator = std.testing.allocator;
1962 
1963     const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1964     const function_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1965     const target_text = [_]u8{0xc3};
1966     const start_index: u16 = 1;
1967     const first_index: u16 = 2;
1968     const second_index: u16 = 3;
1969     const first_target_index: u16 = 4;
1970     const second_target_index: u16 = 5;
1971     const first_symbol: u32 = 7;
1972     const second_symbol: u32 = 8;
1973     const first_target_symbol: u32 = 9;
1974     const second_target_symbol: u32 = 10;
1975     const object = try buildObject(allocator, .{
1976         .sections = &.{
1977             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1978             Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1979             Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1980             Section.progbits(".text.first_target", &target_text, std.elf.SHF_EXECINSTR, 16),
1981             Section.progbits(".text.second_target", &target_text, std.elf.SHF_EXECINSTR, 16),
1982         },
1983         .symbols = &.{
1984             Symbol.section(start_index),
1985             Symbol.section(first_index),
1986             Symbol.section(second_index),
1987             Symbol.section(first_target_index),
1988             Symbol.section(second_target_index),
1989             Symbol.function("_start", start_index, 0, start_text.len),
1990             Symbol.function("first", first_index, 0, function_text.len),
1991             Symbol.function("second", second_index, 0, function_text.len),
1992             Symbol.function("first_target", first_target_index, 0, target_text.len),
1993             Symbol.function("second_target", second_target_index, 0, target_text.len),
1994         },
1995         .relocations = &.{
1996             Relocation.x86_64(start_index, 1, first_symbol, .PLT32, -4),
1997             Relocation.x86_64(start_index, 6, second_symbol, .PLT32, -4),
1998             Relocation.x86_64(first_index, 1, first_target_symbol, .PLT32, -4),
1999             Relocation.x86_64(second_index, 1, second_target_symbol, .PLT32, -4),
2000         },
2001     });
2002     defer allocator.free(object);
2003 
2004     var linked = try linkExecutable(allocator, &.{.{ .name = "distinct-targets.o", .bytes = object }}, .{
2005         .icf = .all,
2006     });
2007     defer linked.deinit(allocator);
2008 
2009     try std.testing.expect(metadata.manifestContribution(linked.manifest, "distinct-targets.o", .section, ".text.first", first_index) != null);
2010     try std.testing.expect(metadata.manifestContribution(linked.manifest, "distinct-targets.o", .section, ".text.second", second_index) != null);
2011 }
2012 
2013 test "ELF linker applies absolute relocations to read-only data" {
2014     const allocator = std.testing.allocator;
2015     const text = [_]u8{
2016         0x48, 0xb8,
2017         0,    0,
2018         0,    0,
2019         0,    0,
2020         0,    0,
2021         0xc3,
2022     };
2023     const rodata = [_]u8{ 'o', 'k', 0 };
2024     const text_index: u16 = 1;
2025     const rodata_index: u16 = 2;
2026     const message_symbol: u32 = 4;
2027     const object = try buildObject(allocator, .{
2028         .sections = &.{
2029             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2030             Section.progbits(".rodata", &rodata, 0, 1),
2031         },
2032         .symbols = &.{
2033             Symbol.section(text_index),
2034             Symbol.section(rodata_index),
2035             Symbol.function("_start", text_index, 0, text.len),
2036             Symbol.object("message", rodata_index, 0, rodata.len),
2037         },
2038         .relocations = &.{Relocation.x86_64(1, 2, message_symbol, .@"64", 0)},
2039     });
2040     defer allocator.free(object);
2041 
2042     var linked = try linkExecutable(allocator, &.{.{ .name = "abs.o", .bytes = object }}, .{ .incremental_mode = .off });
2043     defer linked.deinit(allocator);
2044 
2045     const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
2046     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2047     const rodata_address = rodata_record.address;
2048     try std.testing.expectEqual(rodata_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2049     const rodata_file_offset: usize = @intCast(rodata_record.file_offset);
2050     try std.testing.expectEqualSlices(u8, &rodata, linked.bytes[rodata_file_offset..][0..rodata.len]);
2051 }
2052 
2053 test "ELF linker merges fixed-size mergeable constants" {
2054     const allocator = std.testing.allocator;
2055 
2056     const text = [_]u8{
2057         0x48, 0xb8,
2058         0,    0,
2059         0,    0,
2060         0,    0,
2061         0,    0,
2062         0x48, 0xbb,
2063         0,    0,
2064         0,    0,
2065         0,    0,
2066         0,    0,
2067         0xc3,
2068     };
2069     const text_index: u16 = 1;
2070     const duplicated_symbol: u32 = 3;
2071     const section_addend_symbol: u32 = 4;
2072     const caller_object = try buildObject(allocator, .{
2073         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2074         .symbols = &.{
2075             Symbol.section(text_index),
2076             Symbol.function("_start", text_index, 0, text.len),
2077             Symbol.undefinedObject("duplicated_constant"),
2078             Symbol.undefinedObject("section_addend_constant"),
2079         },
2080         .relocations = &.{
2081             Relocation.x86_64(1, 2, duplicated_symbol, .@"64", 0),
2082             Relocation.x86_64(1, 12, section_addend_symbol, .@"64", 0),
2083         },
2084     });
2085     defer allocator.free(caller_object);
2086 
2087     const constant = [_]u8{ 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe };
2088     const first_section: u16 = 1;
2089     const first_object = try buildObject(allocator, .{
2090         .sections = &.{Section.mergeable(".rodata.cst8", &constant, 0, 8, 8)},
2091         .symbols = &.{
2092             Symbol.section(first_section),
2093             Symbol.object("first_constant", first_section, 0, constant.len),
2094         },
2095     });
2096     defer allocator.free(first_object);
2097 
2098     const duplicate_section: u16 = 1;
2099     const duplicate_object = try buildObject(allocator, .{
2100         .sections = &.{Section.mergeable(".rodata.cst8", &constant, 0, 8, 8)},
2101         .symbols = &.{
2102             Symbol.section(duplicate_section),
2103             Symbol.object("duplicated_constant", duplicate_section, 0, constant.len),
2104         },
2105     });
2106     defer allocator.free(duplicate_object);
2107 
2108     const pair = constant ++ constant;
2109     const pair_section: u16 = 1;
2110     const pair_section_symbol: u32 = 1;
2111     const probe_text = [_]u8{
2112         0x48, 0xb8,
2113         0,    0,
2114         0,    0,
2115         0,    0,
2116         0,    0,
2117         0xc3,
2118     };
2119     const probe_text_index: u16 = 2;
2120     const section_addend_object = try buildObject(allocator, .{
2121         .sections = &.{
2122             Section.mergeable(".rodata.cst8", &pair, 0, 8, 8),
2123             Section.progbits(".text.probe", &probe_text, std.elf.SHF_EXECINSTR, 16),
2124         },
2125         .symbols = &.{
2126             Symbol.section(pair_section),
2127             Symbol.section(probe_text_index),
2128             Symbol.object("section_addend_constant", pair_section, 8, constant.len),
2129             Symbol.function("section_probe", probe_text_index, 0, probe_text.len),
2130         },
2131         .relocations = &.{Relocation.x86_64(probe_text_index, 2, pair_section_symbol, .@"64", 8)},
2132     });
2133     defer allocator.free(section_addend_object);
2134 
2135     var linked = try linkExecutable(allocator, &.{
2136         .{ .name = "caller.o", .bytes = caller_object },
2137         .{ .name = "first.o", .bytes = first_object },
2138         .{ .name = "duplicate.o", .bytes = duplicate_object },
2139         .{ .name = "section-addend.o", .bytes = section_addend_object },
2140     }, .{ .incremental_mode = .off });
2141     defer linked.deinit(allocator);
2142 
2143     const rodata = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2144     try std.testing.expectEqual(@as(u64, constant.len), rodata.size);
2145     try std.testing.expectEqualSlices(u8, &constant, linked.bytes[@as(usize, @intCast(rodata.file_offset))..][0..constant.len]);
2146 
2147     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2148     const text_file_offset: usize = @intCast(text_record.file_offset);
2149     try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2150     try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[text_file_offset + 12 ..][0..8], .little));
2151 
2152     const probe_file_offset = @as(usize, @intCast(text_record.file_offset)) + alignForward(text.len, 16);
2153     try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[probe_file_offset + 2 ..][0..8], .little));
2154 }
2155 
2156 test "ELF linker preserves external mergeable string starts" {
2157     const allocator = std.testing.allocator;
2158 
2159     const text = [_]u8{
2160         0x48, 0xb8,
2161         0,    0,
2162         0,    0,
2163         0,    0,
2164         0,    0,
2165         0x48, 0xbb,
2166         0,    0,
2167         0,    0,
2168         0,    0,
2169         0,    0,
2170         0xc3,
2171     };
2172     const text_index: u16 = 1;
2173     const suffix_symbol: u32 = 3;
2174     const duplicate_symbol: u32 = 4;
2175     const caller_object = try buildObject(allocator, .{
2176         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2177         .symbols = &.{
2178             Symbol.section(text_index),
2179             Symbol.function("_start", text_index, 0, text.len),
2180             Symbol.undefinedObject("suffix_string"),
2181             Symbol.undefinedObject("duplicate_suffix"),
2182         },
2183         .relocations = &.{
2184             Relocation.x86_64(1, 2, suffix_symbol, .@"64", 0),
2185             Relocation.x86_64(1, 12, duplicate_symbol, .@"64", 0),
2186         },
2187     });
2188     defer allocator.free(caller_object);
2189 
2190     const prefix = "prefix-";
2191     const suffix = "tail";
2192     const long_string = prefix ++ suffix ++ "\x00";
2193     const long_section: u16 = 1;
2194     const long_object = try buildObject(allocator, .{
2195         .sections = &.{Section.strings(".rodata.str1.1", long_string, 1)},
2196         .symbols = &.{
2197             Symbol.section(long_section),
2198             Symbol.object("long_string", long_section, 0, long_string.len),
2199         },
2200     });
2201     defer allocator.free(long_object);
2202 
2203     const suffix_string = suffix ++ "\x00";
2204     const suffix_section: u16 = 1;
2205     const suffix_object = try buildObject(allocator, .{
2206         .sections = &.{Section.strings(".rodata.str1.1", suffix_string, 1)},
2207         .symbols = &.{
2208             Symbol.section(suffix_section),
2209             Symbol.object("suffix_string", suffix_section, 0, suffix_string.len),
2210         },
2211     });
2212     defer allocator.free(suffix_object);
2213 
2214     const duplicate_section: u16 = 1;
2215     const duplicate_object = try buildObject(allocator, .{
2216         .sections = &.{Section.strings(".rodata.str1.1", suffix_string, 1)},
2217         .symbols = &.{
2218             Symbol.section(duplicate_section),
2219             Symbol.object("duplicate_suffix", duplicate_section, 0, suffix_string.len),
2220         },
2221     });
2222     defer allocator.free(duplicate_object);
2223 
2224     const second_prefix = "second-";
2225     const second_long = second_prefix ++ suffix ++ "\x00";
2226     const pair = second_long ++ suffix_string;
2227     const second_suffix_offset = second_long.len;
2228     const pair_section: u16 = 1;
2229     const pair_section_symbol: u32 = 1;
2230     const probe_text = [_]u8{
2231         0x48, 0xb8,
2232         0,    0,
2233         0,    0,
2234         0,    0,
2235         0,    0,
2236         0xc3,
2237     };
2238     const probe_text_index: u16 = 2;
2239     const section_addend_object = try buildObject(allocator, .{
2240         .sections = &.{
2241             Section.strings(".rodata.str1.1", pair, 1),
2242             Section.progbits(".text.probe", &probe_text, std.elf.SHF_EXECINSTR, 16),
2243         },
2244         .symbols = &.{
2245             Symbol.section(pair_section),
2246             Symbol.section(probe_text_index),
2247             Symbol.function("section_probe", probe_text_index, 0, probe_text.len),
2248         },
2249         .relocations = &.{
2250             Relocation.x86_64(probe_text_index, 2, pair_section_symbol, .@"64", second_suffix_offset),
2251         },
2252     });
2253     defer allocator.free(section_addend_object);
2254 
2255     var linked = try linkExecutable(allocator, &.{
2256         .{ .name = "caller.o", .bytes = caller_object },
2257         .{ .name = "long.o", .bytes = long_object },
2258         .{ .name = "suffix.o", .bytes = suffix_object },
2259         .{ .name = "duplicate.o", .bytes = duplicate_object },
2260         .{ .name = "section-addend.o", .bytes = section_addend_object },
2261     }, .{ .incremental_mode = .off });
2262     defer linked.deinit(allocator);
2263 
2264     const rodata = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2265     const expected_rodata = long_string ++ second_long ++ suffix_string;
2266     try std.testing.expectEqual(@as(u64, expected_rodata.len), rodata.size);
2267     try std.testing.expectEqualSlices(u8, expected_rodata, linked.bytes[@as(usize, @intCast(rodata.file_offset))..][0..expected_rodata.len]);
2268 
2269     const expected_suffix_address = rodata.address + long_string.len + second_long.len;
2270     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2271     const text_file_offset: usize = @intCast(text_record.file_offset);
2272     try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2273     try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 12 ..][0..8], .little));
2274 
2275     const probe_file_offset = @as(usize, @intCast(text_record.file_offset)) + alignForward(text.len, 16);
2276     try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[probe_file_offset + 2 ..][0..8], .little));
2277 }
2278 
2279 test "ELF linker relaxes x86_64 GOTPCRELX data address loads" {
2280     const allocator = std.testing.allocator;
2281     const text = [_]u8{
2282         0x48, 0x8b, 0x05,
2283         0,    0,    0,
2284         0,    0xc3,
2285     };
2286     const rodata = [_]u8{ 'o', 'k', 0 };
2287     const text_index: u16 = 1;
2288     const rodata_index: u16 = 2;
2289     const message_symbol: u32 = 4;
2290     const object = try buildObject(allocator, .{
2291         .sections = &.{
2292             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2293             Section.progbits(".rodata", &rodata, 0, 1),
2294         },
2295         .symbols = &.{
2296             Symbol.section(text_index),
2297             Symbol.section(rodata_index),
2298             Symbol.function("_start", text_index, 0, text.len),
2299             Symbol.object("message", rodata_index, 0, rodata.len),
2300         },
2301         .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4)},
2302     });
2303     defer allocator.free(object);
2304 
2305     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx.o", .bytes = object }}, .{ .incremental_mode = .off });
2306     defer linked.deinit(allocator);
2307 
2308     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2309     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2310     const text_file_offset: usize = @intCast(text_record.file_offset);
2311     try std.testing.expectEqual(@as(u8, 0x8d), linked.bytes[text_file_offset + 1]);
2312 
2313     const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little);
2314     const next_instruction = text_record.address + 7;
2315     const loaded_address = @as(i128, next_instruction) + displacement;
2316     try std.testing.expectEqual(@as(i128, rodata_record.address), loaded_address);
2317 }
2318 
2319 test "ELF linker relaxes x86_64 REX_GOTPCRELX address ALU operations" {
2320     const allocator = std.testing.allocator;
2321     const text = [_]u8{
2322         0x48, 0x3b, 0x35,
2323         0,    0,    0,
2324         0,    0x4c, 0x3b,
2325         0x0d, 0,    0,
2326         0,    0,    0x48,
2327         0x2b, 0x0d, 0,
2328         0,    0,    0,
2329         0xc3,
2330     };
2331     const rodata = [_]u8{ 'o', 'k', 0 };
2332     const text_index: u16 = 1;
2333     const rodata_index: u16 = 2;
2334     const message_symbol: u32 = 4;
2335     const object = try buildObject(allocator, .{
2336         .sections = &.{
2337             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2338             Section.progbits(".rodata", &rodata, 0, 1),
2339         },
2340         .symbols = &.{
2341             Symbol.section(text_index),
2342             Symbol.section(rodata_index),
2343             Symbol.function("_start", text_index, 0, text.len),
2344             Symbol.object("message", rodata_index, 0, rodata.len),
2345         },
2346         .relocations = &.{
2347             Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4),
2348             Relocation.x86_64(1, 10, message_symbol, .REX_GOTPCRELX, -4),
2349             Relocation.x86_64(1, 17, message_symbol, .REX_GOTPCRELX, -4),
2350         },
2351     });
2352     defer allocator.free(object);
2353 
2354     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-alu.o", .bytes = object }}, .{ .incremental_mode = .off });
2355     defer linked.deinit(allocator);
2356 
2357     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2358     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2359     const text_file_offset: usize = @intCast(text_record.file_offset);
2360     try std.testing.expectEqual(@as(u8, 0x48), linked.bytes[text_file_offset]);
2361     try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 1]);
2362     try std.testing.expectEqual(@as(u8, 0xfe), linked.bytes[text_file_offset + 2]);
2363     try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little));
2364     try std.testing.expectEqual(@as(u8, 0x49), linked.bytes[text_file_offset + 7]);
2365     try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 8]);
2366     try std.testing.expectEqual(@as(u8, 0xf9), linked.bytes[text_file_offset + 9]);
2367     try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 10 ..][0..4], .little));
2368     try std.testing.expectEqual(@as(u8, 0x48), linked.bytes[text_file_offset + 14]);
2369     try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 15]);
2370     try std.testing.expectEqual(@as(u8, 0xe9), linked.bytes[text_file_offset + 16]);
2371     try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
2372 }
2373 
2374 test "ELF linker relaxes x86_64 GOTPCRELX indirect calls" {
2375     const allocator = std.testing.allocator;
2376     const text = [_]u8{
2377         0xff, 0x15,
2378         0,    0,
2379         0,    0,
2380         0xf4, 0xc3,
2381     };
2382     const text_index: u16 = 1;
2383     const callee_symbol: u32 = 3;
2384     const object = try buildObject(allocator, .{
2385         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2386         .symbols = &.{
2387             Symbol.section(text_index),
2388             Symbol.function("_start", text_index, 0, 7),
2389             Symbol.function("callee", text_index, 7, 1),
2390         },
2391         .relocations = &.{Relocation.x86_64(1, 2, callee_symbol, .GOTPCRELX, -4)},
2392     });
2393     defer allocator.free(object);
2394 
2395     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-call.o", .bytes = object }}, .{ .incremental_mode = .off });
2396     defer linked.deinit(allocator);
2397 
2398     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2399     const text_file_offset: usize = @intCast(text_record.file_offset);
2400     try std.testing.expectEqual(@as(u8, 0x67), linked.bytes[text_file_offset]);
2401     try std.testing.expectEqual(@as(u8, 0xe8), linked.bytes[text_file_offset + 1]);
2402 
2403     const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 2 ..][0..4], .little);
2404     const next_instruction = text_record.address + 6;
2405     const loaded_address = @as(i128, next_instruction) + displacement;
2406     try std.testing.expectEqual(@as(i128, text_record.address + 7), loaded_address);
2407 }
2408 
2409 test "ELF linker relaxes x86_64 GOTPCRELX indirect jumps" {
2410     const allocator = std.testing.allocator;
2411     const text = [_]u8{
2412         0xff, 0x25,
2413         0,    0,
2414         0,    0,
2415         0xf4, 0xc3,
2416     };
2417     const text_index: u16 = 1;
2418     const callee_symbol: u32 = 3;
2419     const object = try buildObject(allocator, .{
2420         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2421         .symbols = &.{
2422             Symbol.section(text_index),
2423             Symbol.function("_start", text_index, 0, 7),
2424             Symbol.function("callee", text_index, 7, 1),
2425         },
2426         .relocations = &.{Relocation.x86_64(1, 2, callee_symbol, .GOTPCRELX, -4)},
2427     });
2428     defer allocator.free(object);
2429 
2430     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-jump.o", .bytes = object }}, .{ .incremental_mode = .off });
2431     defer linked.deinit(allocator);
2432 
2433     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2434     const text_file_offset: usize = @intCast(text_record.file_offset);
2435     try std.testing.expectEqual(@as(u8, 0x67), linked.bytes[text_file_offset]);
2436     try std.testing.expectEqual(@as(u8, 0xe9), linked.bytes[text_file_offset + 1]);
2437 
2438     const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 2 ..][0..4], .little);
2439     const next_instruction = text_record.address + 6;
2440     const loaded_address = @as(i128, next_instruction) + displacement;
2441     try std.testing.expectEqual(@as(i128, text_record.address + 7), loaded_address);
2442 }
2443 
2444 test "ELF linker relaxes legacy x86_64 GOTPCREL data address loads" {
2445     const allocator = std.testing.allocator;
2446     const text = [_]u8{
2447         0x48, 0x8b, 0x05,
2448         0,    0,    0,
2449         0,    0xc3,
2450     };
2451     const rodata = [_]u8{ 'o', 'k', 0 };
2452     const text_index: u16 = 1;
2453     const rodata_index: u16 = 2;
2454     const message_symbol: u32 = 4;
2455     const object = try buildObject(allocator, .{
2456         .sections = &.{
2457             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2458             Section.progbits(".rodata", &rodata, 0, 1),
2459         },
2460         .symbols = &.{
2461             Symbol.section(text_index),
2462             Symbol.section(rodata_index),
2463             Symbol.function("_start", text_index, 0, text.len),
2464             Symbol.object("message", rodata_index, 0, rodata.len),
2465         },
2466         .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .GOTPCREL, -4)},
2467     });
2468     defer allocator.free(object);
2469 
2470     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel.o", .bytes = object }}, .{ .incremental_mode = .off });
2471     defer linked.deinit(allocator);
2472 
2473     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2474     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2475     const text_file_offset: usize = @intCast(text_record.file_offset);
2476     try std.testing.expectEqual(@as(u8, 0x8d), linked.bytes[text_file_offset + 1]);
2477 
2478     const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little);
2479     const next_instruction = text_record.address + 7;
2480     const loaded_address = @as(i128, next_instruction) + displacement;
2481     try std.testing.expectEqual(@as(i128, rodata_record.address), loaded_address);
2482 }
2483 
2484 test "ELF linker emits x86_64 GOTPCREL entries for memory operands" {
2485     const allocator = std.testing.allocator;
2486     const text = [_]u8{
2487         0xf3, 0x0f, 0x7e, 0x0d,
2488         0,    0,    0,    0,
2489         0xc3,
2490     };
2491     const rodata = [_]u8{ 'o', 'k', 0 };
2492     const text_index: u16 = 1;
2493     const rodata_index: u16 = 2;
2494     const message_symbol: u32 = 4;
2495     const object = try buildObject(allocator, .{
2496         .sections = &.{
2497             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2498             Section.progbits(".rodata", &rodata, 0, 1),
2499         },
2500         .symbols = &.{
2501             Symbol.section(text_index),
2502             Symbol.section(rodata_index),
2503             Symbol.function("_start", text_index, 0, text.len),
2504             Symbol.object("message", rodata_index, 0, rodata.len),
2505         },
2506         .relocations = &.{Relocation.x86_64(1, 4, message_symbol, .GOTPCREL, -4)},
2507     });
2508     defer allocator.free(object);
2509 
2510     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel-memory.o", .bytes = object }}, .{ .incremental_mode = .off });
2511     defer linked.deinit(allocator);
2512 
2513     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2514     const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2515     const got_record = metadata.manifestSection(linked.manifest, ".got") orelse return error.MissingSection;
2516     const text_file_offset: usize = @intCast(text_record.file_offset);
2517     try std.testing.expectEqual(@as(u64, 8), got_record.size);
2518     try std.testing.expectEqual(rodata_record.address, readU64(linked.bytes, @intCast(got_record.file_offset)));
2519     try std.testing.expectEqualSlices(u8, text[0..4], linked.bytes[text_file_offset..][0..4]);
2520 
2521     const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 4 ..][0..4], .little);
2522     const next_instruction = text_record.address + 8;
2523     const got_address = @as(i128, next_instruction) + displacement;
2524     try std.testing.expectEqual(@as(i128, got_record.address), got_address);
2525 }
2526 
2527 test "ELF linker relaxes x86_64 GOTPCREL weak undefined null checks" {
2528     const allocator = std.testing.allocator;
2529     const text = [_]u8{
2530         0x48, 0x83, 0x3d,
2531         0,    0,    0,
2532         0,    0,    0x75,
2533         0x01, 0xcc, 0xc3,
2534     };
2535     const text_index: u16 = 1;
2536     const weak_symbol: u32 = 3;
2537     const object = try buildObject(allocator, .{
2538         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2539         .symbols = &.{
2540             Symbol.section(text_index),
2541             Symbol.function("_start", text_index, 0, text.len),
2542             Symbol.weakUndefinedFunction("weak_tls_init"),
2543         },
2544         .relocations = &.{Relocation.x86_64(text_index, 3, weak_symbol, .GOTPCREL, -5)},
2545     });
2546     defer allocator.free(object);
2547 
2548     var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel-weak-null.o", .bytes = object }}, .{ .incremental_mode = .off });
2549     defer linked.deinit(allocator);
2550 
2551     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2552     const text_file_offset: usize = @intCast(text_record.file_offset);
2553     const expected = [_]u8{ 0x48, 0x39, 0xe4, 0x0f, 0x1f, 0x44, 0x00, 0x00 };
2554     try std.testing.expectEqualSlices(u8, &expected, linked.bytes[text_file_offset..][0..expected.len]);
2555     try std.testing.expectEqual(@as(u8, 0x75), linked.bytes[text_file_offset + 8]);
2556 }
2557 
2558 test "ELF linker rejects malformed GOTPCRELX relaxation instructions" {
2559     const allocator = std.testing.allocator;
2560     const text = [_]u8{
2561         0x48, 0x8b, 0x45,
2562         0,    0,    0,
2563         0,    0xc3,
2564     };
2565     const rodata = [_]u8{ 'o', 'k', 0 };
2566     const text_index: u16 = 1;
2567     const rodata_index: u16 = 2;
2568     const message_symbol: u32 = 4;
2569     const object = try buildObject(allocator, .{
2570         .sections = &.{
2571             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2572             Section.progbits(".rodata", &rodata, 0, 1),
2573         },
2574         .symbols = &.{
2575             Symbol.section(text_index),
2576             Symbol.section(rodata_index),
2577             Symbol.function("_start", text_index, 0, text.len),
2578             Symbol.object("message", rodata_index, 0, rodata.len),
2579         },
2580         .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4)},
2581     });
2582     defer allocator.free(object);
2583 
2584     var diagnostics: model.Diagnostics = .{};
2585     try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
2586         allocator,
2587         &.{.{ .name = "bad-gotpcrelx.o", .bytes = object }},
2588         .{
2589             .incremental_mode = .off,
2590             .diagnostics = &diagnostics,
2591         },
2592     ));
2593 
2594     const unsupported = diagnostics.first_unsupported_relocation orelse return error.ExpectedUnsupportedRelocationDiagnostic;
2595     try std.testing.expectEqual(@backingInt(std.elf.R_X86_64.REX_GOTPCRELX), unsupported.relocation_type);
2596 }
2597 
2598 test "ELF linker places x86_64 unwind sections in read-only output" {
2599     const allocator = std.testing.allocator;
2600     const text = [_]u8{0xc3};
2601     const unwind = [_]u8{ 0x14, 0, 0, 0, 0, 0, 0, 0 };
2602     const text_index: u16 = 1;
2603     const unwind_index: u16 = 2;
2604     const object = try buildObject(allocator, .{
2605         .sections = &.{
2606             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2607             .{
2608                 .name = ".eh_frame",
2609                 .section_type = std.elf.SHT_X86_64_UNWIND,
2610                 .flags = std.elf.SHF_ALLOC,
2611                 .alignment = 8,
2612                 .bytes = &unwind,
2613             },
2614         },
2615         .symbols = &.{
2616             Symbol.section(text_index),
2617             Symbol.section(unwind_index),
2618             Symbol.function("_start", text_index, 0, text.len),
2619         },
2620     });
2621     defer allocator.free(object);
2622 
2623     var linked = try linkExecutable(allocator, &.{.{ .name = "unwind.o", .bytes = object }}, .{ .incremental_mode = .off });
2624     defer linked.deinit(allocator);
2625 
2626     const eh_frame = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2627     const payload_offset: usize = @intCast(eh_frame.file_offset);
2628     try std.testing.expectEqualSlices(u8, &unwind, linked.bytes[payload_offset..][0..unwind.len]);
2629 }
2630 
2631 test "ELF linker prunes eh_frame FDEs for discarded COMDAT sections" {
2632     const allocator = std.testing.allocator;
2633 
2634     const retained_text = [_]u8{0xc3};
2635     const retained_text_index: u16 = 1;
2636     const retained_symbol: u32 = 2;
2637     var retained_group_payload: [(1 + 1) * 4]u8 = undefined;
2638     const retained_group_bytes = groupBytes(&retained_group_payload, &.{retained_text_index});
2639     const retained_object = try buildObject(allocator, .{
2640         .sections = &.{
2641             Section.progbits(".text.inline", &retained_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
2642             Section.group(".group", retained_group_bytes, retained_symbol),
2643         },
2644         .symbols = &.{
2645             Symbol.section(retained_text_index),
2646             Symbol.function("inline_fn", retained_text_index, 0, retained_text.len),
2647         },
2648     });
2649     defer allocator.free(retained_object);
2650 
2651     const start_text = [_]u8{0xc3};
2652     const duplicate_text = [_]u8{0xcc};
2653     var unwind = @as([0x40]u8, @splat(0));
2654     std.mem.writeInt(u32, unwind[0x00..][0..4], 0x14, .little);
2655     std.mem.writeInt(u32, unwind[0x04..][0..4], 0, .little);
2656     std.mem.writeInt(u32, unwind[0x18..][0..4], 0x10, .little);
2657     std.mem.writeInt(u32, unwind[0x1c..][0..4], 0x1c, .little);
2658     std.mem.writeInt(u32, unwind[0x24..][0..4], 1, .little);
2659     std.mem.writeInt(u32, unwind[0x2c..][0..4], 0x10, .little);
2660     std.mem.writeInt(u32, unwind[0x30..][0..4], 0x30, .little);
2661     std.mem.writeInt(u32, unwind[0x38..][0..4], 1, .little);
2662 
2663     const start_text_index: u16 = 1;
2664     const duplicate_text_index: u16 = 2;
2665     const eh_frame_index: u16 = 3;
2666     const duplicate_section_symbol: u32 = 2;
2667     const start_symbol: u32 = 4;
2668     const duplicate_symbol: u32 = 5;
2669     var frame_group_payload: [(1 + 1) * 4]u8 = undefined;
2670     const frame_group_bytes = groupBytes(&frame_group_payload, &.{duplicate_text_index});
2671     const frame_object = try buildObject(allocator, .{
2672         .sections = &.{
2673             Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
2674             Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
2675             .{
2676                 .name = ".eh_frame",
2677                 .section_type = std.elf.SHT_X86_64_UNWIND,
2678                 .flags = std.elf.SHF_ALLOC,
2679                 .alignment = 8,
2680                 .bytes = &unwind,
2681             },
2682             Section.group(".group", frame_group_bytes, duplicate_symbol),
2683         },
2684         .symbols = &.{
2685             Symbol.section(start_text_index),
2686             Symbol.section(duplicate_text_index),
2687             Symbol.section(eh_frame_index),
2688             Symbol.function("_start", start_text_index, 0, start_text.len),
2689             Symbol.function("inline_fn", duplicate_text_index, 0, duplicate_text.len),
2690         },
2691         .relocations = &.{
2692             Relocation.x86_64(eh_frame_index, 0x20, duplicate_section_symbol, .PC32, 0),
2693             Relocation.x86_64(eh_frame_index, 0x34, start_symbol, .PC32, 0),
2694         },
2695     });
2696     defer allocator.free(frame_object);
2697 
2698     var linked = try linkExecutable(allocator, &.{
2699         .{ .name = "retained.o", .bytes = retained_object },
2700         .{ .name = "frame.o", .bytes = frame_object },
2701     }, .{ .incremental_mode = .off });
2702     defer linked.deinit(allocator);
2703 
2704     const eh_frame_record = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2705     try std.testing.expectEqual(@as(u64, 0x2c), eh_frame_record.size);
2706     const payload_offset: usize = @intCast(eh_frame_record.file_offset);
2707     try std.testing.expectEqual(@as(u32, 0x14), std.mem.readInt(u32, linked.bytes[payload_offset..][0..4], .little));
2708     try std.testing.expectEqual(@as(u32, 0x10), std.mem.readInt(u32, linked.bytes[payload_offset + 0x18 ..][0..4], .little));
2709     try std.testing.expectEqual(@as(u32, 0x1c), std.mem.readInt(u32, linked.bytes[payload_offset + 0x1c ..][0..4], .little));
2710     try std.testing.expect(std.mem.readInt(i32, linked.bytes[payload_offset + 0x20 ..][0..4], .little) != 0);
2711 }
2712 
2713 test "ELF linker emits eh_frame_hdr for retained FDEs" {
2714     const allocator = std.testing.allocator;
2715 
2716     const text = [_]u8{0xc3};
2717     var unwind = @as([0x2c]u8, @splat(0));
2718     std.mem.writeInt(u32, unwind[0x00..][0..4], 0x14, .little);
2719     std.mem.writeInt(u32, unwind[0x04..][0..4], 0, .little);
2720     std.mem.writeInt(u32, unwind[0x18..][0..4], 0x10, .little);
2721     std.mem.writeInt(u32, unwind[0x1c..][0..4], 0x1c, .little);
2722     std.mem.writeInt(u32, unwind[0x24..][0..4], 1, .little);
2723 
2724     const text_index: u16 = 1;
2725     const eh_frame_index: u16 = 2;
2726     const start_symbol: u32 = 3;
2727     const object = try buildObject(allocator, .{
2728         .sections = &.{
2729             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2730             .{
2731                 .name = ".eh_frame",
2732                 .section_type = std.elf.SHT_X86_64_UNWIND,
2733                 .flags = std.elf.SHF_ALLOC,
2734                 .alignment = 8,
2735                 .bytes = &unwind,
2736             },
2737         },
2738         .symbols = &.{
2739             Symbol.section(text_index),
2740             Symbol.section(eh_frame_index),
2741             Symbol.function("_start", text_index, 0, text.len),
2742         },
2743         .relocations = &.{Relocation.x86_64(eh_frame_index, 0x20, start_symbol, .PC32, 0)},
2744     });
2745     defer allocator.free(object);
2746 
2747     var linked = try linkExecutable(allocator, &.{.{ .name = "eh-frame-hdr.o", .bytes = object }}, .{ .incremental_mode = .off, .eh_frame_header = true });
2748     defer linked.deinit(allocator);
2749 
2750     const header = try metadata.linkedSectionByName(linked.bytes, ".eh_frame_hdr");
2751     const rodata = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2752     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2753     const segment = try metadata.programHeaderByType(linked.bytes, std.elf.PT_GNU_EH_FRAME);
2754 
2755     try std.testing.expectEqual(std.elf.SHT_PROGBITS, header.section_type);
2756     try std.testing.expectEqual(@as(u64, std.elf.SHF_ALLOC), header.flags);
2757     try std.testing.expectEqual(@as(u64, eh_frame_hdr_fixed_size + eh_frame_hdr_entry_size), header.size);
2758     try std.testing.expectEqual(header.address, segment.virtual_address);
2759     try std.testing.expectEqual(header.offset, segment.file_offset);
2760     try std.testing.expectEqual(header.size, segment.file_size);
2761 
2762     const offset: usize = @intCast(header.offset);
2763     try std.testing.expectEqual(@as(u8, 1), linked.bytes[offset + 0]);
2764     try std.testing.expectEqual(@as(u8, dwarf_eh_pe_pcrel | dwarf_eh_pe_sdata4), linked.bytes[offset + 1]);
2765     try std.testing.expectEqual(@as(u8, dwarf_eh_pe_udata4), linked.bytes[offset + 2]);
2766     try std.testing.expectEqual(@as(u8, dwarf_eh_pe_datarel | dwarf_eh_pe_sdata4), linked.bytes[offset + 3]);
2767     try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(rodata.address)) - @as(i128, @intCast(header.address + 4)))), std.mem.readInt(i32, linked.bytes[offset + 4 ..][0..4], .little));
2768     try std.testing.expectEqual(@as(u32, 1), std.mem.readInt(u32, linked.bytes[offset + 8 ..][0..4], .little));
2769     try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(text_record.address)) - @as(i128, @intCast(header.address)))), std.mem.readInt(i32, linked.bytes[offset + 12 ..][0..4], .little));
2770     try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(rodata.address + 0x18)) - @as(i128, @intCast(header.address)))), std.mem.readInt(i32, linked.bytes[offset + 16 ..][0..4], .little));
2771 }
2772 
2773 test "ELF linker preserves non-allocated DWARF sections in executable output" {
2774     const allocator = std.testing.allocator;
2775 
2776     const text = [_]u8{0xc3};
2777     const debug = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
2778     const text_index: u16 = 1;
2779     const debug_index: u16 = 2;
2780     const object = try buildObject(allocator, .{
2781         .sections = &.{
2782             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2783             Section.nonAlloc(".debug_info", &debug, std.elf.SHT_PROGBITS, 1),
2784         },
2785         .symbols = &.{
2786             Symbol.section(text_index),
2787             Symbol.section(debug_index),
2788             Symbol.function("_start", text_index, 0, text.len),
2789         },
2790     });
2791     defer allocator.free(object);
2792 
2793     var linked = try linkExecutable(allocator, &.{.{ .name = "debug.o", .bytes = object }}, .{});
2794     defer linked.deinit(allocator);
2795 
2796     try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".text", text_index) != null);
2797     const debug_contribution = metadata.manifestContribution(linked.manifest, "debug.o", .section, ".debug_info", debug_index) orelse return error.MissingSection;
2798     try std.testing.expectEqual(@as(u64, debug.len), debug_contribution.file_size);
2799     try std.testing.expectEqual(@as(usize, 2), linked.manifest.sections.len);
2800 
2801     const debug_plan = linked.manifest.planContributionReplacement(&.{
2802         .{ .input_name = "debug.o", .input_index = 0, .kind = .section, .name = ".debug_info", .ordinal = debug_index, .size = debug.len, .alignment = 1 },
2803     });
2804     try std.testing.expectEqual(incremental.PatchDecision.in_place, debug_plan.decision);
2805 
2806     const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2807     try std.testing.expectEqual(@as(u64, 0), debug_section.address);
2808     try std.testing.expect(debug_section.size >= debug.len);
2809     try std.testing.expectEqualSlices(u8, &debug, linked.bytes[@intCast(debug_section.offset)..][0..debug.len]);
2810 }
2811 
2812 test "ELF linker strips non-allocated DWARF sections when requested" {
2813     const allocator = std.testing.allocator;
2814 
2815     const text = [_]u8{0xc3};
2816     const debug = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
2817     const text_index: u16 = 1;
2818     const debug_index: u16 = 2;
2819     const object = try buildObject(allocator, .{
2820         .sections = &.{
2821             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2822             Section.nonAlloc(".debug_info", &debug, std.elf.SHT_PROGBITS, 1),
2823         },
2824         .symbols = &.{
2825             Symbol.section(text_index),
2826             Symbol.section(debug_index),
2827             Symbol.function("_start", text_index, 0, text.len),
2828         },
2829     });
2830     defer allocator.free(object);
2831 
2832     var linked = try linkExecutable(allocator, &.{.{ .name = "debug.o", .bytes = object }}, .{ .strip_debug = true });
2833     defer linked.deinit(allocator);
2834 
2835     try std.testing.expect(linked.manifest.strip_debug);
2836     try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".text", text_index) != null);
2837     try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".debug_info", debug_index) == null);
2838     try std.testing.expectEqual(@as(usize, 1), linked.manifest.sections.len);
2839     try std.testing.expectError(error.MissingSection, metadata.linkedSectionByName(linked.bytes, ".debug_info"));
2840 }
2841 
2842 test "ELF linker applies relocations in non-allocated DWARF sections" {
2843     const allocator = std.testing.allocator;
2844 
2845     const text = [_]u8{0xc3};
2846     const debug_info = @as([16]u8, @splat(0));
2847     const debug_abbrev = [_]u8{ 1, 2, 3, 4 };
2848     const text_index: u16 = 1;
2849     const debug_info_index: u16 = 2;
2850     const debug_abbrev_index: u16 = 3;
2851     const debug_abbrev_symbol: u32 = 3;
2852     const start_symbol: u32 = 4;
2853     const object = try buildObject(allocator, .{
2854         .sections = &.{
2855             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2856             Section.nonAlloc(".debug_info", &debug_info, std.elf.SHT_PROGBITS, 1),
2857             Section.nonAlloc(".debug_abbrev", &debug_abbrev, std.elf.SHT_PROGBITS, 1),
2858         },
2859         .symbols = &.{
2860             Symbol.section(text_index),
2861             Symbol.section(debug_info_index),
2862             Symbol.section(debug_abbrev_index),
2863             Symbol.function("_start", text_index, 0, text.len),
2864         },
2865         .relocations = &.{
2866             Relocation.x86_64(debug_info_index, 0, start_symbol, .@"64", 0),
2867             Relocation.x86_64(debug_info_index, 8, debug_abbrev_symbol, .@"64", 2),
2868         },
2869     });
2870     defer allocator.free(object);
2871 
2872     var linked = try linkExecutable(allocator, &.{.{ .name = "debug-reloc.o", .bytes = object }}, .{ .incremental_mode = .off });
2873     defer linked.deinit(allocator);
2874 
2875     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2876     const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2877     const debug_offset: usize = @intCast(debug_section.offset);
2878     try std.testing.expectEqual(text_record.address, readU64(linked.bytes, debug_offset));
2879     try std.testing.expectEqual(@as(u64, 2), readU64(linked.bytes, debug_offset + 8));
2880 }
2881 
2882 test "ELF linker leaves DWARF relocations to GC-discarded sections unresolved" {
2883     const allocator = std.testing.allocator;
2884 
2885     const live_text = [_]u8{0xc3};
2886     const dead_text = [_]u8{0xc3};
2887     const debug_info = @as([8]u8, @splat(0));
2888     const live_index: u16 = 1;
2889     const dead_index: u16 = 2;
2890     const debug_index: u16 = 3;
2891     const dead_symbol: u32 = 5;
2892     const object = try buildObject(allocator, .{
2893         .sections = &.{
2894             Section.progbits(".text.live", &live_text, std.elf.SHF_EXECINSTR, 16),
2895             Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
2896             Section.nonAlloc(".debug_info", &debug_info, std.elf.SHT_PROGBITS, 1),
2897         },
2898         .symbols = &.{
2899             Symbol.section(live_index),
2900             Symbol.section(dead_index),
2901             Symbol.section(debug_index),
2902             Symbol.function("_start", live_index, 0, live_text.len),
2903             Symbol.function("dead", dead_index, 0, dead_text.len),
2904         },
2905         .relocations = &.{Relocation.x86_64(debug_index, 0, dead_symbol, .@"64", 0)},
2906     });
2907     defer allocator.free(object);
2908 
2909     var linked = try linkExecutable(allocator, &.{.{ .name = "debug-gc.o", .bytes = object }}, .{ .gc_sections = true, .incremental_mode = .off });
2910     defer linked.deinit(allocator);
2911 
2912     const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2913     try std.testing.expectEqual(@as(u64, 0), readU64(linked.bytes, @intCast(debug_section.offset)));
2914 }
2915 
2916 test "ELF linker keeps SHT_NOBITS out of file payload" {
2917     const allocator = std.testing.allocator;
2918     const text = [_]u8{
2919         0x48, 0xb8,
2920         0,    0,
2921         0,    0,
2922         0,    0,
2923         0,    0,
2924         0xc3,
2925     };
2926     const text_index: u16 = 1;
2927     const bss_index: u16 = 2;
2928     const scratch_symbol: u32 = 4;
2929     const object = try buildObject(allocator, .{
2930         .sections = &.{
2931             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2932             Section.nobits(".bss", 32, std.elf.SHF_WRITE, 16),
2933         },
2934         .symbols = &.{
2935             Symbol.section(text_index),
2936             Symbol.section(bss_index),
2937             Symbol.function("_start", text_index, 0, text.len),
2938             Symbol.object("scratch", bss_index, 0, 32),
2939         },
2940         .relocations = &.{Relocation.x86_64(1, 2, scratch_symbol, .@"64", 0)},
2941     });
2942     defer allocator.free(object);
2943 
2944     var linked = try linkExecutable(allocator, &.{.{ .name = "bss.o", .bytes = object }}, .{ .incremental_mode = .off });
2945     defer linked.deinit(allocator);
2946 
2947     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2948     const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
2949     try std.testing.expectEqual(@as(u64, 32), bss_record.size);
2950     try std.testing.expectEqual(@as(u64, 32), bss_record.reserved_size);
2951     try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
2952     try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
2953 
2954     const bss_header = try metadata.linkedSectionByName(linked.bytes, ".bss");
2955     try std.testing.expectEqual(std.elf.SHT_NOBITS, bss_header.section_type);
2956     try std.testing.expectEqual(bss_record.file_offset, bss_header.offset);
2957     try std.testing.expectEqual(@as(u64, 32), bss_header.size);
2958 
2959     const bss_load = try metadata.loadProgramHeaderByAddress(linked.bytes, bss_record.address);
2960     try std.testing.expectEqual(std.elf.PF_R | std.elf.PF_W, bss_load.flags);
2961     try std.testing.expectEqual(bss_record.file_offset, bss_load.file_offset);
2962     try std.testing.expectEqual(bss_record.address, bss_load.virtual_address);
2963     try std.testing.expectEqual(@as(u64, 0), bss_load.file_size);
2964     try std.testing.expectEqual(@as(u64, 32), bss_load.memory_size);
2965 }
2966 
2967 test "ELF linker resolves symbols on empty allocated sections" {
2968     const allocator = std.testing.allocator;
2969 
2970     const text = [_]u8{
2971         0x48, 0xb8,
2972         0,    0,
2973         0,    0,
2974         0,    0,
2975         0,    0,
2976         0xc3,
2977     };
2978     const caller_text_index: u16 = 1;
2979     const marker_ref: u32 = 3;
2980     const caller_object = try buildObject(allocator, .{
2981         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2982         .symbols = &.{
2983             Symbol.section(caller_text_index),
2984             Symbol.function("_start", caller_text_index, 0, text.len),
2985             Symbol.undefinedObject("__TMC_END__"),
2986         },
2987         .relocations = &.{Relocation.x86_64(1, 2, marker_ref, .@"64", 0)},
2988     });
2989     defer allocator.free(caller_object);
2990 
2991     const data = [_]u8{ 1, 2, 3 };
2992     const marker_index: u16 = 2;
2993     const marker_object = try buildObject(allocator, .{
2994         .sections = &.{
2995             Section.progbits(".data.payload", &data, std.elf.SHF_WRITE, 1),
2996             Section.progbits(".tm_clone_table", &.{}, std.elf.SHF_WRITE, 8),
2997         },
2998         .symbols = &.{
2999             Symbol.section(1),
3000             Symbol.section(marker_index),
3001             Symbol.object("__TMC_END__", marker_index, 0, 0),
3002         },
3003     });
3004     defer allocator.free(marker_object);
3005 
3006     var linked = try linkExecutable(
3007         allocator,
3008         &.{
3009             .{ .name = "caller.o", .bytes = caller_object },
3010             .{ .name = "marker.o", .bytes = marker_object },
3011         },
3012         .{},
3013     );
3014     defer linked.deinit(allocator);
3015 
3016     const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
3017     const marker_contribution = metadata.manifestContribution(linked.manifest, "marker.o", .section, ".tm_clone_table", marker_index) orelse return error.MissingSection;
3018     const loaded = readU64(linked.bytes, @intCast(caller_contribution.file_offset + 2));
3019     try std.testing.expectEqual(@as(u64, 0), marker_contribution.size);
3020     try std.testing.expectEqual(@as(u64, 8), marker_contribution.alignment);
3021     try std.testing.expectEqual(@as(u64, 0), marker_contribution.address % 8);
3022     try std.testing.expectEqual(marker_contribution.address, loaded);
3023 }
3024 
3025 test "ELF linker emits static TLS sections and local-exec relocations" {
3026     const allocator = std.testing.allocator;
3027     const text = [_]u8{
3028         0x64, 0x8b, 0x04, 0x25,
3029         0,    0,    0,    0,
3030         0x64, 0x8b, 0x14, 0x25,
3031         0,    0,    0,    0,
3032         0xc3,
3033     };
3034     const tdata = [_]u8{ 7, 0, 0, 0 };
3035     const text_index: u16 = 1;
3036     const tdata_index: u16 = 2;
3037     const tbss_index: u16 = 3;
3038     const x_symbol: u32 = 5;
3039     const y_symbol: u32 = 6;
3040     const object = try buildObject(allocator, .{
3041         .sections = &.{
3042             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3043             Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3044             Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3045         },
3046         .symbols = &.{
3047             Symbol.section(text_index),
3048             Symbol.section(tdata_index),
3049             Symbol.section(tbss_index),
3050             Symbol.function("_start", text_index, 0, text.len),
3051             Symbol.tlsObject("x", tdata_index, 0, 4),
3052             Symbol.tlsObject("y", tbss_index, 0, 4),
3053         },
3054         .relocations = &.{
3055             Relocation.x86_64(text_index, 4, x_symbol, .TPOFF32, 0),
3056             Relocation.x86_64(text_index, 12, y_symbol, .TPOFF32, 0),
3057         },
3058     });
3059     defer allocator.free(object);
3060 
3061     var linked = try linkExecutable(allocator, &.{.{ .name = "tls.o", .bytes = object }}, .{ .incremental_mode = .off });
3062     defer linked.deinit(allocator);
3063 
3064     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3065     const text_offset: usize = @intCast(text_record.file_offset);
3066     try std.testing.expectEqual(@as(i32, -8), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 4))));
3067     try std.testing.expectEqual(@as(i32, -4), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 12))));
3068 
3069     const tdata_header = try metadata.linkedSectionByName(linked.bytes, ".tdata");
3070     const tbss_header = try metadata.linkedSectionByName(linked.bytes, ".tbss");
3071     try std.testing.expect((tdata_header.flags & std.elf.SHF_TLS) != 0);
3072     try std.testing.expect((tbss_header.flags & std.elf.SHF_TLS) != 0);
3073 
3074     var saw_tls_header = false;
3075     const phnum = readU16(linked.bytes, 56);
3076     for (0..phnum) |index| {
3077         const offset = ehdr_size + index * phdr_size;
3078         if (readU32(linked.bytes, offset) != std.elf.PT_TLS) continue;
3079         saw_tls_header = true;
3080         try std.testing.expectEqual(@as(u64, 4), readU64(linked.bytes, offset + 32));
3081         try std.testing.expectEqual(@as(u64, 8), readU64(linked.bytes, offset + 40));
3082     }
3083     try std.testing.expect(saw_tls_header);
3084 }
3085 
3086 test "ELF linker relaxes x86-64 initial-exec TLS for static executable" {
3087     const allocator = std.testing.allocator;
3088     const text = [_]u8{
3089         0x48, 0x8b, 0x05,
3090         0,    0,    0,
3091         0,    0x4c, 0x8b,
3092         0x05, 0,    0,
3093         0,    0,    0xc3,
3094     };
3095     const tdata = [_]u8{ 7, 0, 0, 0 };
3096     const text_index: u16 = 1;
3097     const tdata_index: u16 = 2;
3098     const x_symbol: u32 = 5;
3099     const object = try buildObject(allocator, .{
3100         .sections = &.{
3101             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3102             Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3103             Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3104         },
3105         .symbols = &.{
3106             Symbol.section(text_index),
3107             Symbol.section(tdata_index),
3108             Symbol.section(3),
3109             Symbol.function("_start", text_index, 0, text.len),
3110             Symbol.tlsObject("x", tdata_index, 0, 4),
3111         },
3112         .relocations = &.{
3113             Relocation.x86_64(text_index, 3, x_symbol, .GOTTPOFF, -4),
3114             Relocation.x86_64(text_index, 10, x_symbol, .GOTTPOFF, -4),
3115         },
3116     });
3117     defer allocator.free(object);
3118 
3119     var linked = try linkExecutable(allocator, &.{.{ .name = "tls-initial-exec.o", .bytes = object }}, .{ .incremental_mode = .off });
3120     defer linked.deinit(allocator);
3121 
3122     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3123     const text_offset: usize = @intCast(text_record.file_offset);
3124     const rax_expected = [_]u8{ 0x48, 0xc7, 0xc0, 0xf8, 0xff, 0xff, 0xff };
3125     const r8_expected = [_]u8{ 0x49, 0xc7, 0xc0, 0xf8, 0xff, 0xff, 0xff };
3126     try std.testing.expectEqualSlices(u8, &rax_expected, linked.bytes[text_offset..][0..rax_expected.len]);
3127     try std.testing.expectEqualSlices(u8, &r8_expected, linked.bytes[text_offset + 7 ..][0..r8_expected.len]);
3128     try std.testing.expect(metadata.manifestSection(linked.manifest, ".got") == null);
3129 }
3130 
3131 test "ELF linker relaxes x86-64 TLS dynamic models for static executable" {
3132     const allocator = std.testing.allocator;
3133     const text = [_]u8{
3134         0x66, 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00,
3135         0x66, 0x66, 0x48, 0xe8, 0x00, 0x00, 0x00, 0x00,
3136         0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00, 0xe8,
3137         0x00, 0x00, 0x00, 0x00, 0x8b, 0x80, 0x00, 0x00,
3138         0x00, 0x00, 0xc3,
3139     };
3140     const tdata = [_]u8{ 7, 0, 0, 0 };
3141     const text_index: u16 = 1;
3142     const tdata_index: u16 = 2;
3143     const x_symbol: u32 = 4;
3144     const tls_get_addr: u32 = 5;
3145     const object = try buildObject(allocator, .{
3146         .sections = &.{
3147             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3148             Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3149         },
3150         .symbols = &.{
3151             Symbol.section(text_index),
3152             Symbol.section(tdata_index),
3153             Symbol.function("_start", text_index, 0, text.len),
3154             Symbol.tlsObject("x", tdata_index, 0, 4),
3155             Symbol.undefinedFunction("__tls_get_addr"),
3156         },
3157         .relocations = &.{
3158             Relocation.x86_64(text_index, 4, x_symbol, .TLSGD, -4),
3159             Relocation.x86_64(text_index, 12, tls_get_addr, .PLT32, -4),
3160             Relocation.x86_64(text_index, 19, x_symbol, .TLSLD, -4),
3161             Relocation.x86_64(text_index, 24, tls_get_addr, .PLT32, -4),
3162             Relocation.x86_64(text_index, 30, x_symbol, .DTPOFF32, 0),
3163         },
3164     });
3165     defer allocator.free(object);
3166 
3167     var linked = try linkExecutable(allocator, &.{.{ .name = "tls-dynamic.o", .bytes = object }}, .{ .incremental_mode = .off });
3168     defer linked.deinit(allocator);
3169 
3170     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3171     const text_offset: usize = @intCast(text_record.file_offset);
3172     const gd_expected = [_]u8{
3173         0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00,
3174         0x00, 0x48, 0x8d, 0x80, 0xfc, 0xff, 0xff, 0xff,
3175     };
3176     const ld_expected = [_]u8{
3177         0x66, 0x66, 0x66, 0x64, 0x48, 0x8b, 0x04, 0x25,
3178         0x00, 0x00, 0x00, 0x00,
3179     };
3180     try std.testing.expectEqualSlices(u8, &gd_expected, linked.bytes[text_offset..][0..gd_expected.len]);
3181     try std.testing.expectEqualSlices(u8, &ld_expected, linked.bytes[text_offset + 16 ..][0..ld_expected.len]);
3182     try std.testing.expectEqual(@as(i32, -4), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 30))));
3183 }
3184 
3185 test "ELF incremental manifest distinguishes TLS file payloads from TBSS memory" {
3186     const allocator = std.testing.allocator;
3187     const text = [_]u8{0xc3};
3188     const tdata = [_]u8{ 7, 0, 0, 0 };
3189     const text_index: u16 = 1;
3190     const tdata_index: u16 = 2;
3191     const tbss_index: u16 = 3;
3192     const object = try buildObject(allocator, .{
3193         .sections = &.{
3194             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3195             Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3196             Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3197         },
3198         .symbols = &.{
3199             Symbol.section(text_index),
3200             Symbol.section(tdata_index),
3201             Symbol.section(tbss_index),
3202             Symbol.function("_start", text_index, 0, text.len),
3203             Symbol.tlsObject("x", tdata_index, 0, 4),
3204             Symbol.tlsObject("y", tbss_index, 0, 4),
3205         },
3206     });
3207     defer allocator.free(object);
3208 
3209     var linked = try linkExecutable(allocator, &.{.{ .name = "tls.o", .bytes = object }}, .{});
3210     defer linked.deinit(allocator);
3211 
3212     const tdata_contribution = metadata.manifestContribution(linked.manifest, "tls.o", .section, ".tdata", tdata_index) orelse return error.MissingSection;
3213     const tbss_contribution = metadata.manifestContribution(linked.manifest, "tls.o", .section, ".tbss", tbss_index) orelse return error.MissingSection;
3214     try std.testing.expectEqual(@as(u64, tdata.len), tdata_contribution.file_size);
3215     try std.testing.expectEqual(@as(u64, 0), tbss_contribution.file_size);
3216 
3217     const tdata_plan = linked.manifest.planContributionReplacement(&.{
3218         .{ .input_name = "tls.o", .input_index = 0, .kind = .section, .name = ".tdata", .ordinal = tdata_index, .size = tdata.len, .alignment = 4 },
3219     });
3220     try std.testing.expectEqual(incremental.PatchDecision.in_place, tdata_plan.decision);
3221 
3222     const tbss_plan = linked.manifest.planContributionReplacement(&.{
3223         .{ .input_name = "tls.o", .input_index = 0, .kind = .section, .name = ".tbss", .ordinal = tbss_index, .size = 4, .alignment = 4 },
3224     });
3225     try std.testing.expectEqual(incremental.PatchDecision.in_place, tbss_plan.decision);
3226 }
3227 
3228 test "ELF linker allocates common symbols in bss" {
3229     const allocator = std.testing.allocator;
3230     const text = [_]u8{
3231         0x48, 0xb8,
3232         0,    0,
3233         0,    0,
3234         0,    0,
3235         0,    0,
3236         0xc3,
3237     };
3238     const text_index: u16 = 1;
3239     const scratch_symbol: u32 = 3;
3240     const object = try buildObject(allocator, .{
3241         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3242         .symbols = &.{
3243             Symbol.section(text_index),
3244             Symbol.function("_start", text_index, 0, text.len),
3245             Symbol.commonObject("scratch", 16, 24),
3246         },
3247         .relocations = &.{Relocation.x86_64(1, 2, scratch_symbol, .@"64", 0)},
3248     });
3249     defer allocator.free(object);
3250 
3251     const inputs = [_]model.Input{.{ .name = "common.o", .bytes = object }};
3252     var linked = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
3253     defer linked.deinit(allocator);
3254     var manifest_linked = try linkExecutable(allocator, &inputs, .{});
3255     defer manifest_linked.deinit(allocator);
3256 
3257     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3258     const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
3259     try std.testing.expectEqual(@as(u64, 24), bss_record.size);
3260     try std.testing.expectEqual(@as(u64, 32), bss_record.reserved_size);
3261     try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
3262     try std.testing.expectEqual(@as(u64, 0), bss_record.address % 16);
3263     try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
3264 
3265     const manifest_bss_record = metadata.manifestSection(manifest_linked.manifest, ".bss") orelse return error.MissingSection;
3266     const common_contribution = metadata.manifestContribution(manifest_linked.manifest, "common.o", .common_symbol, "scratch", scratch_symbol) orelse return error.MissingSection;
3267     try std.testing.expectEqualStrings(".bss", manifest_linked.manifest.string(common_contribution.output_section_name_id));
3268     try std.testing.expectEqual(manifest_bss_record.address, common_contribution.address);
3269     try std.testing.expectEqual(@as(u64, 24), common_contribution.size);
3270     try std.testing.expectEqual(@as(u64, 0), common_contribution.file_size);
3271     try std.testing.expect(common_contribution.reserved_size > common_contribution.size);
3272     try std.testing.expectEqual(@as(u64, 16), common_contribution.alignment);
3273 
3274     const common_plan = manifest_linked.manifest.planContributionReplacement(&.{
3275         .{ .input_name = "common.o", .input_index = 0, .kind = .common_symbol, .name = "scratch", .ordinal = scratch_symbol, .size = 24, .alignment = 16 },
3276     });
3277     try std.testing.expectEqual(incremental.PatchDecision.in_place, common_plan.decision);
3278 
3279     const bss_header = try metadata.linkedSectionByName(linked.bytes, ".bss");
3280     try std.testing.expectEqual(std.elf.SHT_NOBITS, bss_header.section_type);
3281 
3282     const bss_load = try metadata.loadProgramHeaderByAddress(linked.bytes, bss_record.address);
3283     try std.testing.expectEqual(@as(u64, 0), bss_load.file_size);
3284     try std.testing.expectEqual(bss_record.reserved_size, bss_load.memory_size);
3285 }
3286 
3287 test "ELF linker coalesces common symbols by size" {
3288     const allocator = std.testing.allocator;
3289     const text = [_]u8{
3290         0x48, 0xb8,
3291         0,    0,
3292         0,    0,
3293         0,    0,
3294         0,    0,
3295         0xc3,
3296     };
3297     const text_index: u16 = 1;
3298     const caller_scratch: u32 = 3;
3299     const caller_object = try buildObject(allocator, .{
3300         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3301         .symbols = &.{
3302             Symbol.section(text_index),
3303             Symbol.function("_start", text_index, 0, text.len),
3304             Symbol.commonObject("scratch", 8, 8),
3305         },
3306         .relocations = &.{Relocation.x86_64(1, 2, caller_scratch, .@"64", 0)},
3307     });
3308     defer allocator.free(caller_object);
3309 
3310     const storage_object = try buildObject(allocator, .{
3311         .sections = &.{},
3312         .symbols = &.{Symbol.commonObject("scratch", 16, 32)},
3313     });
3314     defer allocator.free(storage_object);
3315 
3316     var linked = try linkExecutable(allocator, &.{
3317         .{ .name = "caller.o", .bytes = caller_object },
3318         .{ .name = "storage.o", .bytes = storage_object },
3319     }, .{ .incremental_mode = .off });
3320     defer linked.deinit(allocator);
3321 
3322     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3323     const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
3324     try std.testing.expectEqual(@as(u64, 32), bss_record.size);
3325     try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
3326     try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
3327 }
3328 
3329 test "ELF linker lets strong definitions override common symbols" {
3330     const allocator = std.testing.allocator;
3331 
3332     const text = [_]u8{
3333         0x48, 0xb8,
3334         0,    0,
3335         0,    0,
3336         0,    0,
3337         0,    0,
3338         0xc3,
3339     };
3340     const text_index: u16 = 1;
3341     const slot_ref: u32 = 3;
3342     const caller_object = try buildObject(allocator, .{
3343         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3344         .symbols = &.{
3345             Symbol.section(text_index),
3346             Symbol.function("_start", text_index, 0, text.len),
3347             Symbol.undefinedObject("shared_slot"),
3348         },
3349         .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3350     });
3351     defer allocator.free(caller_object);
3352 
3353     const common_symbol: u32 = 1;
3354     const common_object = try buildObject(allocator, .{
3355         .sections = &.{},
3356         .symbols = &.{Symbol.commonObject("shared_slot", 32, 128)},
3357     });
3358     defer allocator.free(common_object);
3359 
3360     const data = [_]u8{ 1, 2, 3, 4 };
3361     const data_index: u16 = 1;
3362     const strong_object = try buildObject(allocator, .{
3363         .sections = &.{Section.progbits(".data.shared", &data, std.elf.SHF_WRITE, 4)},
3364         .symbols = &.{ Symbol.section(data_index), Symbol.object("shared_slot", data_index, 0, data.len) },
3365     });
3366     defer allocator.free(strong_object);
3367 
3368     var linked = try linkExecutable(allocator, &.{
3369         .{ .name = "caller.o", .bytes = caller_object },
3370         .{ .name = "common.o", .bytes = common_object },
3371         .{ .name = "strong.o", .bytes = strong_object },
3372     }, .{});
3373     defer linked.deinit(allocator);
3374 
3375     const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", text_index) orelse return error.MissingSection;
3376     const strong_contribution = metadata.manifestContribution(linked.manifest, "strong.o", .section, ".data.shared", data_index) orelse return error.MissingSection;
3377     try std.testing.expect(metadata.manifestContribution(linked.manifest, "common.o", .common_symbol, "shared_slot", common_symbol) == null);
3378     try std.testing.expect(metadata.manifestSection(linked.manifest, ".bss") == null);
3379 
3380     const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
3381     try std.testing.expectEqual(strong_contribution.address, loaded);
3382 }
3383 
3384 test "ELF linker lazily extracts archive members and rescans the same archive" {
3385     const allocator = std.testing.allocator;
3386 
3387     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3388     const caller_text_index: u16 = 1;
3389     const callee_symbol: u32 = 3;
3390     const caller_object = try buildObject(allocator, .{
3391         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3392         .symbols = &.{
3393             Symbol.section(caller_text_index),
3394             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3395             Symbol.undefinedFunction("callee"),
3396         },
3397         .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3398     });
3399     defer allocator.free(caller_object);
3400 
3401     const unused_text = [_]u8{0xc3};
3402     const unused_text_index: u16 = 1;
3403     const unused_object = try buildObject(allocator, .{
3404         .sections = &.{Section.progbits(".text", &unused_text, std.elf.SHF_EXECINSTR, 16)},
3405         .symbols = &.{
3406             Symbol.section(unused_text_index),
3407             Symbol.function("_start", unused_text_index, 0, unused_text.len),
3408         },
3409     });
3410     defer allocator.free(unused_object);
3411 
3412     const helper_text = [_]u8{0xc3};
3413     const helper_text_index: u16 = 1;
3414     const helper_object = try buildObject(allocator, .{
3415         .sections = &.{Section.progbits(".text", &helper_text, std.elf.SHF_EXECINSTR, 16)},
3416         .symbols = &.{
3417             Symbol.section(helper_text_index),
3418             Symbol.function("helper", helper_text_index, 0, helper_text.len),
3419         },
3420     });
3421     defer allocator.free(helper_object);
3422 
3423     const callee_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3424     const callee_text_index: u16 = 1;
3425     const helper_symbol: u32 = 3;
3426     const callee_object = try buildObject(allocator, .{
3427         .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
3428         .symbols = &.{
3429             Symbol.section(callee_text_index),
3430             Symbol.function("callee", callee_text_index, 0, callee_text.len),
3431             Symbol.undefinedFunction("helper"),
3432         },
3433         .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .PLT32, -4)},
3434     });
3435     defer allocator.free(callee_object);
3436 
3437     const archive_bytes = try archive.build(allocator, &.{
3438         .{ .name = "unused.o", .bytes = unused_object },
3439         .{ .name = "helper.o", .bytes = helper_object },
3440         .{ .name = "callee.o", .bytes = callee_object },
3441     });
3442     defer allocator.free(archive_bytes);
3443 
3444     var linked = try linkExecutable(allocator, &.{
3445         .{ .name = "caller.o", .bytes = caller_object },
3446         .{ .name = "libcall.a", .bytes = archive_bytes },
3447     }, .{ .incremental_mode = .off });
3448     defer linked.deinit(allocator);
3449 
3450     try std.testing.expectEqual(@as(usize, 2), linked.manifest.inputs.len);
3451     try std.testing.expectEqualStrings("libcall.a", linked.manifest.string(linked.manifest.inputs[1].name_id));
3452     try std.testing.expectEqual(@as(u64, archive_bytes.len), linked.manifest.inputs[1].size);
3453 
3454     const text = linked.manifest.sections[0];
3455     try std.testing.expectEqual(@as(u64, 33), text.size);
3456     const text_file_offset: usize = @intCast(text.file_offset);
3457     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3458     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
3459 }
3460 
3461 test "ELF linker extracts archive members that define GNU unique symbols" {
3462     const allocator = std.testing.allocator;
3463 
3464     const caller_text = [_]u8{
3465         0x48, 0xb8,
3466         0,    0,
3467         0,    0,
3468         0,    0,
3469         0,    0,
3470         0xc3,
3471     };
3472     const caller_text_index: u16 = 1;
3473     const slot_ref: u32 = 3;
3474     const caller_object = try buildObject(allocator, .{
3475         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3476         .symbols = &.{
3477             Symbol.section(caller_text_index),
3478             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3479             Symbol.undefinedObject("unique_slot"),
3480         },
3481         .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3482     });
3483     defer allocator.free(caller_object);
3484 
3485     const data = [_]u8{ 9, 8, 7, 6 };
3486     const data_index: u16 = 1;
3487     const unique_object = try buildObject(allocator, .{
3488         .sections = &.{Section.progbits(".data.unique", &data, std.elf.SHF_WRITE, 4)},
3489         .symbols = &.{ Symbol.section(data_index), Symbol.gnuUniqueObject("unique_slot", data_index, 0, data.len) },
3490     });
3491     defer allocator.free(unique_object);
3492 
3493     const archive_bytes = try archive.build(allocator, &.{
3494         .{ .name = "unique.o", .bytes = unique_object, .symbols = &.{"unique_slot"} },
3495     });
3496     defer allocator.free(archive_bytes);
3497 
3498     var linked = try linkExecutable(allocator, &.{
3499         .{ .name = "caller.o", .bytes = caller_object },
3500         .{ .name = "libunique.a", .bytes = archive_bytes },
3501     }, .{});
3502     defer linked.deinit(allocator);
3503 
3504     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3505     const unique_contribution = metadata.manifestContribution(linked.manifest, "unique.o", .section, ".data.unique", data_index) orelse return error.MissingSection;
3506     const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little);
3507     try std.testing.expectEqual(unique_contribution.address, loaded);
3508 }
3509 
3510 test "ELF linker coalesces duplicate GNU unique definitions" {
3511     const allocator = std.testing.allocator;
3512 
3513     const caller_text = [_]u8{
3514         0x48, 0xb8,
3515         0,    0,
3516         0,    0,
3517         0,    0,
3518         0,    0,
3519         0xc3,
3520     };
3521     const caller_text_index: u16 = 1;
3522     const slot_ref: u32 = 3;
3523     const caller_object = try buildObject(allocator, .{
3524         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3525         .symbols = &.{
3526             Symbol.section(caller_text_index),
3527             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3528             Symbol.undefinedObject("unique_slot"),
3529         },
3530         .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3531     });
3532     defer allocator.free(caller_object);
3533 
3534     const first_data = [_]u8{ 1, 0, 0, 0 };
3535     const first_data_index: u16 = 1;
3536     const first_object = try buildObject(allocator, .{
3537         .sections = &.{Section.progbits(".data.first_unique", &first_data, std.elf.SHF_WRITE, 4)},
3538         .symbols = &.{
3539             Symbol.section(first_data_index),
3540             Symbol.gnuUniqueObject("unique_slot", first_data_index, 0, first_data.len),
3541         },
3542     });
3543     defer allocator.free(first_object);
3544 
3545     const second_data = [_]u8{ 2, 0, 0, 0 };
3546     const second_data_index: u16 = 1;
3547     const second_object = try buildObject(allocator, .{
3548         .sections = &.{Section.progbits(".data.second_unique", &second_data, std.elf.SHF_WRITE, 4)},
3549         .symbols = &.{
3550             Symbol.section(second_data_index),
3551             Symbol.gnuUniqueObject("unique_slot", second_data_index, 0, second_data.len),
3552         },
3553     });
3554     defer allocator.free(second_object);
3555 
3556     var linked = try linkExecutable(allocator, &.{
3557         .{ .name = "caller.o", .bytes = caller_object },
3558         .{ .name = "first.o", .bytes = first_object },
3559         .{ .name = "second.o", .bytes = second_object },
3560     }, .{});
3561     defer linked.deinit(allocator);
3562 
3563     const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3564     const first_contribution = metadata.manifestContribution(linked.manifest, "first.o", .section, ".data.first_unique", first_data_index) orelse return error.MissingSection;
3565     _ = metadata.manifestContribution(linked.manifest, "second.o", .section, ".data.second_unique", second_data_index) orelse return error.MissingSection;
3566     const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little);
3567     try std.testing.expectEqual(first_contribution.address, loaded);
3568 }
3569 
3570 test "ELF linker skips stale archive candidates after a symbol is resolved" {
3571     const allocator = std.testing.allocator;
3572 
3573     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3574     const caller_text_index: u16 = 1;
3575     const callee_symbol: u32 = 3;
3576     const caller_object = try buildObject(allocator, .{
3577         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3578         .symbols = &.{
3579             Symbol.section(caller_text_index),
3580             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3581             Symbol.undefinedFunction("callee"),
3582         },
3583         .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3584     });
3585     defer allocator.free(caller_object);
3586 
3587     const first_text = [_]u8{0xc3};
3588     const first_text_index: u16 = 1;
3589     const first_object = try buildObject(allocator, .{
3590         .sections = &.{Section.progbits(".text", &first_text, std.elf.SHF_EXECINSTR, 16)},
3591         .symbols = &.{
3592             Symbol.section(first_text_index),
3593             Symbol.function("callee", first_text_index, 0, first_text.len),
3594         },
3595     });
3596     defer allocator.free(first_object);
3597 
3598     const duplicate_text = [_]u8{ 0x90, 0x90, 0xc3 };
3599     const duplicate_text_index: u16 = 1;
3600     const duplicate_object = try buildObject(allocator, .{
3601         .sections = &.{Section.progbits(".text", &duplicate_text, std.elf.SHF_EXECINSTR, 16)},
3602         .symbols = &.{
3603             Symbol.section(duplicate_text_index),
3604             Symbol.function("callee", duplicate_text_index, 0, duplicate_text.len),
3605         },
3606     });
3607     defer allocator.free(duplicate_object);
3608 
3609     const archive_bytes = try archive.build(allocator, &.{
3610         .{ .name = "first.o", .bytes = first_object },
3611         .{ .name = "duplicate.o", .bytes = duplicate_object },
3612     });
3613     defer allocator.free(archive_bytes);
3614 
3615     var linked = try linkExecutable(allocator, &.{
3616         .{ .name = "caller.o", .bytes = caller_object },
3617         .{ .name = "libcallee.a", .bytes = archive_bytes },
3618     }, .{ .incremental_mode = .off });
3619     defer linked.deinit(allocator);
3620 
3621     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3622     const expected_size = alignForward(caller_text.len, 16) + first_text.len;
3623     try std.testing.expectEqual(@as(u64, expected_size), text.size);
3624 }
3625 
3626 test "ELF linker uses archive symbol indexes without parsing unreferenced members" {
3627     const allocator = std.testing.allocator;
3628 
3629     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3630     const caller_text_index: u16 = 1;
3631     const needed_symbol: u32 = 3;
3632     const caller_object = try buildObject(allocator, .{
3633         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3634         .symbols = &.{
3635             Symbol.section(caller_text_index),
3636             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3637             Symbol.undefinedFunction("needed"),
3638         },
3639         .relocations = &.{Relocation.x86_64(1, 1, needed_symbol, .PLT32, -4)},
3640     });
3641     defer allocator.free(caller_object);
3642 
3643     const needed_text = [_]u8{0xc3};
3644     const needed_text_index: u16 = 1;
3645     const needed_object = try buildObject(allocator, .{
3646         .sections = &.{Section.progbits(".text", &needed_text, std.elf.SHF_EXECINSTR, 16)},
3647         .symbols = &.{
3648             Symbol.section(needed_text_index),
3649             Symbol.function("needed", needed_text_index, 0, needed_text.len),
3650         },
3651     });
3652     defer allocator.free(needed_object);
3653 
3654     const unused_coff = coffHeaderBytes();
3655     const archive_bytes = try archive.build(allocator, &.{
3656         .{ .name = "unused.obj", .bytes = &unused_coff, .symbols = &.{"unused"} },
3657         .{ .name = "needed.o", .bytes = needed_object, .symbols = &.{"needed"} },
3658     });
3659     defer allocator.free(archive_bytes);
3660 
3661     var linked = try linkExecutable(allocator, &.{
3662         .{ .name = "caller.o", .bytes = caller_object },
3663         .{ .name = "libneeded.a", .bytes = archive_bytes },
3664     }, .{ .incremental_mode = .off });
3665     defer linked.deinit(allocator);
3666 
3667     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3668     const expected_size = alignForward(caller_text.len, 16) + needed_text.len;
3669     try std.testing.expectEqual(@as(u64, expected_size), text.size);
3670 }
3671 
3672 test "ELF linker diagnoses extracted archive members with mismatched formats" {
3673     const allocator = std.testing.allocator;
3674 
3675     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3676     const caller_text_index: u16 = 1;
3677     const needed_symbol: u32 = 3;
3678     const caller_object = try buildObject(allocator, .{
3679         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3680         .symbols = &.{
3681             Symbol.section(caller_text_index),
3682             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3683             Symbol.undefinedFunction("needed"),
3684         },
3685         .relocations = &.{Relocation.x86_64(1, 1, needed_symbol, .PLT32, -4)},
3686     });
3687     defer allocator.free(caller_object);
3688 
3689     const needed_coff = coffHeaderBytes();
3690     const archive_bytes = try archive.build(allocator, &.{
3691         .{ .name = "needed.obj", .bytes = &needed_coff, .symbols = &.{"needed"} },
3692     });
3693     defer allocator.free(archive_bytes);
3694 
3695     var diagnostics: model.Diagnostics = .{};
3696     try std.testing.expectError(
3697         error.UnsupportedFormat,
3698         linkExecutable(allocator, &.{
3699             .{ .name = "caller.o", .bytes = caller_object },
3700             .{ .name = "libmixed.a", .bytes = archive_bytes },
3701         }, .{ .diagnostics = &diagnostics, .incremental_mode = .off }),
3702     );
3703 
3704     const unsupported = diagnostics.first_unsupported_input_format orelse return error.ExpectedUnsupportedInputFormatDiagnostic;
3705     try std.testing.expectEqual(model.ObjectFormat.coff, unsupported.input_format);
3706     try std.testing.expectEqual(model.ObjectFormat.elf, unsupported.target_format);
3707     try std.testing.expectEqualStrings("libmixed.a", unsupported.input_name);
3708     try std.testing.expectEqualStrings("needed.obj", unsupported.member_name orelse return error.ExpectedUnsupportedInputFormatDiagnostic);
3709 
3710     const failure = diagnostics.linkFailure(error.UnsupportedFormat);
3711     const reported = switch (failure) {
3712         .unsupported_input_format => |reported| reported,
3713         else => return error.ExpectedUnsupportedInputFormatDiagnostic,
3714     };
3715     try std.testing.expectEqual(model.ObjectFormat.coff, reported.input_format);
3716     try std.testing.expectEqual(model.ObjectFormat.elf, reported.target_format);
3717     try std.testing.expectEqualStrings("libmixed.a", reported.input_name);
3718     try std.testing.expectEqualStrings("needed.obj", reported.member_name orelse return error.ExpectedUnsupportedInputFormatDiagnostic);
3719 }
3720 
3721 test "ELF linker extracts indexed archive fanout members" {
3722     const allocator = std.testing.allocator;
3723 
3724     const caller_text = [_]u8{
3725         0xe8, 0, 0, 0, 0,
3726         0xe8, 0, 0, 0, 0,
3727         0xc3,
3728     };
3729     const caller_text_index: u16 = 1;
3730     const first_symbol: u32 = 3;
3731     const second_symbol: u32 = 4;
3732     const caller_object = try buildObject(allocator, .{
3733         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3734         .symbols = &.{
3735             Symbol.section(caller_text_index),
3736             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3737             Symbol.undefinedFunction("first"),
3738             Symbol.undefinedFunction("second"),
3739         },
3740         .relocations = &.{
3741             Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
3742             Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
3743         },
3744     });
3745     defer allocator.free(caller_object);
3746 
3747     const function_text = [_]u8{0xc3};
3748     const first_text_index: u16 = 1;
3749     const first_object = try buildObject(allocator, .{
3750         .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3751         .symbols = &.{
3752             Symbol.section(first_text_index),
3753             Symbol.function("first", first_text_index, 0, function_text.len),
3754         },
3755     });
3756     defer allocator.free(first_object);
3757 
3758     const second_text_index: u16 = 1;
3759     const second_object = try buildObject(allocator, .{
3760         .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3761         .symbols = &.{
3762             Symbol.section(second_text_index),
3763             Symbol.function("second", second_text_index, 0, function_text.len),
3764         },
3765     });
3766     defer allocator.free(second_object);
3767 
3768     const malformed_elf = std.elf.MAGIC ++ "not enough header bytes";
3769     const archive_bytes = try archive.build(allocator, &.{
3770         .{ .name = "unused.o", .bytes = malformed_elf, .symbols = &.{"unused"} },
3771         .{ .name = "first.o", .bytes = first_object, .symbols = &.{"first"} },
3772         .{ .name = "second.o", .bytes = second_object, .symbols = &.{"second"} },
3773     });
3774     defer allocator.free(archive_bytes);
3775 
3776     var linked = try linkExecutable(allocator, &.{
3777         .{ .name = "caller.o", .bytes = caller_object },
3778         .{ .name = "libfanout.a", .bytes = archive_bytes },
3779     }, .{ .incremental_mode = .off });
3780     defer linked.deinit(allocator);
3781 
3782     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3783     try std.testing.expectEqual(@as(u64, 33), text.size);
3784     const text_file_offset: usize = @intCast(text.file_offset);
3785     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3786     try std.testing.expectEqual(@as(i32, 22), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
3787 }
3788 
3789 test "ELF linker preserves wide indexed archive fanout members" {
3790     const allocator = std.testing.allocator;
3791     const function_names = [_][]const u8{
3792         "fanout_0",
3793         "fanout_1",
3794         "fanout_2",
3795         "fanout_3",
3796         "fanout_4",
3797         "fanout_5",
3798         "fanout_6",
3799         "fanout_7",
3800     };
3801     const member_names = [_][]const u8{
3802         "fanout_0.o",
3803         "fanout_1.o",
3804         "fanout_2.o",
3805         "fanout_3.o",
3806         "fanout_4.o",
3807         "fanout_5.o",
3808         "fanout_6.o",
3809         "fanout_7.o",
3810     };
3811     const function_count = function_names.len;
3812 
3813     var caller_text: [function_count * 5 + 1]u8 = undefined;
3814     for (0..function_count) |index| {
3815         const call_offset = index * 5;
3816         caller_text[call_offset..][0..5].* = .{ 0xe8, 0, 0, 0, 0 };
3817     }
3818     caller_text[function_count * 5] = 0xc3;
3819     const caller_text_index: u16 = 1;
3820     var caller_symbols: [function_count + 2]Symbol = undefined;
3821     caller_symbols[0] = Symbol.section(caller_text_index);
3822     caller_symbols[1] = Symbol.function("_start", caller_text_index, 0, caller_text.len);
3823     var caller_relocations: [function_count]Relocation = undefined;
3824     for (function_names, 0..) |name, index| {
3825         const symbol: u32 = @intCast(index + 3);
3826         caller_symbols[index + 2] = Symbol.undefinedFunction(name);
3827         const offset = index * 5 + 1;
3828         caller_relocations[index] = Relocation.x86_64(caller_text_index, offset, symbol, .PLT32, -4);
3829     }
3830     const caller_object = try buildObject(allocator, .{
3831         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3832         .symbols = &caller_symbols,
3833         .relocations = &caller_relocations,
3834     });
3835     defer allocator.free(caller_object);
3836 
3837     var member_bytes: [function_count][]u8 = undefined;
3838     var member_symbol_storage: [function_count][1][]const u8 = undefined;
3839     var members: [function_count]archive.BuildMember = undefined;
3840     const function_text = [_]u8{0xc3};
3841     for (function_names, 0..) |name, index| {
3842         const text_index: u16 = 1;
3843         member_bytes[index] = try buildObject(allocator, .{
3844             .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3845             .symbols = &.{ Symbol.section(text_index), Symbol.function(name, text_index, 0, function_text.len) },
3846         });
3847         member_symbol_storage[index][0] = name;
3848         members[index] = .{
3849             .name = member_names[index],
3850             .bytes = member_bytes[index],
3851             .symbols = member_symbol_storage[index][0..],
3852         };
3853     }
3854     defer for (member_bytes) |bytes| allocator.free(bytes);
3855 
3856     const archive_bytes = try archive.build(allocator, &members);
3857     defer allocator.free(archive_bytes);
3858 
3859     var linked = try linkExecutable(allocator, &.{
3860         .{ .name = "caller.o", .bytes = caller_object },
3861         .{ .name = "libfanout.a", .bytes = archive_bytes },
3862     }, .{ .incremental_mode = .off, .max_link_jobs = 2 });
3863     defer linked.deinit(allocator);
3864 
3865     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3866     const expected_size = alignForward(caller_text.len, 16) + (function_count - 1) * 16 + function_text.len;
3867     try std.testing.expectEqual(@as(u64, expected_size), text.size);
3868 }
3869 
3870 test "ELF linker resolves archive members referenced by later objects" {
3871     const allocator = std.testing.allocator;
3872 
3873     const callee_text = [_]u8{0xc3};
3874     const callee_text_index: u16 = 1;
3875     const callee_object = try buildObject(allocator, .{
3876         .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
3877         .symbols = &.{
3878             Symbol.section(callee_text_index),
3879             Symbol.function("callee", callee_text_index, 0, callee_text.len),
3880         },
3881     });
3882     defer allocator.free(callee_object);
3883 
3884     const archive_bytes = try archive.build(allocator, &.{.{ .name = "callee.o", .bytes = callee_object }});
3885     defer allocator.free(archive_bytes);
3886 
3887     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3888     const caller_text_index: u16 = 1;
3889     const callee_symbol: u32 = 3;
3890     const caller_object = try buildObject(allocator, .{
3891         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3892         .symbols = &.{
3893             Symbol.section(caller_text_index),
3894             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3895             Symbol.undefinedFunction("callee"),
3896         },
3897         .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3898     });
3899     defer allocator.free(caller_object);
3900 
3901     var linked = try linkExecutable(allocator, &.{
3902         .{ .name = "libcallee.a", .bytes = archive_bytes },
3903         .{ .name = "caller.o", .bytes = caller_object },
3904     }, .{ .incremental_mode = .off });
3905     defer linked.deinit(allocator);
3906 
3907     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3908     const text_file_offset: usize = @intCast(text.file_offset);
3909     try std.testing.expectEqual(@as(u64, 17), text.size);
3910     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3911 }
3912 
3913 test "ELF linker carries archive unresolved symbols across later archives" {
3914     const allocator = std.testing.allocator;
3915 
3916     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3917     const caller_text_index: u16 = 1;
3918     const first_symbol: u32 = 3;
3919     const caller_object = try buildObject(allocator, .{
3920         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3921         .symbols = &.{
3922             Symbol.section(caller_text_index),
3923             Symbol.function("_start", caller_text_index, 0, caller_text.len),
3924             Symbol.undefinedFunction("first"),
3925         },
3926         .relocations = &.{Relocation.x86_64(1, 1, first_symbol, .PLT32, -4)},
3927     });
3928     defer allocator.free(caller_object);
3929 
3930     const first_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3931     const first_text_index: u16 = 1;
3932     const second_symbol: u32 = 3;
3933     const first_object = try buildObject(allocator, .{
3934         .sections = &.{Section.progbits(".text", &first_text, std.elf.SHF_EXECINSTR, 16)},
3935         .symbols = &.{
3936             Symbol.section(first_text_index),
3937             Symbol.function("first", first_text_index, 0, first_text.len),
3938             Symbol.undefinedFunction("second"),
3939         },
3940         .relocations = &.{Relocation.x86_64(1, 1, second_symbol, .PLT32, -4)},
3941     });
3942     defer allocator.free(first_object);
3943 
3944     const second_text = [_]u8{0xc3};
3945     const second_text_index: u16 = 1;
3946     const second_object = try buildObject(allocator, .{
3947         .sections = &.{Section.progbits(".text", &second_text, std.elf.SHF_EXECINSTR, 16)},
3948         .symbols = &.{
3949             Symbol.section(second_text_index),
3950             Symbol.function("second", second_text_index, 0, second_text.len),
3951         },
3952     });
3953     defer allocator.free(second_object);
3954 
3955     const first_archive = try archive.build(allocator, &.{.{ .name = "first.o", .bytes = first_object, .symbols = &.{"first"} }});
3956     defer allocator.free(first_archive);
3957     const second_archive = try archive.build(allocator, &.{.{ .name = "second.o", .bytes = second_object, .symbols = &.{"second"} }});
3958     defer allocator.free(second_archive);
3959 
3960     var linked = try linkExecutable(allocator, &.{
3961         .{ .name = "caller.o", .bytes = caller_object },
3962         .{ .name = "libfirst.a", .bytes = first_archive },
3963         .{ .name = "libsecond.a", .bytes = second_archive },
3964     }, .{ .incremental_mode = .off });
3965     defer linked.deinit(allocator);
3966 
3967     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3968     const text_file_offset: usize = @intCast(text.file_offset);
3969     try std.testing.expectEqual(@as(u64, 33), text.size);
3970     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3971     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
3972 }
3973 
3974 test "ELF linker rejects malformed archives" {
3975     try std.testing.expectError(
3976         error.InvalidArchive,
3977         linkExecutable(std.testing.allocator, &.{.{ .name = "bad.a", .bytes = archive.magic ++ "short" }}, .{}),
3978     );
3979 }
3980 
3981 test "ELF linker rejects unresolved globals" {
3982     const allocator = std.testing.allocator;
3983     const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3984     const text_index: u16 = 1;
3985     const missing: u32 = 3;
3986     const object = try buildObject(allocator, .{
3987         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3988         .symbols = &.{
3989             Symbol.section(text_index),
3990             Symbol.function("_start", text_index, 0, text.len),
3991             Symbol.undefinedFunction("missing"),
3992         },
3993         .relocations = &.{Relocation.x86_64(1, 1, missing, .PLT32, -4)},
3994     });
3995     defer allocator.free(object);
3996 
3997     try std.testing.expectError(error.UndefinedSymbol, linkExecutable(allocator, &.{.{ .name = "bad.o", .bytes = object }}, .{}));
3998 }
3999 
4000 test "ELF linker permits unresolved weak symbols" {
4001     const allocator = std.testing.allocator;
4002     const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
4003     const text_index: u16 = 1;
4004     const weak_missing: u32 = 3;
4005     const object = try buildObject(allocator, .{
4006         .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
4007         .symbols = &.{
4008             Symbol.section(text_index),
4009             Symbol.function("_start", text_index, 0, text.len),
4010             Symbol.weakUndefinedFunction("weak_missing"),
4011         },
4012         .relocations = &.{Relocation.x86_64(1, 1, weak_missing, .PLT32, -4)},
4013     });
4014     defer allocator.free(object);
4015 
4016     var linked = try linkExecutable(allocator, &.{.{ .name = "weak.o", .bytes = object }}, .{});
4017     defer linked.deinit(allocator);
4018     try std.testing.expect(linked.bytes.len != 0);
4019 }
4020 
4021 test "ELF linker lets strong definitions override weak definitions" {
4022     const allocator = std.testing.allocator;
4023 
4024     const caller_text = [_]u8{
4025         0x48, 0xb8,
4026         0,    0,
4027         0,    0,
4028         0,    0,
4029         0,    0,
4030         0xc3,
4031     };
4032     const caller_text_index: u16 = 1;
4033     const hook_ref: u32 = 3;
4034     const caller_object = try buildObject(allocator, .{
4035         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4036         .symbols = &.{
4037             Symbol.section(caller_text_index),
4038             Symbol.function("_start", caller_text_index, 0, caller_text.len),
4039             Symbol.undefinedFunction("weak_hook"),
4040         },
4041         .relocations = &.{Relocation.x86_64(1, 2, hook_ref, .@"64", 0)},
4042     });
4043     defer allocator.free(caller_object);
4044 
4045     const weak_text = [_]u8{0xc3};
4046     const weak_index: u16 = 1;
4047     const weak_object = try buildObject(allocator, .{
4048         .sections = &.{Section.progbits(".text.weak", &weak_text, std.elf.SHF_EXECINSTR, 16)},
4049         .symbols = &.{ Symbol.section(weak_index), Symbol.weakFunction("weak_hook", weak_index, 0, weak_text.len) },
4050     });
4051     defer allocator.free(weak_object);
4052 
4053     const strong_text = [_]u8{ 0x90, 0xc3 };
4054     const strong_index: u16 = 1;
4055     const strong_object = try buildObject(allocator, .{
4056         .sections = &.{Section.progbits(".text.strong", &strong_text, std.elf.SHF_EXECINSTR, 16)},
4057         .symbols = &.{
4058             Symbol.section(strong_index),
4059             Symbol.function("weak_hook", strong_index, 0, strong_text.len),
4060         },
4061     });
4062     defer allocator.free(strong_object);
4063 
4064     var linked = try linkExecutable(allocator, &.{
4065         .{ .name = "caller.o", .bytes = caller_object },
4066         .{ .name = "weak.o", .bytes = weak_object },
4067         .{ .name = "strong.o", .bytes = strong_object },
4068     }, .{});
4069     defer linked.deinit(allocator);
4070 
4071     const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
4072     const strong_contribution = metadata.manifestContribution(linked.manifest, "strong.o", .section, ".text.strong", strong_index) orelse return error.MissingSection;
4073     try std.testing.expect(metadata.manifestContribution(linked.manifest, "weak.o", .section, ".text.weak", weak_index) != null);
4074 
4075     const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
4076     try std.testing.expectEqual(strong_contribution.address, loaded);
4077 }
4078 
4079 test "ELF linker selects first weak definition when no strong definition exists" {
4080     const allocator = std.testing.allocator;
4081 
4082     const caller_text = [_]u8{
4083         0x48, 0xb8,
4084         0,    0,
4085         0,    0,
4086         0,    0,
4087         0,    0,
4088         0xc3,
4089     };
4090     const caller_text_index: u16 = 1;
4091     const hook_ref: u32 = 3;
4092     const caller_object = try buildObject(allocator, .{
4093         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4094         .symbols = &.{
4095             Symbol.section(caller_text_index),
4096             Symbol.function("_start", caller_text_index, 0, caller_text.len),
4097             Symbol.undefinedFunction("weak_only_hook"),
4098         },
4099         .relocations = &.{Relocation.x86_64(1, 2, hook_ref, .@"64", 0)},
4100     });
4101     defer allocator.free(caller_object);
4102 
4103     const first_text = [_]u8{0xc3};
4104     const first_index: u16 = 1;
4105     const first_object = try buildObject(allocator, .{
4106         .sections = &.{Section.progbits(".text.first_weak", &first_text, std.elf.SHF_EXECINSTR, 16)},
4107         .symbols = &.{
4108             Symbol.section(first_index),
4109             Symbol.weakFunction("weak_only_hook", first_index, 0, first_text.len),
4110         },
4111     });
4112     defer allocator.free(first_object);
4113 
4114     const second_text = [_]u8{ 0x90, 0xc3 };
4115     const second_index: u16 = 1;
4116     const second_object = try buildObject(allocator, .{
4117         .sections = &.{Section.progbits(".text.second_weak", &second_text, std.elf.SHF_EXECINSTR, 16)},
4118         .symbols = &.{
4119             Symbol.section(second_index),
4120             Symbol.weakFunction("weak_only_hook", second_index, 0, second_text.len),
4121         },
4122     });
4123     defer allocator.free(second_object);
4124 
4125     var linked = try linkExecutable(allocator, &.{
4126         .{ .name = "caller.o", .bytes = caller_object },
4127         .{ .name = "first.o", .bytes = first_object },
4128         .{ .name = "second.o", .bytes = second_object },
4129     }, .{});
4130     defer linked.deinit(allocator);
4131 
4132     const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
4133     const first_contribution = metadata.manifestContribution(linked.manifest, "first.o", .section, ".text.first_weak", first_index) orelse return error.MissingSection;
4134     try std.testing.expect(metadata.manifestContribution(linked.manifest, "second.o", .section, ".text.second_weak", second_index) != null);
4135 
4136     const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
4137     try std.testing.expectEqual(first_contribution.address, loaded);
4138 }
4139 
4140 test "ELF linker parallel unresolved relocation scan matches serial output" {
4141     const allocator = std.testing.allocator;
4142     const relocation_count = format.parallel_relocation_threshold + 1024;
4143 
4144     const text = [_]u8{0xc3};
4145     const text_index: u16 = 1;
4146     const data = try allocator.alloc(u8, relocation_count * 8);
4147     defer allocator.free(data);
4148     @memset(data, 0);
4149     const data_index: u16 = 2;
4150     const target_ref: u32 = 4;
4151     const relocations = try allocator.alloc(Relocation, relocation_count);
4152     defer allocator.free(relocations);
4153     for (relocations, 0..) |*relocation, index| {
4154         relocation.* = Relocation.x86_64(data_index, index * 8, target_ref, .@"64", 0);
4155     }
4156     const caller_object = try buildObject(allocator, .{
4157         .sections = &.{
4158             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4159             Section.progbits(".data.refs", data, std.elf.SHF_WRITE, 8),
4160         },
4161         .symbols = &.{
4162             Symbol.section(text_index),
4163             Symbol.section(data_index),
4164             Symbol.function("_start", text_index, 0, text.len),
4165             Symbol.undefinedObject("target_value"),
4166         },
4167         .relocations = relocations,
4168     });
4169     defer allocator.free(caller_object);
4170 
4171     const target_data = [_]u8{ 1, 2, 3, 4 };
4172     const target_index: u16 = 1;
4173     const target_object = try buildObject(allocator, .{
4174         .sections = &.{Section.progbits(".rodata.target", &target_data, 0, 4)},
4175         .symbols = &.{
4176             Symbol.section(target_index),
4177             Symbol.object("target_value", target_index, 0, target_data.len),
4178         },
4179     });
4180     defer allocator.free(target_object);
4181 
4182     const inputs = [_]model.Input{
4183         .{ .name = "caller.o", .bytes = caller_object },
4184         .{ .name = "target.o", .bytes = target_object },
4185     };
4186 
4187     var serial = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4188     defer serial.deinit(allocator);
4189     var concurrent = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4190     defer concurrent.deinit(allocator);
4191 
4192     try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4193 }
4194 
4195 test "ELF linker parallel global symbols and executable entries match serial output" {
4196     const allocator = std.testing.allocator;
4197     const object_count = 128;
4198     const definitions_per_object = symbol_table.parallel_global_symbol_threshold / object_count + 8;
4199 
4200     const inputs = try allocator.alloc(model.Input, object_count);
4201     defer allocator.free(inputs);
4202     var built_count: usize = 0;
4203     defer {
4204         for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4205     }
4206 
4207     for (inputs, 0..) |*input, object_index| {
4208         const text = [_]u8{0xc3};
4209         const section_name = try std.fmt.allocPrint(allocator, ".text.{d}", .{object_index});
4210         defer allocator.free(section_name);
4211         const text_index: u16 = 1;
4212 
4213         var names: [definitions_per_object][]u8 = undefined;
4214         var named: usize = 0;
4215         defer for (names[0..named]) |name| allocator.free(name);
4216 
4217         var symbols: [definitions_per_object + 2]Symbol = undefined;
4218         var symbol_count: usize = 0;
4219         symbols[symbol_count] = Symbol.section(text_index);
4220         symbol_count += 1;
4221         if (object_index == 0) {
4222             symbols[symbol_count] = Symbol.function("_start", text_index, 0, text.len);
4223             symbol_count += 1;
4224         }
4225 
4226         for (0..definitions_per_object) |symbol_index| {
4227             names[symbol_index] = try std.fmt.allocPrint(allocator, "global_{d}_{d}", .{ object_index, symbol_index });
4228             named += 1;
4229             symbols[symbol_count] = Symbol.object(names[symbol_index], text_index, 0, text.len);
4230             symbol_count += 1;
4231         }
4232 
4233         input.* = .{
4234             .name = "globals.o",
4235             .bytes = try buildObject(allocator, .{
4236                 .sections = &.{Section.progbits(section_name, &text, std.elf.SHF_EXECINSTR, 16)},
4237                 .symbols = symbols[0..symbol_count],
4238             }),
4239         };
4240         built_count += 1;
4241     }
4242 
4243     var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4244     defer serial.deinit(allocator);
4245     var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4246     defer concurrent.deinit(allocator);
4247 
4248     try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4249 }
4250 
4251 test "ELF linker parallel output payload copy matches serial output" {
4252     const allocator = std.testing.allocator;
4253     const object_count = 64;
4254     const payload_size = payload.parallel_object_copy_threshold / object_count + 4096;
4255 
4256     const payload_bytes = try allocator.alloc(u8, payload_size);
4257     defer allocator.free(payload_bytes);
4258     for (payload_bytes, 0..) |*byte, index| byte.* = @truncate(index *% 251);
4259 
4260     const inputs = try allocator.alloc(model.Input, object_count);
4261     defer allocator.free(inputs);
4262     var built_count: usize = 0;
4263     defer {
4264         for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4265     }
4266 
4267     for (inputs, 0..) |*input, object_index| {
4268         const text = [_]u8{0xc3};
4269         const section_name = try std.fmt.allocPrint(allocator, ".rodata.copy.{d}", .{object_index});
4270         defer allocator.free(section_name);
4271         const symbol_name = try std.fmt.allocPrint(allocator, "copy_payload_{d}", .{object_index});
4272         defer allocator.free(symbol_name);
4273 
4274         input.* = .{
4275             .name = "copy.o",
4276             .bytes = if (object_index == 0) try buildObject(allocator, .{
4277                 .sections = &.{
4278                     Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4279                     Section.progbits(section_name, payload_bytes, 0, 16),
4280                 },
4281                 .symbols = &.{
4282                     Symbol.section(1),
4283                     Symbol.section(2),
4284                     Symbol.function("_start", 1, 0, text.len),
4285                     Symbol.object(symbol_name, 2, 0, payload_bytes.len),
4286                 },
4287             }) else try buildObject(allocator, .{
4288                 .sections = &.{Section.progbits(section_name, payload_bytes, 0, 16)},
4289                 .symbols = &.{ Symbol.section(1), Symbol.object(symbol_name, 1, 0, payload_bytes.len) },
4290             }),
4291         };
4292         built_count += 1;
4293     }
4294 
4295     var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4296     defer serial.deinit(allocator);
4297     var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4298     defer concurrent.deinit(allocator);
4299 
4300     try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4301 }
4302 
4303 test "ELF linker keeps load segment pages disjoint with tls" {
4304     const allocator = std.testing.allocator;
4305     const text = [_]u8{0xc3};
4306     const tdata = [_]u8{ 7, 0, 0, 0, 0, 0, 0, 0 };
4307     const data = [_]u8{ 1, 2, 3, 4 };
4308     const text_index: u16 = 1;
4309     const tdata_index: u16 = 2;
4310     const object = try buildObject(allocator, .{
4311         .sections = &.{
4312             Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4313             Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 8),
4314             Section.nobits(".tbss", 64, std.elf.SHF_WRITE | std.elf.SHF_TLS, 8),
4315             Section.progbits(".data", &data, std.elf.SHF_WRITE, 4),
4316             Section.nobits(".bss", 128, std.elf.SHF_WRITE, 8),
4317         },
4318         .symbols = &.{
4319             Symbol.section(text_index),
4320             Symbol.section(tdata_index),
4321             Symbol.section(3),
4322             Symbol.section(4),
4323             Symbol.section(5),
4324             Symbol.function("_start", text_index, 0, text.len),
4325             Symbol.tlsObject("x", tdata_index, 0, 8),
4326         },
4327     });
4328     defer allocator.free(object);
4329 
4330     var linked = try linkExecutable(allocator, &.{.{ .name = "tls-pages.o", .bytes = object }}, .{ .incremental_mode = .off });
4331     defer linked.deinit(allocator);
4332 
4333     const page_size: u64 = 0x1000;
4334     const phnum = readU16(linked.bytes, 56);
4335     var previous_page_end: u64 = 0;
4336     var tls_vaddr: u64 = 0;
4337     var tls_offset: u64 = 0;
4338     var tls_filesz: u64 = 0;
4339     var load_count: usize = 0;
4340     for (0..phnum) |index| {
4341         const offset = ehdr_size + index * phdr_size;
4342         const header_type = readU32(linked.bytes, offset);
4343         if (header_type == std.elf.PT_TLS) {
4344             tls_offset = readU64(linked.bytes, offset + 8);
4345             tls_vaddr = readU64(linked.bytes, offset + 16);
4346             tls_filesz = readU64(linked.bytes, offset + 32);
4347             continue;
4348         }
4349         if (header_type != std.elf.PT_LOAD) continue;
4350         load_count += 1;
4351         const load_offset = readU64(linked.bytes, offset + 8);
4352         const load_vaddr = readU64(linked.bytes, offset + 16);
4353         const load_filesz = readU64(linked.bytes, offset + 32);
4354         const load_memsz = readU64(linked.bytes, offset + 40);
4355         try std.testing.expect(load_memsz >= load_filesz);
4356         const start_page = load_vaddr / page_size;
4357         try std.testing.expect(start_page >= previous_page_end);
4358         previous_page_end = (load_vaddr + load_memsz + page_size - 1) / page_size;
4359         _ = load_offset;
4360     }
4361     try std.testing.expect(load_count >= 3);
4362     try std.testing.expect(tls_filesz != 0);
4363 
4364     var template_backed = false;
4365     for (0..phnum) |index| {
4366         const offset = ehdr_size + index * phdr_size;
4367         if (readU32(linked.bytes, offset) != std.elf.PT_LOAD) continue;
4368         const load_offset = readU64(linked.bytes, offset + 8);
4369         const load_vaddr = readU64(linked.bytes, offset + 16);
4370         const load_filesz = readU64(linked.bytes, offset + 32);
4371         if (tls_vaddr < load_vaddr or tls_vaddr + tls_filesz > load_vaddr + load_filesz) continue;
4372         if (tls_vaddr - tls_offset != load_vaddr - load_offset) continue;
4373         template_backed = true;
4374     }
4375     try std.testing.expect(template_backed);
4376 }
4377 
4378 test "ELF linker synthesizes iplt machinery for ifunc references" {
4379     const allocator = std.testing.allocator;
4380 
4381     const resolver_text = [_]u8{ 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4382     const resolver_index: u16 = 1;
4383     const provider_object = try buildObject(allocator, .{
4384         .sections = &.{Section.progbits(".text.resolver", &resolver_text, std.elf.SHF_EXECINSTR, 16)},
4385         .symbols = &.{
4386             Symbol.section(resolver_index),
4387             Symbol.ifunc("fast_copy", resolver_index, 0, resolver_text.len),
4388         },
4389     });
4390     defer allocator.free(provider_object);
4391 
4392     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4393     const caller_index: u16 = 1;
4394     const copy_symbol: u32 = 3;
4395     const bounds_symbol: u32 = 4;
4396     const caller_object = try buildObject(allocator, .{
4397         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4398         .symbols = &.{
4399             Symbol.section(caller_index),
4400             Symbol.function("_start", caller_index, 0, caller_text.len),
4401             Symbol.undefinedFunction("fast_copy"),
4402             Symbol.undefinedObject("__rela_iplt_start"),
4403         },
4404         .relocations = &.{
4405             Relocation.x86_64(caller_index, 1, copy_symbol, .PLT32, -4),
4406             Relocation.x86_64(caller_index, 8, bounds_symbol, .PC32, -4),
4407         },
4408     });
4409     defer allocator.free(caller_object);
4410 
4411     const inputs = [_]model.Input{
4412         .{ .name = "caller.o", .bytes = caller_object },
4413         .{ .name = "provider.o", .bytes = provider_object },
4414     };
4415     var linked = try linkExecutable(allocator, &inputs, .{});
4416     defer linked.deinit(allocator);
4417 
4418     const iplt = metadata.manifestSection(linked.manifest, ".iplt") orelse return error.MissingSection;
4419     const igot = metadata.manifestSection(linked.manifest, ".igot") orelse return error.MissingSection;
4420     const rela = metadata.manifestSection(linked.manifest, ".rela.iplt") orelse return error.MissingSection;
4421     const resolver = metadata.manifestContribution(linked.manifest, "provider.o", .section, ".text.resolver", 1) orelse return error.MissingSection;
4422 
4423     try std.testing.expectEqual(@as(u64, 16), iplt.size);
4424     try std.testing.expectEqual(@as(u64, 8), igot.size);
4425     try std.testing.expectEqual(@as(u64, 24), rela.size);
4426 
4427     const stub_offset: usize = @intCast(iplt.file_offset);
4428     try std.testing.expectEqual(@as(u8, 0xff), linked.bytes[stub_offset]);
4429     try std.testing.expectEqual(@as(u8, 0x25), linked.bytes[stub_offset + 1]);
4430     const stub_disp = std.mem.readInt(i32, linked.bytes[stub_offset + 2 ..][0..4], .little);
4431     try std.testing.expectEqual(@as(i128, @intCast(igot.address)), @as(i128, @intCast(iplt.address + 6)) + stub_disp);
4432 
4433     const slot_offset: usize = @intCast(igot.file_offset);
4434     try std.testing.expectEqual(resolver.address, std.mem.readInt(u64, linked.bytes[slot_offset..][0..8], .little));
4435 
4436     const rela_offset: usize = @intCast(rela.file_offset);
4437     try std.testing.expectEqual(igot.address, std.mem.readInt(u64, linked.bytes[rela_offset..][0..8], .little));
4438     try std.testing.expectEqual(@as(u64, 37), std.mem.readInt(u64, linked.bytes[rela_offset + 8 ..][0..8], .little));
4439     try std.testing.expectEqual(resolver.address, std.mem.readInt(u64, linked.bytes[rela_offset + 16 ..][0..8], .little));
4440 
4441     const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
4442     const text_offset: usize = @intCast(text.file_offset);
4443     const call_disp = std.mem.readInt(i32, linked.bytes[text_offset + 1 ..][0..4], .little);
4444     try std.testing.expectEqual(@as(i128, @intCast(iplt.address)), @as(i128, @intCast(text.address + 5)) + call_disp);
4445     const bounds_disp = std.mem.readInt(i32, linked.bytes[text_offset + 8 ..][0..4], .little);
4446     try std.testing.expectEqual(@as(i128, @intCast(rela.address)), @as(i128, @intCast(text.address + 12)) + bounds_disp);
4447 }
4448 
4449 test "ELF linker parallel ifunc marks match serial output" {
4450     const allocator = std.testing.allocator;
4451     const filler_count = 128;
4452     const symbols_per_filler = ifunc.parallel_mark_symbol_threshold / filler_count + 8;
4453 
4454     const inputs = try allocator.alloc(model.Input, filler_count + 2);
4455     defer allocator.free(inputs);
4456     var built_count: usize = 0;
4457     defer {
4458         for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4459     }
4460 
4461     const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
4462     const caller_index: u16 = 1;
4463     const copy_symbol: u32 = 3;
4464     inputs[built_count] = .{ .name = "caller.o", .bytes = try buildObject(allocator, .{
4465         .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4466         .symbols = &.{
4467             Symbol.section(caller_index),
4468             Symbol.function("_start", caller_index, 0, caller_text.len),
4469             Symbol.undefinedFunction("fast_copy"),
4470         },
4471         .relocations = &.{Relocation.x86_64(caller_index, 1, copy_symbol, .PLT32, -4)},
4472     }) };
4473     built_count += 1;
4474 
4475     const resolver_text = [_]u8{ 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4476     const resolver_index: u16 = 1;
4477     inputs[built_count] = .{ .name = "provider.o", .bytes = try buildObject(allocator, .{
4478         .sections = &.{Section.progbits(".text.resolver", &resolver_text, std.elf.SHF_EXECINSTR, 16)},
4479         .symbols = &.{
4480             Symbol.section(resolver_index),
4481             Symbol.ifunc("fast_copy", resolver_index, 0, resolver_text.len),
4482         },
4483     }) };
4484     built_count += 1;
4485 
4486     for (0..filler_count) |object_index| {
4487         const text = [_]u8{0xc3};
4488         const text_index: u16 = 1;
4489 
4490         var names: [symbols_per_filler][]u8 = undefined;
4491         var named: usize = 0;
4492         defer for (names[0..named]) |name| allocator.free(name);
4493 
4494         var symbols: [symbols_per_filler + 1]Symbol = undefined;
4495         symbols[0] = Symbol.section(text_index);
4496         for (0..symbols_per_filler) |symbol_index| {
4497             const pattern = "ifunc_mark_fill_{d}_{d}";
4498             names[symbol_index] = try std.fmt.allocPrint(allocator, pattern, .{ object_index, symbol_index });
4499             named += 1;
4500             symbols[symbol_index + 1] = Symbol.function(names[symbol_index], text_index, 0, text.len);
4501         }
4502 
4503         inputs[built_count] = .{ .name = "fill.o", .bytes = try buildObject(allocator, .{
4504             .sections = &.{Section.progbits(".text.fill", &text, std.elf.SHF_EXECINSTR, 16)},
4505             .symbols = &symbols,
4506         }) };
4507         built_count += 1;
4508     }
4509 
4510     var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4511     defer serial.deinit(allocator);
4512     var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4513     defer concurrent.deinit(allocator);
4514 
4515     try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4516 }
4517 
4518 test "elf namespace" {
4519     std.testing.refAllDecls(elf);
4520     std.testing.refAllDecls(link);
4521     std.testing.refAllDecls(metadata);
4522     std.testing.refAllDecls(address_suite);
4523     std.testing.refAllDecls(archive_suite);
4524     std.testing.refAllDecls(ehframe_suite);
4525     std.testing.refAllDecls(group_suite);
4526     std.testing.refAllDecls(icf_suite);
4527     std.testing.refAllDecls(image_suite);
4528     std.testing.refAllDecls(layout_suite);
4529     std.testing.refAllDecls(liveness_suite);
4530     std.testing.refAllDecls(merge_suite);
4531     std.testing.refAllDecls(relocation_suite);
4532 }