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 }