lib/tldr/src/formats/coff.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const root = @import("../root.zig");
   3 const incremental = root.incremental;
   4 const model = root.model;
   5 const trace = root.trace;
   6 
   7 const Allocator = std.mem.Allocator;
   8 
   9 const header_size = @sizeOf(std.coff.Header);
  10 const section_header_size = @sizeOf(std.coff.SectionHeader);
  11 const symbol_size = std.coff.Symbol.sizeOf();
  12 const relocation_size = 10;
  13 const absolute_relocation_kind: u16 = 0;
  14 const pe_offset = 0x80;
  15 const pe_signature_size = 4;
  16 const pe32_plus_optional_header_size = 240;
  17 const pe_data_directory_count = 16;
  18 const coff_file_alignment = 0x200;
  19 const coff_text_section_flags = 0x60000020;
  20 const coff_executable_flags = 0x22;
  21 const coff_code_flags = 0x20;
  22 const coff_uninitialized_data_flags = 0x80;
  23 const coff_discardable_flags = 0x02000000;
  24 const coff_execute_flags = 0x20000000;
  25 const pe32_plus_magic = 0x20b;
  26 const windows_cui_subsystem = 3;
  27 const nx_compat_dll_characteristic = 0x0100;
  28 
  29 pub const Object = struct {
  30     target: model.Target,
  31     sections: []Section,
  32     relocations: []Relocation,
  33     symbols: []Symbol,
  34     string_table: []const u8,
  35 
  36     pub fn deinit(self: *Object, allocator: Allocator) void {
  37         if (self.sections.len != 0) allocator.free(self.sections);
  38         if (self.relocations.len != 0) allocator.free(self.relocations);
  39         if (self.symbols.len != 0) allocator.free(self.symbols);
  40         self.* = undefined;
  41     }
  42 };
  43 
  44 pub const Section = struct {
  45     name: []const u8,
  46     size: u32,
  47     offset: u32,
  48     relocation_offset: u32,
  49     relocation_count: u16,
  50     flags: u32,
  51     alignment: u16,
  52 };
  53 
  54 pub const Relocation = struct {
  55     section_index: usize,
  56     virtual_address: u32,
  57     symbol_table_index: u32,
  58     kind: u16,
  59 };
  60 
  61 pub const Symbol = struct {
  62     raw_index: u32,
  63     name: []const u8,
  64     value: u32,
  65     section_number: i16,
  66     kind: u16,
  67     storage_class: u8,
  68     aux_count: u8,
  69 };
  70 
  71 const TextContribution = struct {
  72     input_index: usize,
  73     input_name: []const u8,
  74     section_index: usize,
  75     section_name: []const u8,
  76     bytes: []const u8,
  77     alignment: u64,
  78     output_offset: u64 = 0,
  79 };
  80 
  81 const LinkObject = struct {
  82     input_name: []const u8,
  83     input_index: usize,
  84     object: Object,
  85 };
  86 
  87 const SymbolDefinition = struct {
  88     input_index: usize,
  89     section_index: usize,
  90     value: u32,
  91 };
  92 
  93 pub fn isObject(bytes: []const u8) bool {
  94     if (bytes.len < header_size) return false;
  95     const machine = std.mem.readInt(u16, bytes[0..2], .little);
  96     switch (@as(std.coff.IMAGE.FILE.MACHINE, @fromBackingInt(@intCast(machine)))) {
  97         .AMD64, .ARM64 => {},
  98         else => return false,
  99     }
 100     return std.mem.readInt(u16, bytes[16..18], .little) == 0;
 101 }
 102 
 103 pub fn linkExecutable(
 104     allocator: Allocator,
 105     inputs: []const model.Input,
 106     options: model.LinkOptions,
 107 ) model.Error!root.LinkedImage {
 108     const link_phase = trace.scope("link.coff");
 109     defer link_phase.end();
 110     if (options.diagnostics) |diagnostics| diagnostics.clear();
 111     if (options.output_kind != .executable) return error.UnsupportedOutputKind;
 112     if (options.target.architecture != .x86_64 or options.target.endianness != .little) {
 113         return error.UnsupportedArchitecture;
 114     }
 115     if (options.page_size < coff_file_alignment) return error.InvalidAlignment;
 116     if (options.gc_sections or options.icf != .off) return error.UnsupportedFormat;
 117     if (inputs.len == 0) return error.NoAllocSections;
 118 
 119     var scratch_state = std.heap.ArenaAllocator.init(allocator);
 120     defer scratch_state.deinit();
 121     const scratch = scratch_state.allocator();
 122 
 123     var manifest_builder = build_manifest: {
 124         const manifest_phase = trace.product(.manifest_recording);
 125         defer manifest_phase.end();
 126         var builder = try incremental.Builder.init(allocator, options);
 127         errdefer builder.deinit();
 128         for (inputs) |input| try builder.addInput(input);
 129         break :build_manifest builder;
 130     };
 131     errdefer manifest_builder.deinit();
 132 
 133     var contributions = std.ArrayListUnmanaged(TextContribution).empty;
 134     defer contributions.deinit(scratch);
 135 
 136     var definitions: std.StringHashMapUnmanaged(SymbolDefinition) = .{};
 137     defer definitions.deinit(scratch);
 138 
 139     var objects = std.ArrayListUnmanaged(LinkObject).empty;
 140     defer objects.deinit(scratch);
 141 
 142     for (inputs, 0..) |input, input_index| {
 143         if (root.archive.isArchive(input.bytes)) return error.UnsupportedFormat;
 144         const object = try parseObject(scratch, input.bytes);
 145         if (object.target.object_format != .coff) return error.UnsupportedFormat;
 146         if (object.target.architecture != options.target.architecture) return error.UnsupportedArchitecture;
 147 
 148         try objects.append(scratch, .{
 149             .input_name = input.name,
 150             .input_index = input_index,
 151             .object = object,
 152         });
 153     }
 154 
 155     {
 156         const layout_phase = trace.product(.section_contribution_graph);
 157         defer layout_phase.end();
 158         for (objects.items) |link_object| {
 159             const input = inputs[link_object.input_index];
 160             try collectTextContributions(scratch, &contributions, input, link_object.input_index, link_object.object.sections);
 161         }
 162     }
 163     {
 164         const symbol_phase = trace.product(.symbol_database);
 165         defer symbol_phase.end();
 166         for (objects.items) |link_object| {
 167             try indexExternalDefinitions(scratch, &definitions, link_object.input_index, link_object.object);
 168         }
 169     }
 170 
 171     const text_size = layout_text: {
 172         const layout_phase = trace.product(.address_assignment);
 173         defer layout_phase.end();
 174         break :layout_text try layoutTextContributions(contributions.items);
 175     };
 176     if (text_size == 0) return error.NoAllocSections;
 177 
 178     const entry = resolve_entry: {
 179         const symbol_phase = trace.product(.symbol_database);
 180         defer symbol_phase.end();
 181         const entry_definition = definitions.get(options.entry_symbol) orelse return error.MissingEntrySymbol;
 182         const entry_contribution = contributionForDefinition(contributions.items, entry_definition) orelse return error.MissingEntrySymbol;
 183         if (entry_definition.value > entry_contribution.bytes.len) return error.InvalidRange;
 184         break :resolve_entry .{
 185             .definition = entry_definition,
 186             .contribution = entry_contribution,
 187         };
 188     };
 189 
 190     const section_alignment = options.page_size;
 191     const text_rva = section_alignment;
 192     const entry_rva = try checkedU32FromU64(try checkedAddU64(text_rva, try checkedAddU64(entry.contribution.output_offset, entry.definition.value)));
 193     const text_raw_size = try alignForward(text_size, coff_file_alignment);
 194     const headers_size = try alignForward(pe_offset + pe_signature_size + header_size + pe32_plus_optional_header_size + section_header_size, coff_file_alignment);
 195     const size_of_image = try alignForward(try checkedAddU64(text_rva, text_size), section_alignment);
 196     const total_size = try checkedAddU64(headers_size, text_raw_size);
 197 
 198     const image = try allocator.alloc(u8, try checkedUsize(total_size));
 199     errdefer allocator.free(image);
 200     {
 201         const write_phase = trace.product(.output_writing);
 202         defer write_phase.end();
 203         @memset(image, 0);
 204 
 205         try writePeHeaders(
 206             image,
 207             options,
 208             text_size,
 209             text_raw_size,
 210             headers_size,
 211             entry_rva,
 212             size_of_image,
 213         );
 214         for (contributions.items) |contribution| {
 215             const start = try checkedUsize(try checkedAddU64(headers_size, contribution.output_offset));
 216             @memcpy(image[start..][0..contribution.bytes.len], contribution.bytes);
 217         }
 218     }
 219     {
 220         const manifest_phase = trace.product(.manifest_recording);
 221         defer manifest_phase.end();
 222         for (contributions.items) |contribution| {
 223             try manifest_builder.addContribution(
 224                 contribution.input_name,
 225                 contribution.input_index,
 226                 .section,
 227                 contribution.section_name,
 228                 @intCast(contribution.section_index),
 229                 ".text",
 230                 try checkedAddU64(options.image_base, try checkedAddU64(text_rva, contribution.output_offset)),
 231                 try checkedAddU64(headers_size, contribution.output_offset),
 232                 contribution.bytes.len,
 233                 contribution.bytes.len,
 234                 contribution.alignment,
 235             );
 236         }
 237     }
 238     try applyRelocations(
 239         image,
 240         objects.items,
 241         contributions.items,
 242         &definitions,
 243         options,
 244         headers_size,
 245         text_rva,
 246     );
 247 
 248     {
 249         const manifest_phase = trace.product(.manifest_recording);
 250         defer manifest_phase.end();
 251         try manifest_builder.addSection(
 252             ".text",
 253             try checkedAddU64(options.image_base, text_rva),
 254             headers_size,
 255             text_size,
 256             text_raw_size,
 257             section_alignment,
 258         );
 259     }
 260 
 261     const manifest = finish_manifest: {
 262         const manifest_phase = trace.product(.manifest_recording);
 263         defer manifest_phase.end();
 264         break :finish_manifest try manifest_builder.finish();
 265     };
 266 
 267     return .{
 268         .bytes = image,
 269         .manifest = manifest,
 270     };
 271 }
 272 
 273 pub fn parseObject(allocator: Allocator, bytes: []const u8) model.Error!Object {
 274     const phase = trace.product(.input_discovery);
 275     defer phase.end();
 276     _ = try range(bytes, 0, header_size);
 277 
 278     const machine = try readU16(bytes, 0);
 279     const target = try targetFromMachine(machine);
 280     const section_count = try readU16(bytes, 2);
 281     const symbol_table_offset = try readU32(bytes, 8);
 282     const raw_symbol_count = try readU32(bytes, 12);
 283     const optional_header_size = try readU16(bytes, 16);
 284     if (optional_header_size != 0) return error.UnsupportedFormat;
 285 
 286     const section_table_offset = header_size + @as(usize, optional_header_size);
 287     const section_bytes_len = try checkedMul(@as(usize, section_count), section_header_size);
 288     _ = try range(bytes, section_table_offset, section_bytes_len);
 289 
 290     const string_table = try coffStringTable(bytes, symbol_table_offset, raw_symbol_count);
 291 
 292     const sections = try allocator.alloc(Section, section_count);
 293     errdefer allocator.free(sections);
 294     for (sections, 0..) |*section, index| {
 295         const section_offset = section_table_offset + index * section_header_size;
 296         const raw_data_size = try readU32(bytes, section_offset + 16);
 297         const raw_data_offset = try readU32(bytes, section_offset + 20);
 298         const relocation_offset = try readU32(bytes, section_offset + 24);
 299         const relocation_count = try readU16(bytes, section_offset + 32);
 300         if (raw_data_size != 0 and raw_data_offset != 0) {
 301             _ = try range(bytes, try checkedUsize(raw_data_offset), try checkedUsize(raw_data_size));
 302         }
 303         if (relocation_count != 0) {
 304             _ = try range(
 305                 bytes,
 306                 try checkedUsize(relocation_offset),
 307                 try checkedMul(@as(usize, relocation_count), relocation_size),
 308             );
 309         }
 310         const flags = try readU32(bytes, section_offset + 36);
 311         section.* = .{
 312             .name = try sectionName((try range(bytes, section_offset, 8))[0..8], string_table),
 313             .size = raw_data_size,
 314             .offset = raw_data_offset,
 315             .relocation_offset = relocation_offset,
 316             .relocation_count = relocation_count,
 317             .flags = flags,
 318             .alignment = sectionAlignment(flags),
 319         };
 320     }
 321 
 322     const symbol_range = try coffSymbolRange(bytes, symbol_table_offset, raw_symbol_count);
 323     const primary_symbol_indices = try allocator.alloc(bool, try checkedUsize(raw_symbol_count));
 324     defer if (primary_symbol_indices.len != 0) allocator.free(primary_symbol_indices);
 325     @memset(primary_symbol_indices, false);
 326 
 327     var retained_symbol_count: usize = 0;
 328     var symbol_index: usize = 0;
 329     while (symbol_index < raw_symbol_count) {
 330         const symbol_offset = symbol_index * symbol_size;
 331         const aux_count = symbol_range[symbol_offset + 17];
 332         if (symbol_index + 1 + @as(usize, aux_count) > raw_symbol_count) return error.InvalidObject;
 333         primary_symbol_indices[symbol_index] = true;
 334         retained_symbol_count += 1;
 335         symbol_index += 1 + @as(usize, aux_count);
 336     }
 337 
 338     const relocations = try parseRelocations(allocator, bytes, target.architecture, sections, primary_symbol_indices);
 339     errdefer if (relocations.len != 0) allocator.free(relocations);
 340 
 341     const symbols = try allocator.alloc(Symbol, retained_symbol_count);
 342     errdefer allocator.free(symbols);
 343     var cursor: usize = 0;
 344     symbol_index = 0;
 345     while (symbol_index < raw_symbol_count) {
 346         const symbol_offset = symbol_index * symbol_size;
 347         const aux_count = symbol_range[symbol_offset + 17];
 348         const section_number = std.mem.readInt(i16, symbol_range[symbol_offset + 12 ..][0..2], .little);
 349         try validateSymbolSectionNumber(section_number, section_count);
 350         symbols[cursor] = .{
 351             .raw_index = @intCast(symbol_index),
 352             .name = try symbolName(symbol_range[symbol_offset..][0..8], string_table),
 353             .value = std.mem.readInt(u32, symbol_range[symbol_offset + 8 ..][0..4], .little),
 354             .section_number = section_number,
 355             .kind = std.mem.readInt(u16, symbol_range[symbol_offset + 14 ..][0..2], .little),
 356             .storage_class = symbol_range[symbol_offset + 16],
 357             .aux_count = aux_count,
 358         };
 359         cursor += 1;
 360         symbol_index += 1 + @as(usize, aux_count);
 361     }
 362 
 363     return .{
 364         .target = target,
 365         .sections = sections,
 366         .relocations = relocations,
 367         .symbols = symbols,
 368         .string_table = string_table,
 369     };
 370 }
 371 
 372 pub fn parseObjectMetadata(allocator: Allocator, bytes: []const u8) model.Error!root.Object {
 373     var object = try parseObject(allocator, bytes);
 374     defer object.deinit(allocator);
 375 
 376     const sections = try allocator.alloc(root.ObjectSection, object.sections.len);
 377     errdefer allocator.free(sections);
 378     for (sections, object.sections) |*section, source| {
 379         section.* = .{
 380             .name = source.name,
 381             .size = source.size,
 382             .offset = source.offset,
 383             .alignment = source.alignment,
 384             .flags = source.flags,
 385             .relocation_count = source.relocation_count,
 386         };
 387     }
 388 
 389     const symbols = try allocator.alloc(root.ObjectSymbol, object.symbols.len);
 390     errdefer allocator.free(symbols);
 391     for (symbols, object.symbols) |*symbol, source| {
 392         symbol.* = .{
 393             .name = source.name,
 394             .section_index = source.section_number,
 395             .value = source.value,
 396             .kind = source.kind,
 397             .binding = source.storage_class,
 398             .external = source.storage_class == @backingInt(std.coff.StorageClass.EXTERNAL),
 399             .undefined = source.section_number == 0,
 400         };
 401     }
 402 
 403     return .{
 404         .target = object.target,
 405         .sections = sections,
 406         .symbols = symbols,
 407     };
 408 }
 409 
 410 fn collectTextContributions(
 411     allocator: Allocator,
 412     contributions: *std.ArrayListUnmanaged(TextContribution),
 413     input: model.Input,
 414     input_index: usize,
 415     sections: []const Section,
 416 ) model.Error!void {
 417     for (sections, 0..) |section, section_index| {
 418         if (!sectionHasImageData(section)) continue;
 419         try contributions.append(allocator, .{
 420             .input_index = input_index,
 421             .input_name = input.name,
 422             .section_index = section_index,
 423             .section_name = section.name,
 424             .bytes = try sectionData(input.bytes, section),
 425             .alignment = @max(section.alignment, 1),
 426         });
 427     }
 428 }
 429 
 430 fn applyRelocations(
 431     image: []u8,
 432     objects: []const LinkObject,
 433     contributions: []const TextContribution,
 434     definitions: *const std.StringHashMapUnmanaged(SymbolDefinition),
 435     options: model.LinkOptions,
 436     text_file_offset: u64,
 437     text_rva: u64,
 438 ) model.Error!void {
 439     const phase = trace.product(.relocation_application);
 440     defer phase.end();
 441     for (objects) |link_object| {
 442         for (link_object.object.relocations) |relocation| {
 443             if (relocation.kind == absolute_relocation_kind) continue;
 444             if (relocation.section_index >= link_object.object.sections.len) return error.InvalidObject;
 445             const section = link_object.object.sections[relocation.section_index];
 446             if (!sectionHasImageData(section)) continue;
 447             const source = contributionForSection(
 448                 contributions,
 449                 link_object.input_index,
 450                 relocation.section_index,
 451             ) orelse return error.InvalidObject;
 452             const symbol = symbolByRawIndex(link_object.object.symbols, relocation.symbol_table_index) orelse return error.InvalidObject;
 453             const target = try definitionForRelocationSymbol(
 454                 definitions,
 455                 link_object.input_name,
 456                 link_object.input_index,
 457                 link_object.object.sections,
 458                 symbol,
 459                 options,
 460             );
 461             const target_contribution = contributionForDefinition(contributions, target) orelse return error.InvalidObject;
 462             if (target.value > target_contribution.bytes.len) return error.InvalidRange;
 463             const target_rva = try symbolRva(text_rva, target_contribution, target.value);
 464 
 465             switch (@as(std.coff.IMAGE.REL.AMD64, @fromBackingInt(@intCast(relocation.kind)))) {
 466                 .ADDR64 => try applyAddr64(
 467                     image,
 468                     text_file_offset,
 469                     source,
 470                     relocation,
 471                     try checkedRelocationAddU64(options.image_base, target_rva),
 472                 ),
 473                 .ADDR32 => try applyAddr32(
 474                     image,
 475                     text_file_offset,
 476                     source,
 477                     relocation,
 478                     try checkedRelocationAddU64(options.image_base, target_rva),
 479                 ),
 480                 .ADDR32NB => try applyAddr32(
 481                     image,
 482                     text_file_offset,
 483                     source,
 484                     relocation,
 485                     target_rva,
 486                 ),
 487                 .REL32 => try applyRel32(
 488                     image,
 489                     text_file_offset,
 490                     text_rva,
 491                     source,
 492                     relocation,
 493                     target_rva,
 494                     4,
 495                 ),
 496                 .REL32_1 => try applyRel32(
 497                     image,
 498                     text_file_offset,
 499                     text_rva,
 500                     source,
 501                     relocation,
 502                     target_rva,
 503                     5,
 504                 ),
 505                 .REL32_2 => try applyRel32(
 506                     image,
 507                     text_file_offset,
 508                     text_rva,
 509                     source,
 510                     relocation,
 511                     target_rva,
 512                     6,
 513                 ),
 514                 .REL32_3 => try applyRel32(
 515                     image,
 516                     text_file_offset,
 517                     text_rva,
 518                     source,
 519                     relocation,
 520                     target_rva,
 521                     7,
 522                 ),
 523                 .REL32_4 => try applyRel32(
 524                     image,
 525                     text_file_offset,
 526                     text_rva,
 527                     source,
 528                     relocation,
 529                     target_rva,
 530                     8,
 531                 ),
 532                 .REL32_5 => try applyRel32(
 533                     image,
 534                     text_file_offset,
 535                     text_rva,
 536                     source,
 537                     relocation,
 538                     target_rva,
 539                     9,
 540                 ),
 541                 else => {
 542                     if (options.diagnostics) |diagnostics| {
 543                         diagnostics.recordUnsupportedRelocation(
 544                             link_object.input_name,
 545                             section.name,
 546                             symbol.name,
 547                             relocation.kind,
 548                         );
 549                     }
 550                     return error.UnsupportedRelocation;
 551                 },
 552             }
 553         }
 554     }
 555 }
 556 
 557 fn applyAddr64(
 558     image: []u8,
 559     text_file_offset: u64,
 560     source: TextContribution,
 561     relocation: Relocation,
 562     target_address: u64,
 563 ) model.Error!void {
 564     const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 8);
 565     const addend = std.mem.readInt(u64, image[patch_index..][0..8], .little);
 566     std.mem.writeInt(u64, image[patch_index..][0..8], try checkedRelocationAddU64(target_address, addend), .little);
 567 }
 568 
 569 fn applyAddr32(
 570     image: []u8,
 571     text_file_offset: u64,
 572     source: TextContribution,
 573     relocation: Relocation,
 574     target_address: u64,
 575 ) model.Error!void {
 576     const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 4);
 577     const addend: u64 = std.mem.readInt(u32, image[patch_index..][0..4], .little);
 578     const value = try checkedRelocationAddU64(target_address, addend);
 579     std.mem.writeInt(u32, image[patch_index..][0..4], try checkedU32FromRelocation(value), .little);
 580 }
 581 
 582 fn applyRel32(
 583     image: []u8,
 584     text_file_offset: u64,
 585     text_rva: u64,
 586     source: TextContribution,
 587     relocation: Relocation,
 588     target_rva: u64,
 589     base_distance: u64,
 590 ) model.Error!void {
 591     const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 4);
 592 
 593     const place_rva = try checkedAddU64(
 594         text_rva,
 595         try checkedAddU64(source.output_offset, relocation.virtual_address),
 596     );
 597     const addend: i64 = std.mem.readInt(i32, image[patch_index..][0..4], .little);
 598     const value = try checkedSubI64(
 599         try checkedAddI64(try checkedI64FromU64(target_rva), addend),
 600         try checkedI64FromU64(try checkedAddU64(place_rva, base_distance)),
 601     );
 602     std.mem.writeInt(i32, image[patch_index..][0..4], try checkedI32FromI64(value), .little);
 603 }
 604 
 605 fn relocationPatchIndex(
 606     image: []const u8,
 607     text_file_offset: u64,
 608     source: TextContribution,
 609     relocation: Relocation,
 610     width: usize,
 611 ) model.Error!usize {
 612     const patch_file_offset = try checkedAddU64(
 613         text_file_offset,
 614         try checkedAddU64(source.output_offset, relocation.virtual_address),
 615     );
 616     const patch_index = try checkedUsize(patch_file_offset);
 617     if (patch_index > image.len or width > image.len - patch_index) return error.InvalidRange;
 618     return patch_index;
 619 }
 620 
 621 fn symbolRva(text_rva: u64, target: TextContribution, target_value: u32) model.Error!u64 {
 622     return try checkedAddU64(
 623         text_rva,
 624         try checkedAddU64(target.output_offset, target_value),
 625     );
 626 }
 627 
 628 fn symbolByRawIndex(symbols: []const Symbol, raw_index: u32) ?Symbol {
 629     for (symbols) |symbol| {
 630         if (symbol.raw_index == raw_index) return symbol;
 631     }
 632     return null;
 633 }
 634 
 635 fn definitionForRelocationSymbol(
 636     definitions: *const std.StringHashMapUnmanaged(SymbolDefinition),
 637     input_name: []const u8,
 638     input_index: usize,
 639     sections: []const Section,
 640     symbol: Symbol,
 641     options: model.LinkOptions,
 642 ) model.Error!SymbolDefinition {
 643     if (symbol.section_number > 0) {
 644         const section_index: usize = @intCast(symbol.section_number - 1);
 645         if (section_index >= sections.len) return error.InvalidObject;
 646         return .{
 647             .input_index = input_index,
 648             .section_index = section_index,
 649             .value = symbol.value,
 650         };
 651     }
 652     if (symbol.section_number != 0) return error.InvalidObject;
 653     if (definitions.get(symbol.name)) |definition| return definition;
 654     if (options.diagnostics) |diagnostics| diagnostics.recordUndefinedSymbol(input_name, symbol.name);
 655     return error.UndefinedSymbol;
 656 }
 657 
 658 fn indexExternalDefinitions(
 659     allocator: Allocator,
 660     definitions: *std.StringHashMapUnmanaged(SymbolDefinition),
 661     input_index: usize,
 662     object: Object,
 663 ) model.Error!void {
 664     for (object.symbols) |symbol| {
 665         if (symbol.storage_class != @backingInt(std.coff.StorageClass.EXTERNAL)) continue;
 666         if (symbol.section_number <= 0) continue;
 667         const section_index: usize = @intCast(symbol.section_number - 1);
 668         if (section_index >= object.sections.len) return error.InvalidObject;
 669         if (!sectionHasImageData(object.sections[section_index])) continue;
 670         const gop = try definitions.getOrPut(allocator, symbol.name);
 671         if (gop.found_existing) return error.DuplicateSymbol;
 672         gop.value_ptr.* = .{
 673             .input_index = input_index,
 674             .section_index = section_index,
 675             .value = symbol.value,
 676         };
 677     }
 678 }
 679 
 680 fn contributionForSection(
 681     contributions: []const TextContribution,
 682     input_index: usize,
 683     section_index: usize,
 684 ) ?TextContribution {
 685     for (contributions) |contribution| {
 686         if (contribution.input_index != input_index) continue;
 687         if (contribution.section_index != section_index) continue;
 688         return contribution;
 689     }
 690     return null;
 691 }
 692 
 693 fn layoutTextContributions(contributions: []TextContribution) model.Error!u64 {
 694     var text_size: u64 = 0;
 695     for (contributions) |*contribution| {
 696         text_size = try alignForward(text_size, contribution.alignment);
 697         contribution.output_offset = text_size;
 698         text_size = try checkedAddU64(text_size, contribution.bytes.len);
 699     }
 700     return text_size;
 701 }
 702 
 703 fn contributionForDefinition(
 704     contributions: []const TextContribution,
 705     definition: SymbolDefinition,
 706 ) ?TextContribution {
 707     for (contributions) |contribution| {
 708         if (contribution.input_index != definition.input_index) continue;
 709         if (contribution.section_index != definition.section_index) continue;
 710         return contribution;
 711     }
 712     return null;
 713 }
 714 
 715 fn sectionHasImageData(section: Section) bool {
 716     if (section.size == 0) return false;
 717     if (section.flags & coff_discardable_flags != 0) return false;
 718     if (section.flags & coff_uninitialized_data_flags != 0) return false;
 719     const image_flags = coff_code_flags | coff_execute_flags;
 720     return section.flags & image_flags != 0;
 721 }
 722 
 723 fn sectionData(bytes: []const u8, section: Section) model.Error![]const u8 {
 724     return try range(bytes, try checkedUsize(section.offset), try checkedUsize(section.size));
 725 }
 726 
 727 fn writePeHeaders(
 728     image: []u8,
 729     options: model.LinkOptions,
 730     text_size: u64,
 731     text_raw_size: u64,
 732     headers_size: u64,
 733     entry_rva: u32,
 734     size_of_image: u64,
 735 ) model.Error!void {
 736     image[0] = 'M';
 737     image[1] = 'Z';
 738     writeU32(image, 0x3c, pe_offset);
 739     @memcpy(image[pe_offset..][0..pe_signature_size], "PE\x00\x00");
 740 
 741     const coff_offset = pe_offset + pe_signature_size;
 742     writeU16(image, coff_offset, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
 743     writeU16(image, coff_offset + 2, 1);
 744     writeU32(image, coff_offset + 16, pe32_plus_optional_header_size);
 745     writeU16(image, coff_offset + 18, coff_executable_flags);
 746 
 747     const optional_offset = coff_offset + header_size;
 748     writeU16(image, optional_offset, pe32_plus_magic);
 749     writeU32(image, optional_offset + 4, try checkedU32FromU64(text_raw_size));
 750     writeU32(image, optional_offset + 16, entry_rva);
 751     writeU32(image, optional_offset + 20, try checkedU32FromU64(options.page_size));
 752     writeU64(image, optional_offset + 24, options.image_base);
 753     writeU32(image, optional_offset + 32, try checkedU32FromU64(options.page_size));
 754     writeU32(image, optional_offset + 36, coff_file_alignment);
 755     writeU16(image, optional_offset + 40, 6);
 756     writeU16(image, optional_offset + 48, 6);
 757     writeU32(image, optional_offset + 56, try checkedU32FromU64(size_of_image));
 758     writeU32(image, optional_offset + 60, try checkedU32FromU64(headers_size));
 759     writeU16(image, optional_offset + 68, windows_cui_subsystem);
 760     writeU16(image, optional_offset + 70, nx_compat_dll_characteristic);
 761     writeU64(image, optional_offset + 72, 0x100000);
 762     writeU64(image, optional_offset + 80, 0x1000);
 763     writeU64(image, optional_offset + 88, 0x100000);
 764     writeU64(image, optional_offset + 96, 0x1000);
 765     writeU32(image, optional_offset + 108, pe_data_directory_count);
 766 
 767     const section_offset = optional_offset + pe32_plus_optional_header_size;
 768     writeName(image, section_offset, 8, ".text");
 769     writeU32(image, section_offset + 8, try checkedU32FromU64(text_size));
 770     writeU32(image, section_offset + 12, try checkedU32FromU64(options.page_size));
 771     writeU32(image, section_offset + 16, try checkedU32FromU64(text_raw_size));
 772     writeU32(image, section_offset + 20, try checkedU32FromU64(headers_size));
 773     writeU32(image, section_offset + 36, coff_text_section_flags);
 774 }
 775 
 776 fn alignForward(value: u64, alignment: u64) model.Error!u64 {
 777     if (alignment == 0) return error.InvalidAlignment;
 778     if (!std.math.isPowerOfTwo(alignment)) return error.InvalidAlignment;
 779     const mask = alignment - 1;
 780     return (try checkedAddU64(value, mask)) & ~mask;
 781 }
 782 
 783 fn targetFromMachine(machine: u16) model.Error!model.Target {
 784     return switch (@as(std.coff.IMAGE.FILE.MACHINE, @fromBackingInt(@intCast(machine)))) {
 785         .AMD64 => .{
 786             .object_format = .coff,
 787             .architecture = .x86_64,
 788             .endianness = .little,
 789             .pointer_width_bits = 64,
 790         },
 791         .ARM64 => .{
 792             .object_format = .coff,
 793             .architecture = .aarch64,
 794             .endianness = .little,
 795             .pointer_width_bits = 64,
 796         },
 797         else => error.UnsupportedArchitecture,
 798     };
 799 }
 800 
 801 fn coffStringTable(bytes: []const u8, symbol_table_offset: u32, symbol_count: u32) model.Error![]const u8 {
 802     if (symbol_table_offset == 0) return &.{};
 803     const symbol_bytes_len = try checkedMul(try checkedUsize(symbol_count), symbol_size);
 804     const string_table_offset = try checkedUsize(symbol_table_offset) + symbol_bytes_len;
 805     _ = try range(bytes, string_table_offset, 4);
 806     const string_table_size = try readU32(bytes, string_table_offset);
 807     if (string_table_size < 4) return error.InvalidStringTable;
 808     return try range(bytes, string_table_offset, try checkedUsize(string_table_size));
 809 }
 810 
 811 fn coffSymbolRange(bytes: []const u8, symbol_table_offset: u32, symbol_count: u32) model.Error![]const u8 {
 812     if (symbol_count == 0) return &.{};
 813     if (symbol_table_offset == 0) return error.InvalidObject;
 814     return try range(
 815         bytes,
 816         try checkedUsize(symbol_table_offset),
 817         try checkedMul(try checkedUsize(symbol_count), symbol_size),
 818     );
 819 }
 820 
 821 fn parseRelocations(
 822     allocator: Allocator,
 823     bytes: []const u8,
 824     architecture: model.Architecture,
 825     sections: []const Section,
 826     primary_symbol_indices: []const bool,
 827 ) model.Error![]Relocation {
 828     var relocation_count: usize = 0;
 829     for (sections) |section| {
 830         relocation_count = try checkedAdd(relocation_count, section.relocation_count);
 831     }
 832 
 833     const relocations = try allocator.alloc(Relocation, relocation_count);
 834     errdefer allocator.free(relocations);
 835 
 836     var cursor: usize = 0;
 837     for (sections, 0..) |section, section_index| {
 838         const relocation_range = try range(
 839             bytes,
 840             try checkedUsize(section.relocation_offset),
 841             try checkedMul(section.relocation_count, relocation_size),
 842         );
 843         var index: usize = 0;
 844         while (index < section.relocation_count) : (index += 1) {
 845             const offset = index * relocation_size;
 846             const virtual_address = std.mem.readInt(u32, relocation_range[offset..][0..4], .little);
 847             const symbol_table_index = std.mem.readInt(u32, relocation_range[offset + 4 ..][0..4], .little);
 848             const kind = std.mem.readInt(u16, relocation_range[offset + 8 ..][0..2], .little);
 849             if (kind != absolute_relocation_kind) {
 850                 try validateRelocationSpan(architecture, kind, virtual_address, section.size);
 851                 if (!isPrimarySymbolIndex(primary_symbol_indices, symbol_table_index)) {
 852                     return error.InvalidObject;
 853                 }
 854             }
 855             relocations[cursor] = .{
 856                 .section_index = section_index,
 857                 .virtual_address = virtual_address,
 858                 .symbol_table_index = symbol_table_index,
 859                 .kind = kind,
 860             };
 861             cursor += 1;
 862         }
 863     }
 864 
 865     return relocations;
 866 }
 867 
 868 fn validateRelocationSpan(
 869     architecture: model.Architecture,
 870     kind: u16,
 871     virtual_address: u32,
 872     section_size: u32,
 873 ) model.Error!void {
 874     if (virtual_address >= section_size) return error.InvalidObject;
 875     if (coffRelocationWidth(architecture, kind)) |width| {
 876         const offset: u64 = virtual_address;
 877         const size: u64 = section_size;
 878         if (width > size - offset) return error.InvalidObject;
 879     }
 880 }
 881 
 882 fn coffRelocationWidth(architecture: model.Architecture, kind: u16) ?u64 {
 883     return switch (architecture) {
 884         .x86_64 => coffAmd64RelocationWidth(kind),
 885         .aarch64 => coffArm64RelocationWidth(kind),
 886         else => null,
 887     };
 888 }
 889 
 890 fn coffAmd64RelocationWidth(kind: u16) ?u64 {
 891     return switch (@as(std.coff.IMAGE.REL.AMD64, @fromBackingInt(@intCast(kind)))) {
 892         .ADDR64 => 8,
 893         .SECTION => 2,
 894         .SECREL7 => 1,
 895         .ADDR32,
 896         .ADDR32NB,
 897         .REL32,
 898         .REL32_1,
 899         .REL32_2,
 900         .REL32_3,
 901         .REL32_4,
 902         .REL32_5,
 903         .SECREL,
 904         .TOKEN,
 905         .SREL32,
 906         .SSPAN32,
 907         => 4,
 908         else => null,
 909     };
 910 }
 911 
 912 fn coffArm64RelocationWidth(kind: u16) ?u64 {
 913     return switch (@as(std.coff.IMAGE.REL.ARM64, @fromBackingInt(@intCast(kind)))) {
 914         .SECTION => 2,
 915         .ADDR64 => 8,
 916         .ADDR32,
 917         .ADDR32NB,
 918         .BRANCH26,
 919         .PAGEBASE_REL21,
 920         .REL21,
 921         .PAGEOFFSET_12A,
 922         .PAGEOFFSET_12L,
 923         .SECREL,
 924         .SECREL_LOW12A,
 925         .SECREL_HIGH12A,
 926         .SECREL_LOW12L,
 927         .TOKEN,
 928         .BRANCH19,
 929         .BRANCH14,
 930         .REL32,
 931         => 4,
 932         else => null,
 933     };
 934 }
 935 
 936 fn isPrimarySymbolIndex(primary_symbol_indices: []const bool, symbol_table_index: u32) bool {
 937     const index = std.math.cast(usize, symbol_table_index) orelse return false;
 938     return index < primary_symbol_indices.len and primary_symbol_indices[index];
 939 }
 940 
 941 fn sectionName(raw_name: *const [8]u8, string_table: []const u8) model.Error![]const u8 {
 942     if (raw_name[0] == '/') {
 943         const end = std.mem.indexOfScalar(u8, raw_name, 0) orelse raw_name.len;
 944         const offset = std.fmt.parseInt(u32, raw_name[1..end], 10) catch return error.InvalidStringTable;
 945         return try stringFromTable(string_table, offset);
 946     }
 947     return paddedName(raw_name);
 948 }
 949 
 950 fn symbolName(raw_name: *const [8]u8, string_table: []const u8) model.Error![]const u8 {
 951     if (std.mem.eql(u8, raw_name[0..4], "\x00\x00\x00\x00")) {
 952         return try stringFromTable(string_table, std.mem.readInt(u32, raw_name[4..8], .little));
 953     }
 954     return paddedName(raw_name);
 955 }
 956 
 957 fn stringFromTable(table: []const u8, offset: u32) model.Error![]const u8 {
 958     const start = try checkedUsize(offset);
 959     if (start < 4 or start >= table.len) return error.InvalidStringTable;
 960     const end = std.mem.indexOfScalarPos(u8, table, start, 0) orelse return error.InvalidStringTable;
 961     return table[start..end];
 962 }
 963 
 964 fn paddedName(bytes: *const [8]u8) []const u8 {
 965     const end = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
 966     return bytes[0..end];
 967 }
 968 
 969 fn sectionAlignment(flags: u32) u16 {
 970     const encoded = @as(u4, @truncate((flags >> 20) & 0xf));
 971     if (encoded == 0) return 1;
 972     return @as(u16, 1) << (encoded - 1);
 973 }
 974 
 975 fn validateSymbolSectionNumber(section_number: i16, section_count: u16) model.Error!void {
 976     if (section_number == 0 or section_number == -1 or section_number == -2) return;
 977     if (section_number < 0) return error.InvalidObject;
 978     if (@as(u16, @intCast(section_number)) > section_count) return error.InvalidObject;
 979 }
 980 
 981 fn range(bytes: []const u8, offset: usize, len: usize) model.Error![]const u8 {
 982     if (offset > bytes.len) return error.InvalidRange;
 983     if (len > bytes.len - offset) return error.InvalidRange;
 984     return bytes[offset..][0..len];
 985 }
 986 
 987 fn readU16(bytes: []const u8, offset: usize) model.Error!u16 {
 988     return std.mem.readInt(u16, (try range(bytes, offset, 2))[0..2], .little);
 989 }
 990 
 991 fn readU32(bytes: []const u8, offset: usize) model.Error!u32 {
 992     return std.mem.readInt(u32, (try range(bytes, offset, 4))[0..4], .little);
 993 }
 994 
 995 fn checkedUsize(value: anytype) model.Error!usize {
 996     return std.math.cast(usize, value) orelse error.InvalidRange;
 997 }
 998 
 999 fn checkedMul(a: usize, b: usize) model.Error!usize {
1000     return std.math.mul(usize, a, b) catch error.InvalidRange;
1001 }
1002 
1003 fn checkedAdd(a: usize, b: usize) model.Error!usize {
1004     return std.math.add(usize, a, b) catch error.InvalidRange;
1005 }
1006 
1007 fn checkedAddU64(a: u64, b: u64) model.Error!u64 {
1008     return std.math.add(u64, a, b) catch error.InvalidRange;
1009 }
1010 
1011 fn checkedAddI64(a: i64, b: i64) model.Error!i64 {
1012     return std.math.add(i64, a, b) catch error.InvalidRange;
1013 }
1014 
1015 fn checkedSubI64(a: i64, b: i64) model.Error!i64 {
1016     return std.math.sub(i64, a, b) catch error.InvalidRange;
1017 }
1018 
1019 fn checkedU32FromU64(value: u64) model.Error!u32 {
1020     return std.math.cast(u32, value) orelse error.InvalidRange;
1021 }
1022 
1023 fn checkedRelocationAddU64(a: u64, b: u64) model.Error!u64 {
1024     return std.math.add(u64, a, b) catch error.RelocationOverflow;
1025 }
1026 
1027 fn checkedU32FromRelocation(value: u64) model.Error!u32 {
1028     return std.math.cast(u32, value) orelse error.RelocationOverflow;
1029 }
1030 
1031 fn checkedI64FromU64(value: u64) model.Error!i64 {
1032     return std.math.cast(i64, value) orelse error.InvalidRange;
1033 }
1034 
1035 fn checkedI32FromI64(value: i64) model.Error!i32 {
1036     return std.math.cast(i32, value) orelse error.RelocationOverflow;
1037 }
1038 
1039 fn writeU16(bytes: []u8, offset: usize, value: u16) void {
1040     std.mem.writeInt(u16, bytes[offset..][0..2], value, .little);
1041 }
1042 
1043 fn writeI16(bytes: []u8, offset: usize, value: i16) void {
1044     std.mem.writeInt(i16, bytes[offset..][0..2], value, .little);
1045 }
1046 
1047 fn writeU32(bytes: []u8, offset: usize, value: u32) void {
1048     std.mem.writeInt(u32, bytes[offset..][0..4], value, .little);
1049 }
1050 
1051 fn writeU64(bytes: []u8, offset: usize, value: u64) void {
1052     std.mem.writeInt(u64, bytes[offset..][0..8], value, .little);
1053 }
1054 
1055 fn writeName(bytes: []u8, offset: usize, comptime size: usize, value: []const u8) void {
1056     @memset(bytes[offset..][0..size], 0);
1057     @memcpy(bytes[offset..][0..value.len], value);
1058 }
1059 
1060 fn fixtureObject(allocator: Allocator) ![]u8 {
1061     const text = "\xe8\x00\x00\x00\x00\x90\x90\xc3";
1062     const section_offset = header_size;
1063     const text_offset = header_size + section_header_size;
1064     const relocation_offset = text_offset + text.len;
1065     const symbol_offset = relocation_offset + relocation_size;
1066     const string_name = "long_external_symbol";
1067     const string_table_size = 4 + string_name.len + 1;
1068     const string_table_offset = symbol_offset + 2 * symbol_size;
1069     const total_size = string_table_offset + string_table_size;
1070 
1071     const bytes = try allocator.alloc(u8, total_size);
1072     @memset(bytes, 0);
1073 
1074     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1075     writeU16(bytes, 2, 1);
1076     writeU32(bytes, 8, @intCast(symbol_offset));
1077     writeU32(bytes, 12, 2);
1078 
1079     writeName(bytes, section_offset, 8, ".text");
1080     writeU32(bytes, section_offset + 16, text.len);
1081     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1082     writeU32(bytes, section_offset + 24, @intCast(relocation_offset));
1083     writeU16(bytes, section_offset + 32, 1);
1084     writeU32(bytes, section_offset + 36, 0x60500020);
1085     @memcpy(bytes[text_offset..][0..text.len], text);
1086     writeU32(bytes, relocation_offset, 1);
1087     writeU32(bytes, relocation_offset + 4, 0);
1088     writeU16(bytes, relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.REL32));
1089 
1090     writeName(bytes, symbol_offset, 8, "_start");
1091     writeU32(bytes, symbol_offset + 8, 0);
1092     writeI16(bytes, symbol_offset + 12, 1);
1093     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1094 
1095     const long_symbol_offset = symbol_offset + symbol_size;
1096     writeU32(bytes, long_symbol_offset + 4, 4);
1097     writeU32(bytes, long_symbol_offset + 8, 1);
1098     writeI16(bytes, long_symbol_offset + 12, 1);
1099     bytes[long_symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1100 
1101     writeU32(bytes, string_table_offset, string_table_size);
1102     @memcpy(bytes[string_table_offset + 4 ..][0..string_name.len], string_name);
1103     return bytes;
1104 }
1105 
1106 fn fixtureRelocationOnlyObject(allocator: Allocator) ![]u8 {
1107     const section_offset = header_size;
1108     const text = "\x00\x00\x00\x00";
1109     const text_offset = header_size + section_header_size;
1110     const relocation_offset = text_offset + text.len;
1111     const symbol_offset = relocation_offset + relocation_size;
1112     const string_table_offset = symbol_offset + symbol_size;
1113     const total_size = string_table_offset + 4;
1114 
1115     const bytes = try allocator.alloc(u8, total_size);
1116     @memset(bytes, 0);
1117 
1118     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1119     writeU16(bytes, 2, 1);
1120     writeU32(bytes, 8, @intCast(symbol_offset));
1121     writeU32(bytes, 12, 1);
1122 
1123     writeName(bytes, section_offset, 8, ".text");
1124     writeU32(bytes, section_offset + 16, text.len);
1125     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1126     writeU32(bytes, section_offset + 24, @intCast(relocation_offset));
1127     writeU16(bytes, section_offset + 32, 1);
1128     writeU32(bytes, section_offset + 36, 0x60500020);
1129     @memcpy(bytes[text_offset..][0..text.len], text);
1130 
1131     writeU32(bytes, relocation_offset, 0);
1132     writeU32(bytes, relocation_offset + 4, 0);
1133     writeU16(bytes, relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.ADDR32NB));
1134 
1135     writeName(bytes, symbol_offset, 8, "_target");
1136     writeI16(bytes, symbol_offset + 12, 1);
1137     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1138     writeU32(bytes, string_table_offset, 4);
1139 
1140     return bytes;
1141 }
1142 
1143 fn fixtureExecutableObject(allocator: Allocator) ![]u8 {
1144     const section_offset = header_size;
1145     const text = "\x31\xc0\xc3";
1146     const text_offset = header_size + section_header_size;
1147     const symbol_offset = text_offset + text.len;
1148     const string_table_offset = symbol_offset + symbol_size;
1149     const total_size = string_table_offset + 4;
1150 
1151     const bytes = try allocator.alloc(u8, total_size);
1152     @memset(bytes, 0);
1153 
1154     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1155     writeU16(bytes, 2, 1);
1156     writeU32(bytes, 8, @intCast(symbol_offset));
1157     writeU32(bytes, 12, 1);
1158 
1159     writeName(bytes, section_offset, 8, ".text");
1160     writeU32(bytes, section_offset + 16, text.len);
1161     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1162     writeU32(bytes, section_offset + 36, 0x60500020);
1163     @memcpy(bytes[text_offset..][0..text.len], text);
1164 
1165     writeName(bytes, symbol_offset, 8, "_start");
1166     writeI16(bytes, symbol_offset + 12, 1);
1167     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1168     writeU32(bytes, string_table_offset, 4);
1169 
1170     return bytes;
1171 }
1172 
1173 fn fixtureRel32CallerObject(allocator: Allocator) ![]u8 {
1174     const section_offset = header_size;
1175     const text = "\xe8\x00\x00\x00\x00\xc3";
1176     const text_offset = header_size + section_header_size;
1177     const relocation_offset = text_offset + text.len;
1178     const symbol_offset = relocation_offset + relocation_size;
1179     const string_table_offset = symbol_offset + 2 * symbol_size;
1180     const total_size = string_table_offset + 4;
1181 
1182     const bytes = try allocator.alloc(u8, total_size);
1183     @memset(bytes, 0);
1184 
1185     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1186     writeU16(bytes, 2, 1);
1187     writeU32(bytes, 8, @intCast(symbol_offset));
1188     writeU32(bytes, 12, 2);
1189 
1190     writeName(bytes, section_offset, 8, ".text");
1191     writeU32(bytes, section_offset + 16, text.len);
1192     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1193     writeU32(bytes, section_offset + 24, @intCast(relocation_offset));
1194     writeU16(bytes, section_offset + 32, 1);
1195     writeU32(bytes, section_offset + 36, 0x60500020);
1196     @memcpy(bytes[text_offset..][0..text.len], text);
1197 
1198     writeU32(bytes, relocation_offset, 1);
1199     writeU32(bytes, relocation_offset + 4, 1);
1200     writeU16(bytes, relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.REL32));
1201 
1202     writeName(bytes, symbol_offset, 8, "_start");
1203     writeI16(bytes, symbol_offset + 12, 1);
1204     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1205 
1206     const callee_symbol_offset = symbol_offset + symbol_size;
1207     writeName(bytes, callee_symbol_offset, 8, "callee");
1208     writeI16(bytes, callee_symbol_offset + 12, 0);
1209     bytes[callee_symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1210 
1211     writeU32(bytes, string_table_offset, 4);
1212     return bytes;
1213 }
1214 
1215 fn fixtureRel32CalleeObject(allocator: Allocator) ![]u8 {
1216     const section_offset = header_size;
1217     const text = "\xc3";
1218     const text_offset = header_size + section_header_size;
1219     const symbol_offset = text_offset + text.len;
1220     const string_table_offset = symbol_offset + symbol_size;
1221     const total_size = string_table_offset + 4;
1222 
1223     const bytes = try allocator.alloc(u8, total_size);
1224     @memset(bytes, 0);
1225 
1226     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1227     writeU16(bytes, 2, 1);
1228     writeU32(bytes, 8, @intCast(symbol_offset));
1229     writeU32(bytes, 12, 1);
1230 
1231     writeName(bytes, section_offset, 8, ".text");
1232     writeU32(bytes, section_offset + 16, text.len);
1233     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1234     writeU32(bytes, section_offset + 36, 0x60500020);
1235     @memcpy(bytes[text_offset..][0..text.len], text);
1236 
1237     writeName(bytes, symbol_offset, 8, "callee");
1238     writeI16(bytes, symbol_offset + 12, 1);
1239     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1240     writeU32(bytes, string_table_offset, 4);
1241 
1242     return bytes;
1243 }
1244 
1245 fn fixtureAmd64AddressRelocationObject(allocator: Allocator) ![]u8 {
1246     const section_offset = header_size;
1247     const text_len = 17;
1248     const text_offset = header_size + section_header_size;
1249     const relocation_count = 3;
1250     const relocation_offset = text_offset + text_len;
1251     const symbol_offset = relocation_offset + relocation_count * relocation_size;
1252     const string_table_offset = symbol_offset + 2 * symbol_size;
1253     const total_size = string_table_offset + 4;
1254 
1255     const bytes = try allocator.alloc(u8, total_size);
1256     @memset(bytes, 0);
1257 
1258     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1259     writeU16(bytes, 2, 1);
1260     writeU32(bytes, 8, @intCast(symbol_offset));
1261     writeU32(bytes, 12, 2);
1262 
1263     writeName(bytes, section_offset, 8, ".text");
1264     writeU32(bytes, section_offset + 16, text_len);
1265     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1266     writeU32(bytes, section_offset + 24, @intCast(relocation_offset));
1267     writeU16(bytes, section_offset + 32, relocation_count);
1268     writeU32(bytes, section_offset + 36, 0x60500020);
1269     writeU64(bytes, text_offset, 5);
1270     writeU32(bytes, text_offset + 8, 7);
1271     writeU32(bytes, text_offset + 12, 11);
1272     bytes[text_offset + 16] = 0xc3;
1273 
1274     writeU32(bytes, relocation_offset, 0);
1275     writeU32(bytes, relocation_offset + 4, 1);
1276     writeU16(bytes, relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.ADDR64));
1277 
1278     const addr32_relocation_offset = relocation_offset + relocation_size;
1279     writeU32(bytes, addr32_relocation_offset, 8);
1280     writeU32(bytes, addr32_relocation_offset + 4, 1);
1281     writeU16(bytes, addr32_relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.ADDR32));
1282 
1283     const rva_relocation_offset = addr32_relocation_offset + relocation_size;
1284     writeU32(bytes, rva_relocation_offset, 12);
1285     writeU32(bytes, rva_relocation_offset + 4, 1);
1286     writeU16(bytes, rva_relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.ADDR32NB));
1287 
1288     writeName(bytes, symbol_offset, 8, "_start");
1289     writeI16(bytes, symbol_offset + 12, 1);
1290     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1291 
1292     const target_symbol_offset = symbol_offset + symbol_size;
1293     writeName(bytes, target_symbol_offset, 8, "target");
1294     writeU32(bytes, target_symbol_offset + 8, 16);
1295     writeI16(bytes, target_symbol_offset + 12, 1);
1296     bytes[target_symbol_offset + 16] = @backingInt(std.coff.StorageClass.STATIC);
1297     writeU32(bytes, string_table_offset, 4);
1298 
1299     return bytes;
1300 }
1301 
1302 fn fixtureRel32VariantsObject(allocator: Allocator) ![]u8 {
1303     const section_offset = header_size;
1304     const text_len = 33;
1305     const target_offset = 32;
1306     const text_offset = header_size + section_header_size;
1307     const relocation_count = 6;
1308     const relocation_offset = text_offset + text_len;
1309     const symbol_offset = relocation_offset + relocation_count * relocation_size;
1310     const string_table_offset = symbol_offset + 2 * symbol_size;
1311     const total_size = string_table_offset + 4;
1312 
1313     const bytes = try allocator.alloc(u8, total_size);
1314     @memset(bytes, 0);
1315 
1316     writeU16(bytes, 0, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64));
1317     writeU16(bytes, 2, 1);
1318     writeU32(bytes, 8, @intCast(symbol_offset));
1319     writeU32(bytes, 12, 2);
1320 
1321     writeName(bytes, section_offset, 8, ".text");
1322     writeU32(bytes, section_offset + 16, text_len);
1323     writeU32(bytes, section_offset + 20, @intCast(text_offset));
1324     writeU32(bytes, section_offset + 24, @intCast(relocation_offset));
1325     writeU16(bytes, section_offset + 32, relocation_count);
1326     writeU32(bytes, section_offset + 36, 0x60500020);
1327     bytes[text_offset + target_offset] = 0xc3;
1328 
1329     const kinds = [_]std.coff.IMAGE.REL.AMD64{
1330         .REL32,
1331         .REL32_1,
1332         .REL32_2,
1333         .REL32_3,
1334         .REL32_4,
1335         .REL32_5,
1336     };
1337     for (kinds, 0..) |kind, index| {
1338         const current_relocation_offset = relocation_offset + index * relocation_size;
1339         writeU32(bytes, current_relocation_offset, @intCast(index * 4));
1340         writeU32(bytes, current_relocation_offset + 4, 1);
1341         writeU16(bytes, current_relocation_offset + 8, @backingInt(kind));
1342     }
1343 
1344     writeName(bytes, symbol_offset, 8, "_start");
1345     writeI16(bytes, symbol_offset + 12, 1);
1346     bytes[symbol_offset + 16] = @backingInt(std.coff.StorageClass.EXTERNAL);
1347 
1348     const target_symbol_offset = symbol_offset + symbol_size;
1349     writeName(bytes, target_symbol_offset, 8, "target");
1350     writeU32(bytes, target_symbol_offset + 8, target_offset);
1351     writeI16(bytes, target_symbol_offset + 12, 1);
1352     bytes[target_symbol_offset + 16] = @backingInt(std.coff.StorageClass.STATIC);
1353     writeU32(bytes, string_table_offset, 4);
1354 
1355     return bytes;
1356 }
1357 
1358 test "COFF parser reads object sections and symbols" {
1359     const allocator = std.testing.allocator;
1360     const bytes = try fixtureObject(allocator);
1361     defer allocator.free(bytes);
1362 
1363     var object = try parseObject(allocator, bytes);
1364     defer object.deinit(allocator);
1365 
1366     try std.testing.expectEqual(model.ObjectFormat.coff, object.target.object_format);
1367     try std.testing.expectEqual(model.Architecture.x86_64, object.target.architecture);
1368     try std.testing.expectEqual(@as(usize, 1), object.sections.len);
1369     try std.testing.expectEqualStrings(".text", object.sections[0].name);
1370     try std.testing.expectEqual(@as(u32, 8), object.sections[0].size);
1371     try std.testing.expectEqual(@as(u16, 16), object.sections[0].alignment);
1372     try std.testing.expectEqual(@as(usize, 1), object.relocations.len);
1373     try std.testing.expectEqual(@as(usize, 0), object.relocations[0].section_index);
1374     try std.testing.expectEqual(@as(u32, 1), object.relocations[0].virtual_address);
1375     try std.testing.expectEqual(@as(u32, 0), object.relocations[0].symbol_table_index);
1376     try std.testing.expectEqual(@as(u16, @backingInt(std.coff.IMAGE.REL.AMD64.REL32)), object.relocations[0].kind);
1377     try std.testing.expectEqual(@as(usize, 2), object.symbols.len);
1378     try std.testing.expectEqualStrings("_start", object.symbols[0].name);
1379     try std.testing.expectEqual(@as(i16, 1), object.symbols[0].section_number);
1380     try std.testing.expectEqualStrings("long_external_symbol", object.symbols[1].name);
1381     try std.testing.expectEqual(@as(u32, 1), object.symbols[1].value);
1382 }
1383 
1384 test "COFF metadata parser projects sections and symbols" {
1385     const allocator = std.testing.allocator;
1386     const bytes = try fixtureObject(allocator);
1387     defer allocator.free(bytes);
1388 
1389     var object = try parseObjectMetadata(allocator, bytes);
1390     defer object.deinit(allocator);
1391 
1392     try std.testing.expectEqual(model.ObjectFormat.coff, object.target.object_format);
1393     try std.testing.expectEqual(@as(usize, 1), object.sections.len);
1394     try std.testing.expectEqualStrings(".text", object.sections[0].name);
1395     try std.testing.expectEqual(@as(u64, 8), object.sections[0].size);
1396     try std.testing.expectEqual(@as(usize, 2), object.symbols.len);
1397     try std.testing.expectEqualStrings("_start", object.symbols[0].name);
1398     try std.testing.expectEqual(@as(i32, 1), object.symbols[0].section_index);
1399     try std.testing.expect(object.symbols[0].external);
1400 }
1401 
1402 test "COFF linker emits a minimal PE executable" {
1403     const allocator = std.testing.allocator;
1404     const bytes = try fixtureExecutableObject(allocator);
1405     defer allocator.free(bytes);
1406 
1407     var linked = try root.link(
1408         allocator,
1409         &.{.{ .name = "start.obj", .bytes = bytes }},
1410         .{ .target = .windows_x86_64_coff },
1411     );
1412     defer linked.deinit(allocator);
1413 
1414     try std.testing.expectEqual(@as(u8, 'M'), linked.bytes[0]);
1415     try std.testing.expectEqual(@as(u8, 'Z'), linked.bytes[1]);
1416     const actual_pe_offset = try readU32(linked.bytes, 0x3c);
1417     try std.testing.expectEqual(@as(u32, pe_offset), actual_pe_offset);
1418     const pe_start: usize = @intCast(actual_pe_offset);
1419     try std.testing.expectEqualSlices(u8, "PE\x00\x00", linked.bytes[pe_start..][0..4]);
1420 
1421     const coff_offset = pe_start + pe_signature_size;
1422     try std.testing.expectEqual(@as(u16, @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64)), try readU16(linked.bytes, coff_offset));
1423     try std.testing.expectEqual(@as(u16, 1), try readU16(linked.bytes, coff_offset + 2));
1424     try std.testing.expectEqual(@as(u16, pe32_plus_optional_header_size), try readU16(linked.bytes, coff_offset + 16));
1425 
1426     const optional_offset = coff_offset + header_size;
1427     try std.testing.expectEqual(@as(u16, pe32_plus_magic), try readU16(linked.bytes, optional_offset));
1428     try std.testing.expectEqual(@as(u32, 0x1000), try readU32(linked.bytes, optional_offset + 16));
1429     try std.testing.expectEqual(@as(u64, 0x400000), std.mem.readInt(u64, linked.bytes[optional_offset + 24 ..][0..8], .little));
1430     try std.testing.expectEqual(@as(u32, 0x1000), try readU32(linked.bytes, optional_offset + 32));
1431     try std.testing.expectEqual(@as(u32, coff_file_alignment), try readU32(linked.bytes, optional_offset + 36));
1432 
1433     const section_offset = optional_offset + pe32_plus_optional_header_size;
1434     try std.testing.expectEqualSlices(u8, ".text", linked.bytes[section_offset..][0..5]);
1435     try std.testing.expectEqual(@as(u32, 3), try readU32(linked.bytes, section_offset + 8));
1436     try std.testing.expectEqual(@as(u32, 0x1000), try readU32(linked.bytes, section_offset + 12));
1437     try std.testing.expectEqual(@as(u32, coff_file_alignment), try readU32(linked.bytes, section_offset + 16));
1438     const raw_offset = try readU32(linked.bytes, section_offset + 20);
1439     try std.testing.expectEqual(@as(u32, coff_file_alignment), raw_offset);
1440     try std.testing.expectEqualSlices(u8, "\x31\xc0\xc3", linked.bytes[@as(usize, @intCast(raw_offset))..][0..3]);
1441 
1442     try std.testing.expectEqual(model.ObjectFormat.coff, linked.manifest.target.object_format);
1443     try std.testing.expectEqual(@as(usize, 1), linked.manifest.inputs.len);
1444     try std.testing.expectEqual(@as(usize, 1), linked.manifest.sections.len);
1445     try std.testing.expectEqual(@as(usize, 1), linked.manifest.contributions.len);
1446     try std.testing.expectEqualStrings(".text", linked.manifest.string(linked.manifest.sections[0].name_id));
1447     try std.testing.expectEqual(@as(u64, 0x401000), linked.manifest.sections[0].address);
1448     try std.testing.expectEqual(@as(u64, coff_file_alignment), linked.manifest.sections[0].file_offset);
1449 }
1450 
1451 test "COFF linker applies x86_64 REL32 relocations" {
1452     const allocator = std.testing.allocator;
1453     const caller = try fixtureRel32CallerObject(allocator);
1454     defer allocator.free(caller);
1455     const callee = try fixtureRel32CalleeObject(allocator);
1456     defer allocator.free(callee);
1457 
1458     var linked = try root.link(
1459         allocator,
1460         &.{
1461             .{ .name = "caller.obj", .bytes = caller },
1462             .{ .name = "callee.obj", .bytes = callee },
1463         },
1464         .{ .target = .windows_x86_64_coff },
1465     );
1466     defer linked.deinit(allocator);
1467 
1468     const text = linked.manifest.sections[0];
1469     const text_offset: usize = @intCast(text.file_offset);
1470     try std.testing.expectEqual(@as(u64, 17), text.size);
1471     try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_offset + 1 ..][0..4], .little));
1472     try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_offset + 5]);
1473     try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_offset + 16]);
1474     try std.testing.expectEqual(@as(usize, 2), linked.manifest.contributions.len);
1475     try std.testing.expectEqualStrings("caller.obj", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1476     try std.testing.expectEqualStrings("callee.obj", linked.manifest.string(linked.manifest.contributions[1].input_name_id));
1477 }
1478 
1479 test "COFF linker applies x86_64 absolute and RVA relocations" {
1480     const allocator = std.testing.allocator;
1481     const bytes = try fixtureAmd64AddressRelocationObject(allocator);
1482     defer allocator.free(bytes);
1483 
1484     var linked = try root.link(
1485         allocator,
1486         &.{.{ .name = "addresses.obj", .bytes = bytes }},
1487         .{ .target = .windows_x86_64_coff },
1488     );
1489     defer linked.deinit(allocator);
1490 
1491     const text = linked.manifest.sections[0];
1492     const text_offset: usize = @intCast(text.file_offset);
1493     try std.testing.expectEqual(@as(u64, 17), text.size);
1494     try std.testing.expectEqual(@as(u64, 0x401015), std.mem.readInt(u64, linked.bytes[text_offset..][0..8], .little));
1495     try std.testing.expectEqual(@as(u32, 0x401017), try readU32(linked.bytes, text_offset + 8));
1496     try std.testing.expectEqual(@as(u32, 0x101b), try readU32(linked.bytes, text_offset + 12));
1497     try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_offset + 16]);
1498 }
1499 
1500 test "COFF linker applies x86_64 REL32 variant relocations" {
1501     const allocator = std.testing.allocator;
1502     const bytes = try fixtureRel32VariantsObject(allocator);
1503     defer allocator.free(bytes);
1504 
1505     var linked = try root.link(
1506         allocator,
1507         &.{.{ .name = "rel32-variants.obj", .bytes = bytes }},
1508         .{ .target = .windows_x86_64_coff },
1509     );
1510     defer linked.deinit(allocator);
1511 
1512     const text = linked.manifest.sections[0];
1513     const text_offset: usize = @intCast(text.file_offset);
1514     const expected = [_]i32{ 28, 23, 18, 13, 8, 3 };
1515     try std.testing.expectEqual(@as(u64, 33), text.size);
1516     for (expected, 0..) |value, index| {
1517         try std.testing.expectEqual(value, std.mem.readInt(i32, linked.bytes[text_offset + index * 4 ..][0..4], .little));
1518     }
1519     try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_offset + 32]);
1520 }
1521 
1522 test "COFF linker records unsupported relocation diagnostics" {
1523     const allocator = std.testing.allocator;
1524     const bytes = try fixtureRelocationOnlyObject(allocator);
1525     defer allocator.free(bytes);
1526     const relocation_offset = header_size + section_header_size + 4;
1527     writeU16(bytes, relocation_offset + 8, @backingInt(std.coff.IMAGE.REL.AMD64.TOKEN));
1528 
1529     var diagnostics: model.Diagnostics = .{};
1530     try std.testing.expectError(
1531         error.UnsupportedRelocation,
1532         root.link(
1533             allocator,
1534             &.{.{ .name = "reloc.obj", .bytes = bytes }},
1535             .{
1536                 .target = .windows_x86_64_coff,
1537                 .entry_symbol = "_target",
1538                 .diagnostics = &diagnostics,
1539             },
1540         ),
1541     );
1542 
1543     const failure = diagnostics.linkFailure(error.UnsupportedRelocation);
1544     const relocation = switch (failure) {
1545         .unsupported_relocation => |relocation| relocation,
1546         else => return error.ExpectedUnsupportedRelocationDiagnostic,
1547     };
1548     try std.testing.expectEqual(@as(u32, 13), relocation.relocation_type);
1549     try std.testing.expectEqualStrings("reloc.obj", relocation.input_name);
1550     try std.testing.expectEqualStrings(".text", relocation.section_name);
1551     try std.testing.expectEqualStrings("_target", relocation.symbol_name);
1552 }
1553 
1554 test "COFF parser rejects truncated section data" {
1555     const allocator = std.testing.allocator;
1556     const bytes = try fixtureObject(allocator);
1557     defer allocator.free(bytes);
1558 
1559     try std.testing.expectError(error.InvalidRange, parseObject(allocator, bytes[0 .. header_size + section_header_size + 1]));
1560 }
1561 
1562 test "COFF parser accepts packed relocation entries" {
1563     const allocator = std.testing.allocator;
1564     const bytes = try fixtureRelocationOnlyObject(allocator);
1565     defer allocator.free(bytes);
1566 
1567     var object = try parseObject(allocator, bytes);
1568     defer object.deinit(allocator);
1569 
1570     try std.testing.expectEqual(@as(usize, 1), object.relocations.len);
1571     try std.testing.expectEqual(@as(u32, 0), object.relocations[0].virtual_address);
1572     try std.testing.expectEqual(@as(u16, @backingInt(std.coff.IMAGE.REL.AMD64.ADDR32NB)), object.relocations[0].kind);
1573 }
1574 
1575 test "COFF parser rejects invalid relocation symbol references" {
1576     const allocator = std.testing.allocator;
1577     const bytes = try fixtureRelocationOnlyObject(allocator);
1578     defer allocator.free(bytes);
1579 
1580     const relocation_offset = header_size + section_header_size + 4;
1581     writeU32(bytes, relocation_offset + 4, 1);
1582 
1583     try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
1584 }
1585 
1586 test "COFF parser rejects relocations outside section data" {
1587     const allocator = std.testing.allocator;
1588     const bytes = try fixtureRelocationOnlyObject(allocator);
1589     defer allocator.free(bytes);
1590 
1591     const relocation_offset = header_size + section_header_size + 4;
1592     writeU32(bytes, relocation_offset, 4);
1593 
1594     try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
1595 }
1596 
1597 test "COFF parser rejects relocations that extend past section data" {
1598     const allocator = std.testing.allocator;
1599     const bytes = try fixtureObject(allocator);
1600     defer allocator.free(bytes);
1601 
1602     const relocation_offset = header_size + section_header_size + 8;
1603     writeU32(bytes, relocation_offset, 5);
1604 
1605     try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
1606 }
1607 
1608 test "COFF parser rejects relocation targets inside auxiliary symbol records" {
1609     const allocator = std.testing.allocator;
1610     const bytes = try fixtureObject(allocator);
1611     defer allocator.free(bytes);
1612 
1613     const relocation_offset = header_size + section_header_size + 8;
1614     const symbol_offset = relocation_offset + relocation_size;
1615     writeU32(bytes, relocation_offset + 4, 1);
1616     bytes[symbol_offset + 17] = 1;
1617 
1618     try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
1619 }
1620 
1621 test "COFF parser rejects invalid symbol section references" {
1622     const allocator = std.testing.allocator;
1623     const bytes = try fixtureObject(allocator);
1624     defer allocator.free(bytes);
1625 
1626     const symbol_offset = header_size + section_header_size + 8 + relocation_size;
1627     writeI16(bytes, symbol_offset + 12, 2);
1628 
1629     try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
1630 }