1const Coff = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.cpu.arch.endian();
5
6const std = @import("std");
7const Io = std.Io;
8const assert = std.debug.assert;
9const log = std.log.scoped(.link);
10const Crc32 = std.hash.crc.@"CRC-32/JAMCRC";
11
12const codegen = @import("../codegen.zig");
13const Compilation = @import("../Compilation.zig");
14const InternPool = @import("../InternPool.zig");
15const link = @import("../link.zig");
16const MappedFile = link.MappedFile;
17const target_util = @import("../target.zig");
18const Type = @import("../Type.zig");
19const Value = @import("../Value.zig");
20const Zcu = @import("../Zcu.zig");
21const ModuleDefinition = @import("../libs/mingw/def.zig").ModuleDefinition;
22const implib = @import("../libs/mingw/implib.zig");
23const Path = std.Build.Cache.Path;
24const Alignment = MappedFile.Alignment;
25
26base: link.File,
27options: link.File.OpenOptions,
28mf: MappedFile,
29nodes: std.MultiArrayList(Node),
30members: std.ArrayList(Member),
31pending_members: std.array_hash_map.Auto(Member.Index, void),
32lib_string_table: std.ArrayList(String),
33lib_string_len: u32,
34long_names_table: LongNamesTable,
35import_table: ImportTable,
36export_table: ExportTable,
37symbol_table: SymbolTable,
38inputs: std.array_hash_map.Custom(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false),
39input_archives: std.ArrayList(InputArchive),
40input_archive_members: std.ArrayList(InputArchive.Member),
41input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),
42input_archive_symbol_indices: std.array_hash_map.Auto(String, InputArchive.SearchList),
43pending_input: ?InputArchive.Member.Index,
44pending_default_libs: std.ArrayList(struct {
45 path: []const u8,
46 ioi: InputObject.Index,
47}),
48alternate_names: std.array_hash_map.Auto(String, String),
49input_objects: std.ArrayList(InputObject),
50input_symbols: std.ArrayList(struct { si: Symbol.Index, name: String }),
51input_sections: std.ArrayList(Node.InputSection),
52input_section_pending_index: u32,
53inputs_complete: bool,
54exports_complete: bool,
55pending_special_symbol: SpecialSymbol,
56strings: std.HashMapUnmanaged(
57 u32,
58 void,
59 std.hash_map.StringIndexContext,
60 std.hash_map.default_max_load_percentage,
61),
62string_bytes: std.ArrayList(u8),
63section_table: std.array_hash_map.Auto(String, Section),
64pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index),
65object_section_table: std.array_hash_map.Auto(String, Symbol.Index),
66section_merges: std.array_hash_map.Auto(String, String),
67section_merge_pending_index: u32,
68symbols: std.ArrayList(Symbol),
69globals: std.array_hash_map.Auto(String, Global),
70global_pending_index: u32,
71navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index),
72uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index),
73lazy: std.EnumArray(link.File.LazySymbol.Kind, struct {
74 map: std.array_hash_map.Auto(InternPool.Index, Symbol.Index),
75 pending_index: u32,
76}),
77pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct {
78 alignment: InternPool.Alignment,
79}),
80relocs: std.ArrayList(Reloc),
81first_free_reloc: Reloc.Index,
82last_free_reloc: Reloc.Index,
83const_prog_node: std.Progress.Node,
84synth_prog_node: std.Progress.Node,
85symbol_prog_node: std.Progress.Node,
86member_prog_node: std.Progress.Node,
87input_prog_node: std.Progress.Node,
88
89pub const default_file_alignment: u16 = 0x200;
90pub const default_size_of_stack_reserve: u32 = 0x1000000;
91pub const default_size_of_stack_commit: u32 = 0x1000;
92pub const default_size_of_heap_reserve: u32 = 0x100000;
93pub const default_size_of_heap_commit: u32 = 0x1000;
94
95pub const imp_prefix = "__imp_";
96
97const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len;
98
99const Error = link.Error || error{MappedFileIo};
100const LoadInputError = Error ||
101 Io.File.SeekError ||
102 Io.File.Reader.SizeError ||
103 Io.Reader.Error ||
104 MappedFile.Error;
105
106/// This is the start of a Portable Executable (PE) file.
107/// It starts with a MS-DOS header followed by a MS-DOS stub program.
108/// This data does not change so we include it as follows in all binaries.
109///
110/// In this context,
111/// A "paragraph" is 16 bytes.
112/// A "page" is 512 bytes.
113/// A "long" is 4 bytes.
114/// A "word" is 2 bytes.
115pub const msdos_stub: [120]u8 = .{
116 'M', 'Z', // Magic number. Stands for Mark Zbikowski (designer of the MS-DOS executable format).
117 0x78, 0x00, // Number of bytes in the last page. This matches the size of this entire MS-DOS stub.
118 0x01, 0x00, // Number of pages.
119 0x00, 0x00, // Number of entries in the relocation table.
120 0x04, 0x00, // The number of paragraphs taken up by the header. 4 * 16 = 64, which matches the header size (all bytes before the MS-DOS stub program).
121 0x00, 0x00, // The number of paragraphs required by the program.
122 0x00, 0x00, // The number of paragraphs requested by the program.
123 0x00, 0x00, // Initial value for SS (relocatable segment address).
124 0x00, 0x00, // Initial value for SP.
125 0x00, 0x00, // Checksum.
126 0x00, 0x00, // Initial value for IP.
127 0x00, 0x00, // Initial value for CS (relocatable segment address).
128 0x40, 0x00, // Absolute offset to relocation table. 64 matches the header size (all bytes before the MS-DOS stub program).
129 0x00, 0x00, // Overlay number. Zero means this is the main executable.
130}
131 // Reserved words.
132 ++ .{
133 0x00, 0x00,
134 0x00, 0x00,
135 0x00, 0x00,
136 0x00, 0x00,
137 }
138 // OEM-related fields.
139 ++ .{
140 0x00, 0x00, // OEM identifier.
141 0x00, 0x00, // OEM information.
142 }
143 // Reserved words.
144 ++ .{
145 0x00, 0x00,
146 0x00, 0x00,
147 0x00, 0x00,
148 0x00, 0x00,
149 0x00, 0x00,
150 0x00, 0x00,
151 0x00, 0x00,
152 0x00, 0x00,
153 0x00, 0x00,
154 0x00, 0x00,
155 }
156 // Address of the PE header (a long). This matches the size of this entire MS-DOS stub, so that's the address of what's after this MS-DOS stub.
157 ++ .{ 0x78, 0x00, 0x00, 0x00 }
158 // What follows is a 16-bit x86 MS-DOS program of 7 instructions that prints the bytes after these instructions and then exits.
159 ++ .{
160 // Set the value of the data segment to the same value as the code segment.
161 0x0e, // push cs
162 0x1f, // pop ds
163 // Set the DX register to the address of the message.
164 // If you count all bytes of these 7 instructions you get 14, so that's the address of what's after these instructions.
165 0xba, 14, 0x00, // mov dx, 14
166 // Set AH to the system call code for printing a message.
167 0xb4, 0x09, // mov ah, 0x09
168 // Perform the system call to print the message.
169 0xcd, 0x21, // int 0x21
170 // Set AH to 0x4c which is the system call code for exiting, and set AL to 0x01 which is the exit code.
171 0xb8, 0x01, 0x4c, // mov ax, 0x4c01
172 // Peform the system call to exit the program with exit code 1.
173 0xcd, 0x21, // int 0x21
174 }
175 // Message to print.
176 ++ "This program cannot be run in DOS mode.".*
177 // Message terminators.
178 ++ .{
179 '$', // We do not pass a length to the print system call; the string is terminated by this character.
180 0x00, 0x00, // Terminating zero bytes.
181 };
182
183pub const Node = union(enum) {
184 file,
185 header,
186 /// Images and archives only.
187 signature,
188 /// Archives only.
189 archive_member_header: Member.Index,
190 archive_member: Member.Index,
191
192 coff_header,
193
194 /// Image only
195 optional_header,
196 data_directories,
197
198 section_table,
199
200 /// Archives and objects only
201 symbol_table,
202 string_table,
203 relocation_table: Symbol.SectionNumber,
204 relocation_table_entry: Reloc.Index,
205
206 image_section: Symbol.Index,
207
208 /// Images only
209 import_directory_table,
210 import_lookup_table: ImportTable.Index,
211 import_address_table: ImportTable.Index,
212 import_hint_name_table: ImportTable.Index,
213
214 /// Images only
215 export_directory_table,
216 export_address_table,
217 export_name_pointer_table,
218 export_ordinal_table,
219 export_name_table,
220
221 pseudo_section: PseudoSectionMapIndex,
222 object_section: ObjectSectionMapIndex,
223 input_section: InputSection.Index,
224 import_thunk: GlobalMapIndex,
225 nav: NavMapIndex,
226 uav: UavMapIndex,
227 lazy_code: LazyMapRef.Index(.code),
228 lazy_const_data: LazyMapRef.Index(.const_data),
229 builtin: Symbol.Index,
230
231 /// Takes the place of a known node index when that node is not present in the output
232 placeholder,
233
234 pub const PseudoSectionMapIndex = enum(u32) {
235 _,
236
237 pub fn name(psmi: PseudoSectionMapIndex, coff: *const Coff) String {
238 return coff.pseudo_section_table.keys()[@backingInt(psmi)];
239 }
240
241 pub fn symbol(psmi: PseudoSectionMapIndex, coff: *const Coff) Symbol.Index {
242 return coff.pseudo_section_table.values()[@backingInt(psmi)];
243 }
244 };
245
246 pub const ObjectSectionMapIndex = enum(u32) {
247 _,
248
249 pub fn name(osmi: ObjectSectionMapIndex, coff: *const Coff) String {
250 return coff.object_section_table.keys()[@backingInt(osmi)];
251 }
252
253 pub fn symbol(osmi: ObjectSectionMapIndex, coff: *const Coff) Symbol.Index {
254 return coff.object_section_table.values()[@backingInt(osmi)];
255 }
256 };
257
258 pub const GlobalMapIndex = enum(u32) {
259 none,
260 _,
261
262 pub fn wrap(i: ?u32) GlobalMapIndex {
263 return @fromBackingInt(@intCast((i orelse return .none) + 1));
264 }
265
266 pub fn unwrap(gmi: GlobalMapIndex) ?u32 {
267 return switch (gmi) {
268 .none => null,
269 _ => @backingInt(gmi) - 1,
270 };
271 }
272
273 pub fn name(gmi: GlobalMapIndex, coff: *const Coff) String {
274 return coff.globals.keys()[gmi.unwrap().?];
275 }
276
277 pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index {
278 return coff.globals.values()[gmi.unwrap().?].si;
279 }
280
281 pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional {
282 return coff.globals.values()[gmi.unwrap().?].lib_name;
283 }
284 };
285
286 pub const NavMapIndex = enum(u32) {
287 _,
288
289 pub fn navIndex(nmi: NavMapIndex, coff: *const Coff) InternPool.Nav.Index {
290 return coff.navs.keys()[@backingInt(nmi)];
291 }
292
293 pub fn symbol(nmi: NavMapIndex, coff: *const Coff) Symbol.Index {
294 return coff.navs.values()[@backingInt(nmi)];
295 }
296 };
297
298 pub const UavMapIndex = enum(u32) {
299 _,
300
301 pub fn uavValue(umi: UavMapIndex, coff: *const Coff) InternPool.Index {
302 return coff.uavs.keys()[@backingInt(umi)];
303 }
304
305 pub fn symbol(umi: UavMapIndex, coff: *const Coff) Symbol.Index {
306 return coff.uavs.values()[@backingInt(umi)];
307 }
308 };
309
310 const InputSection = struct {
311 ioi: InputObject.Index,
312 si: Symbol.Index,
313 comdat_si: Symbol.Index,
314 file_location: MappedFile.Node.FileLocation,
315 first_li: Node.InputSection.LocalIndex,
316 crc: u32,
317
318 pub const Index = enum(u32) {
319 _,
320
321 pub fn inputSection(isi: Index, coff: *const Coff) *InputSection {
322 return &coff.input_sections.items[@backingInt(isi)];
323 }
324
325 pub fn input(isi: Index, coff: *const Coff) InputObject.Index {
326 return coff.input_sections.items[@backingInt(isi)].ioi;
327 }
328
329 pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation {
330 return coff.input_sections.items[@backingInt(isi)].file_location;
331 }
332
333 pub fn symbol(isi: Index, coff: *const Coff) Symbol.Index {
334 return coff.input_sections.items[@backingInt(isi)].si;
335 }
336
337 pub fn firstSymbol(isi: Index, coff: *const Coff) LocalIndex {
338 return coff.input_sections.items[@backingInt(isi)].first_li;
339 }
340 };
341
342 const LocalIndex = enum(u32) {
343 _,
344
345 pub fn name(isli: LocalIndex, coff: *const Coff) String {
346 return coff.input_symbols.items[@backingInt(isli)].name;
347 }
348 };
349 };
350
351 pub const LazyMapRef = struct {
352 kind: link.File.LazySymbol.Kind,
353 index: u32,
354
355 pub fn Index(comptime kind: link.File.LazySymbol.Kind) type {
356 return enum(u32) {
357 _,
358
359 pub fn ref(lmi: @This()) LazyMapRef {
360 return .{ .kind = kind, .index = @backingInt(lmi) };
361 }
362
363 pub fn lazySymbol(lmi: @This(), coff: *const Coff) link.File.LazySymbol {
364 return lmi.ref().lazySymbol(coff);
365 }
366
367 pub fn symbol(lmi: @This(), coff: *const Coff) Symbol.Index {
368 return lmi.ref().symbol(coff);
369 }
370 };
371 }
372
373 pub fn lazySymbol(lmr: LazyMapRef, coff: *const Coff) link.File.LazySymbol {
374 return .{ .kind = lmr.kind, .ty = coff.lazy.getPtrConst(lmr.kind).map.keys()[lmr.index] };
375 }
376
377 pub fn symbol(lmr: LazyMapRef, coff: *const Coff) Symbol.Index {
378 return coff.lazy.getPtrConst(lmr.kind).map.values()[lmr.index];
379 }
380 };
381
382 pub const Tag = @typeInfo(Node).@"union".tag_type.?;
383
384 const known_count = @typeInfo(@TypeOf(known)).@"struct".field_names.len;
385 const known = known: {
386 const Known = enum {
387 file,
388 header,
389 signature,
390 first_linker_member_header,
391 first_linker_member,
392 second_linker_member_header,
393 second_linker_member,
394 longnames_member_header,
395 longnames_member,
396 zcu_member_header,
397 zcu_member,
398 coff_header,
399 optional_header,
400 data_directories,
401 section_table,
402 };
403 var mut_known: std.enums.EnumFieldStruct(Known, MappedFile.Node.Index, null) = undefined;
404 const info = @typeInfo(Known).@"enum";
405 for (info.field_names, info.field_values) |field_name, field_value|
406 @field(mut_known, field_name) = @fromBackingInt(@intCast(field_value));
407 break :known mut_known;
408 };
409
410 comptime {
411 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Node) == 8);
412 }
413};
414
415pub const InputArchive = struct {
416 path: std.Build.Cache.Path,
417
418 const Index = enum(u32) {
419 _,
420
421 pub fn path(iai: InputArchive.Index, coff: *Coff) std.Build.Cache.Path {
422 return coff.input_archives.items[@backingInt(iai)].path;
423 }
424 };
425
426 pub const Member = struct {
427 iai: InputArchive.Index,
428 name: String,
429 content: union(enum) {
430 // This range includes the member header
431 object: MappedFile.Node.FileLocation,
432 import: struct {
433 symbol_name: String,
434 lib_name: String,
435 // Either ordinal or hint, depending on value of name_type
436 import_ordinal_hint: u16,
437 type: std.coff.ImportType,
438 name_type: std.coff.ImportNameType,
439 },
440 },
441 flags: packed struct {
442 // Set if an attempt was made to load this member
443 is_loaded: bool,
444 },
445
446 const Index = enum(u32) {
447 _,
448
449 pub fn member(iami: InputArchive.Member.Index, coff: *Coff) *InputArchive.Member {
450 return &coff.input_archive_members.items[@backingInt(iami)];
451 }
452 };
453
454 pub const Symbol = struct {
455 iami: InputArchive.Member.Index,
456 // Set to its own index to indicate its the last in the list
457 next: InputArchive.Member.Symbol.Index,
458
459 const Index = enum(u32) {
460 _,
461 };
462 };
463 };
464
465 pub const SearchList = struct {
466 first: InputArchive.Member.Symbol.Index,
467 last: InputArchive.Member.Symbol.Index,
468 };
469};
470
471pub const InputObject = struct {
472 path: std.Build.Cache.Path,
473 member_name: ?[]const u8,
474 source_name: String.Optional,
475
476 pub const Index = enum(u32) {
477 _,
478
479 pub fn path(ioi: Index, coff: *const Coff) std.Build.Cache.Path {
480 return coff.input_objects.items[@backingInt(ioi)].path;
481 }
482
483 pub fn memberName(ioi: Index, coff: *const Coff) ?[]const u8 {
484 return coff.input_objects.items[@backingInt(ioi)].member_name;
485 }
486 };
487};
488
489pub const Member = struct {
490 kind: std.coff.ArchiveMemberHeader.Kind,
491 header_ni: MappedFile.Node.Index,
492 content_ni: MappedFile.Node.Index,
493 first_linker_indices: std.array_hash_map.Auto(struct {
494 mi: Member.Index,
495 name: String,
496 }, FirstLinkerIndex),
497
498 pub const Index = enum(u16) {
499 first,
500 second,
501 longnames,
502 _,
503
504 const known_count = @typeInfo(Index).@"enum".field_names.len;
505
506 pub fn get(member_index: Member.Index, coff: *Coff) *Member {
507 return &coff.members.items[@backingInt(member_index)];
508 }
509 };
510
511 pub const FirstLinkerIndex = enum(u32) {
512 _,
513 };
514
515 pub fn headerPtr(member: *Member, coff: *Coff) *std.coff.ArchiveMemberHeader {
516 return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf)));
517 }
518
519 /// Sets `name` as the name field of this member's header, either directly (if it's short enough),
520 /// or by creating an entry in the longnames member and storing a reference to that entry.
521 pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void {
522 const max_name_len = @typeInfo(@FieldType(std.coff.ArchiveMemberHeader, "name")).array.len;
523 const opt_name_offset = if (name.len >= max_name_len) offset: {
524 const gpa = coff.base.comp.gpa;
525 const entries_ctx = LongNamesTable.Adapter{ .coff = coff };
526 const gop = try coff.long_names_table.entries.getOrPutAdapted(
527 gpa,
528 name,
529 entries_ctx,
530 );
531
532 if (!gop.found_existing) {
533 errdefer _ = coff.export_table.entries.pop();
534
535 _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf);
536 const new_size = Alignment.@"4".forward(old_size + name.len + 1);
537 assert(new_size < comptime try std.math.powi(u64, 10, max_name_len - 1));
538
539 try Node.known.longnames_member.resizeLeaf(gpa, &coff.mf, new_size);
540 const name_table_slice = Node.known.longnames_member.slice(&coff.mf);
541 const name_slice = name_table_slice[@intCast(old_size)..][0 .. name.len + 1];
542 @memcpy(name_slice[0..name.len], name);
543 name_slice[name.len] = 0;
544
545 gop.value_ptr.* = .{
546 .offset = old_size,
547 .len = name.len,
548 };
549 }
550
551 break :offset gop.value_ptr.offset;
552 } else null;
553
554 const header = member.headerPtr(coff);
555 if (opt_name_offset) |name_offset| {
556 header.name[0] = '/';
557 storeHeaderDecimalStr(header.name[1..], name_offset);
558 } else {
559 @memcpy(header.name[0..name.len], name);
560 header.name[name.len] = '/';
561 const padding = max_name_len - name.len - 1;
562 @memset(header.name[max_name_len - padding ..], ' ');
563 }
564
565 storeHeaderDecimalStr(&header.date, timestamp);
566
567 // Matching the Microsoft behaviour of emitting blanks for these fields
568 header.user_id = @splat(' ');
569 header.group_id = @splat(' ');
570
571 // file_mode is actually octal, but we only ever write 0 to it
572 storeHeaderDecimalStr(&header.file_mode, 0);
573 if (!member.content_ni.hasResized(&coff.mf))
574 storeHeaderDecimalStr(
575 &header.size,
576 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
577 );
578
579 @memcpy(&header.end_of_header, std.coff.archive_end_of_header);
580 }
581
582 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
583 const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
584 assert(array_info.child == u8);
585 assert(value < comptime try std.math.powi(u64, 10, array_info.len));
586 _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
587 .width = array_info.len,
588 .alignment = .left,
589 .fill = ' ',
590 });
591 }
592
593 pub fn loadHeaderDecimalStr(field_ptr: anytype, value: u64) void {
594 const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
595 assert(array_info.child == u8);
596 assert(value < comptime try std.math.powi(u64, 10, array_info.len));
597 _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
598 .width = array_info.len,
599 .alignment = .left,
600 .fill = ' ',
601 });
602 }
603};
604
605pub const LongNamesTable = struct {
606 ni: MappedFile.Node.Index.Optional = .none,
607 entries: std.array_hash_map.Auto(void, Entry),
608
609 pub const Entry = struct {
610 offset: u64,
611 len: u64,
612 };
613
614 const Adapter = struct {
615 coff: *Coff,
616
617 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
618 assert(adapter.coff.isArchive());
619 const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf);
620 const rhs = adapter.coff.long_names_table.entries.values()[rhs_index];
621 return std.mem.eql(u8, longnames_slice[@intCast(rhs.offset)..][0..@intCast(rhs.len)], lhs_key);
622 }
623
624 pub fn hash(_: Adapter, key: []const u8) u32 {
625 assert(std.mem.findScalar(u8, key, 0) == null);
626 return std.array_hash_map.hashString(key);
627 }
628 };
629};
630
631pub const SymbolTable = struct {
632 ni: MappedFile.Node.Index,
633 strings_ni: MappedFile.Node.Index,
634 strings: std.array_hash_map.Auto(String, StringIndex),
635 symbols: std.array_hash_map.Auto(Symbol.Index, SymbolTable.Index),
636 pending_symbol_index: u32,
637
638 // Resizing the symbol table node has the result of accumulating padding
639 // between the last symbol in the symbol table node and the start of the
640 // string table node, due to the shifting method when resizing the parent in MappedFile.
641 // The spec requires the string table begin immediately after the last symbol,
642 // so we compact the symbol table node and move the string table back if needed.
643 pending_shrink: bool,
644
645 pub const StringIndex = enum(u32) {
646 _,
647 };
648
649 pub const SymbolName = union(enum) {
650 short: []const u8,
651 long: StringIndex,
652
653 pub fn store(name: SymbolName, coff: *const Coff, field: *[8]u8) void {
654 switch (name) {
655 .short => |s| {
656 @memcpy(field[0..s.len], s);
657 @memset(field[s.len..], 0);
658 },
659 .long => |l| {
660 @memset(field[0..4], 0);
661 std.mem.writePackedInt(u32, field[4..], 0, @backingInt(l), coff.targetEndian());
662 },
663 }
664 }
665 };
666
667 // Symbol.Index does not map 1:1 with SymbolTable.Index:
668 // - Not all symbols need a symbol table entry
669 // - A variable number of auxiliary entries may trail each symbol
670 pub const Index = enum(u32) {
671 none,
672 _,
673
674 pub fn wrap(i: u32) Index {
675 return @fromBackingInt(@intCast(i + 1));
676 }
677
678 pub fn unwrap(sti: Index) ?u32 {
679 return switch (sti) {
680 .none => null,
681 _ => @backingInt(sti) - 1,
682 };
683 }
684 };
685};
686
687pub const ExportTable = struct {
688 ni: MappedFile.Node.Index,
689 export_directory_table_ni: MappedFile.Node.Index,
690 export_address_table_si: Symbol.Index,
691 name_pointer_table_ni: MappedFile.Node.Index,
692 ordinal_table_ni: MappedFile.Node.Index,
693 name_table_ni: MappedFile.Node.Index,
694 entries: std.array_hash_map.Auto(void, Entry),
695 pending_sort: bool = false,
696
697 pub const Entry = struct {
698 si: Symbol.Index,
699 name_index: u32,
700 name_len: u32,
701 export_address_table_ri: Reloc.Index,
702 };
703
704 const Adapter = struct {
705 coff: *Coff,
706
707 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
708 const coff = adapter.coff;
709 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
710 const rhs = coff.export_table.entries.values()[rhs_index];
711 return std.mem.eql(u8, name_table_slice[rhs.name_index..][0..rhs.name_len], lhs_key);
712 }
713
714 pub fn hash(_: Adapter, key: []const u8) u32 {
715 assert(std.mem.findScalar(u8, key, 0) == null);
716 return std.array_hash_map.hashString(key);
717 }
718 };
719
720 pub const Ordinal = enum(u16) {
721 _,
722
723 pub fn get(export_index: ExportTable.Ordinal, coff: *Coff) *Entry {
724 return &coff.export_table.entries.values()[@backingInt(export_index)];
725 }
726 };
727};
728
729pub const ImportTable = struct {
730 ni: MappedFile.Node.Index,
731 entries: std.array_hash_map.Auto(void, Entry),
732 iat_symbol_indices: std.array_hash_map.Auto(struct {
733 iti: ImportTable.Index,
734 name: String.Optional,
735 // If name == .none this is the ordinal, otherwise the hint
736 ordinal_hint: u16,
737 }, u32),
738
739 pub const Entry = struct {
740 import_lookup_table_ni: MappedFile.Node.Index,
741 import_address_table_si: Symbol.Index,
742 import_hint_name_table_ni: MappedFile.Node.Index,
743 // All .iat_ptr globals that reference this table.
744 // This is separate from `iat_symbol_indices` because multiple symbols
745 // can reference to the same iat entry, after name demangling.
746 import_address_table_symbols: std.ArrayList(Symbol.Index),
747 len: u32,
748 hint_name_len: u32,
749 };
750
751 const Adapter = struct {
752 coff: *Coff,
753
754 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
755 const coff = adapter.coff;
756 const dll_name = coff.import_table.entries.values()[rhs_index]
757 .import_hint_name_table_ni.sliceConst(&coff.mf);
758 return std.mem.startsWith(u8, dll_name, lhs_key) and
759 std.mem.startsWith(u8, dll_name[lhs_key.len..], ".dll\x00");
760 }
761
762 pub fn hash(_: Adapter, key: []const u8) u32 {
763 assert(std.mem.findScalar(u8, key, 0) == null);
764 return std.array_hash_map.hashString(key);
765 }
766 };
767
768 pub const Index = enum(u32) {
769 _,
770
771 pub fn get(import_index: ImportTable.Index, coff: *Coff) *Entry {
772 return &coff.import_table.entries.values()[@backingInt(import_index)];
773 }
774 };
775};
776
777pub const String = enum(u32) {
778 @".data" = 0,
779 @".idata" = 6,
780 @".rdata" = 13,
781 @".text" = 20,
782 @".tls$" = 26,
783 @".edata" = 32,
784 @".ctors" = 39,
785 @".ctors$ZZZ" = 46,
786 @".dtors" = 57,
787 @".dtors$ZZZ" = 64,
788 @".bss" = 75,
789 @".fptable" = 80,
790 @".tls" = 89,
791 @".thunks" = 94,
792 _,
793
794 pub const Optional = enum(u32) {
795 @".data" = @backingInt(String.@".data"),
796 @".idata" = @backingInt(String.@".idata"),
797 @".rdata" = @backingInt(String.@".rdata"),
798 @".text" = @backingInt(String.@".text"),
799 @".tls$" = @backingInt(String.@".tls$"),
800 @".edata" = @backingInt(String.@".edata"),
801 @".ctors" = @backingInt(String.@".ctors"),
802 @".ctors$ZZZ" = @backingInt(String.@".ctors$ZZZ"),
803 @".dtors" = @backingInt(String.@".dtors"),
804 @".dtors$ZZZ" = @backingInt(String.@".dtors$ZZZ"),
805 @".bss" = @backingInt(String.@".bss"),
806 @".fptable" = @backingInt(String.@".fptable"),
807 @".tls" = @backingInt(String.@".tls"),
808 @".thunks" = @backingInt(String.@".thunks"),
809 none = std.math.maxInt(u32),
810 _,
811
812 pub fn unwrap(os: String.Optional) ?String {
813 return switch (os) {
814 else => |s| @fromBackingInt(@intCast(@backingInt(s))),
815 .none => null,
816 };
817 }
818
819 pub fn toSlice(os: String.Optional, coff: *Coff) ?[:0]const u8 {
820 return (os.unwrap() orelse return null).toSlice(coff);
821 }
822 };
823
824 pub fn toSlice(s: String, coff: *Coff) [:0]const u8 {
825 const slice = coff.string_bytes.items[@backingInt(s)..];
826 return slice[0..std.mem.findScalar(u8, slice, 0).? :0];
827 }
828
829 pub fn toOptional(s: String) String.Optional {
830 return @fromBackingInt(@intCast(@backingInt(s)));
831 }
832};
833
834pub const Section = struct {
835 si: Symbol.Index,
836 relocation_table_ni: MappedFile.Node.Index.Optional,
837
838 pub const RelocationIndex = enum(u16) {
839 none,
840 _,
841
842 pub fn wrap(i: ?u16) RelocationIndex {
843 return @fromBackingInt(@intCast((i orelse return .none) + 1));
844 }
845
846 pub fn unwrap(sri: RelocationIndex) ?u16 {
847 return switch (sri) {
848 .none => null,
849 _ => @backingInt(sri) - 1,
850 };
851 }
852
853 pub fn entry(
854 sri: RelocationIndex,
855 coff: *Coff,
856 sn: Symbol.SectionNumber,
857 ) ?*align(2) std.coff.Relocation {
858 if (sri == .none) return null;
859 const table_slice = sn.section(coff).relocation_table_ni.unwrap().?.slice(&coff.mf);
860 return @ptrCast(@alignCast(&table_slice[@as(u32, sri.unwrap().?) * std.coff.Relocation.sizeOf()]));
861 }
862 };
863};
864
865pub const Global = struct {
866 si: Symbol.Index,
867 lib_name: String.Optional,
868};
869
870pub const WeakExternalStrat = enum(u3) {
871 none,
872 no_library,
873 library,
874 alias,
875 anti_dependency,
876
877 pub fn fromFlag(flag: std.coff.WeakExternalFlag) WeakExternalStrat {
878 return switch (flag) {
879 .SEARCH_NOLIBRARY => .no_library,
880 .SEARCH_LIBRARY => .library,
881 .SEARCH_ALIAS => .alias,
882 .ANTI_DEPENDENCY => .anti_dependency,
883 _ => unreachable,
884 };
885 }
886};
887
888const SpecialSymbol = enum {
889 entry,
890 tls,
891 none,
892};
893
894pub const Symbol = struct {
895 ni: MappedFile.Node.Index.Optional,
896 rva: u32,
897 value: std.meta.BareUnion(Symbol.Value),
898 extra: std.meta.BareUnion(Symbol.Extra),
899 flags: packed struct(u16) {
900 value_tag: ValueTag,
901 extra_tag: ExtraTag,
902 type: Symbol.Type,
903 dll_storage_class: DllStorageClass,
904 weak_external_strat: WeakExternalStrat,
905 _: u5 = 0,
906 },
907 /// The first index of the contiguous range of location relocs for this symbol.
908 /// The list is terminated by a reloc with a different .target than this symbol.
909 /// These are relocations that have a .loc that points to this symbol.
910 loc_relocs: Reloc.Index,
911 /// The tail of a linked list of relocations with a .target that points to this symbol.
912 target_relocs: Reloc.Index,
913 section_number: SectionNumber,
914 gmi: Node.GlobalMapIndex,
915
916 pub const DllStorageClass = enum(u2) {
917 default,
918 dllimport,
919 dllexport,
920 };
921
922 pub const Type = enum(u2) {
923 unknown,
924 code,
925 data,
926 };
927
928 const ValueTag = enum(u2) {
929 none,
930 node_offset,
931 weak_alias_si,
932 weak_alias_name,
933 };
934
935 pub const Value = union(ValueTag) {
936 none,
937 /// The offset of the symbol within its node. Used with symbols that
938 /// don't create their own nodes: .input_section, .import_address_table
939 /// Images only.
940 node_offset: u32,
941 /// Images: the weak alias that should replace this symbol if it is not resolved.
942 /// Objects: he target of a weak external that hasn't been assigned an sti yet.
943 /// Globals only.
944 weak_alias_si: Symbol.Index,
945 /// For weak externals that have an alias that is also an undef
946 /// external, this is the name of the alias global that should
947 /// be generated and resolved if this symbol is not resolved.
948 /// Globals only, images only.
949 weak_alias_name: String,
950 };
951
952 const ExtraTag = enum(u2) {
953 size,
954 isli,
955 next_alias_si,
956 };
957
958 pub const Extra = union(ExtraTag) {
959 // The size of the symbol
960 size: u32,
961 /// Only valid when .ni == .input_section and .value_tag == .node_offset
962 isli: Node.InputSection.LocalIndex,
963 /// The next symbol in the list of aliases of this symbol.
964 next_alias_si: Symbol.Index,
965 };
966
967 pub fn setValue(sym: *Symbol, value: Symbol.Value) void {
968 sym.flags.value_tag = std.meta.activeTag(value);
969 sym.value = switch (sym.flags.value_tag) {
970 inline else => |t| @unionInit(
971 @FieldType(Symbol, "value"),
972 @tagName(t),
973 @field(value, @tagName(t)),
974 ),
975 };
976 }
977
978 pub fn setExtra(sym: *Symbol, extra: Symbol.Extra) void {
979 sym.flags.extra_tag = std.meta.activeTag(extra);
980 sym.extra = switch (sym.flags.extra_tag) {
981 inline else => |t| @unionInit(
982 @FieldType(Symbol, "extra"),
983 @tagName(t),
984 @field(extra, @tagName(t)),
985 ),
986 };
987 }
988
989 pub fn nodeOffset(sym: *const Symbol, coff: *Coff) u32 {
990 return switch (sym.flags.value_tag) {
991 .node_offset => offset: {
992 assert(switch (coff.getNode(sym.ni.unwrap().?)) {
993 // Separate nodes are not created for these entries per-symbol
994 .input_section, .import_address_table => true,
995 else => false,
996 });
997 break :offset sym.value.node_offset;
998 },
999 else => 0,
1000 };
1001 }
1002
1003 pub fn size(sym: *const Symbol, coff: *Coff) u32 {
1004 var size_sym = sym;
1005 while (size_sym.flags.extra_tag == .next_alias_si)
1006 size_sym = size_sym.extra.next_alias_si.get(coff);
1007 if (size_sym.flags.extra_tag != .size) return 0;
1008 return size_sym.extra.size;
1009 }
1010
1011 pub fn setSize(sym: *Symbol, coff: *Coff, new_size: u32) void {
1012 var size_sym = sym;
1013 while (size_sym.flags.extra_tag == .next_alias_si)
1014 size_sym = size_sym.extra.next_alias_si.get(coff);
1015 assert(size_sym.flags.extra_tag == .size);
1016 size_sym.extra.size = new_size;
1017 }
1018
1019 pub const SectionNumber = enum(i16) {
1020 UNDEFINED = 0,
1021 ABSOLUTE = -1,
1022 DEBUG = -2,
1023 _,
1024
1025 fn toIndex(sn: SectionNumber) u15 {
1026 return @intCast(@backingInt(sn) - 1);
1027 }
1028
1029 fn hasIndex(sn: SectionNumber) bool {
1030 return @backingInt(sn) > 0;
1031 }
1032
1033 pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index {
1034 return sn.section(coff).si;
1035 }
1036
1037 pub fn name(sn: SectionNumber, coff: *const Coff) String {
1038 return coff.section_table.keys()[sn.toIndex()];
1039 }
1040
1041 pub fn section(sn: SectionNumber, coff: *const Coff) *Section {
1042 return &coff.section_table.values()[sn.toIndex()];
1043 }
1044
1045 pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader {
1046 return &coff.sectionTableSlice()[sn.toIndex()];
1047 }
1048 };
1049
1050 pub const Index = enum(u32) {
1051 null,
1052 bss,
1053 data,
1054 rdata,
1055 text,
1056 _,
1057
1058 const known_count = @typeInfo(Index).@"enum".field_names.len;
1059
1060 pub fn get(si: Symbol.Index, coff: *Coff) *Symbol {
1061 return &coff.symbols.items[@backingInt(si)];
1062 }
1063
1064 pub fn unwrap(si: Symbol.Index) ?Symbol.Index {
1065 if (si == .null) return null;
1066 return si;
1067 }
1068
1069 pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index {
1070 return si.get(coff).ni.unwrap().?;
1071 }
1072
1073 pub fn sti(si: Symbol.Index, coff: *Coff) SymbolTable.Index {
1074 assert(!coff.isImage());
1075 return coff.symbol_table.symbols.get(si) orelse .none;
1076 }
1077
1078 pub fn next(si: Symbol.Index) Symbol.Index {
1079 return @fromBackingInt(@intCast(@backingInt(si) + 1));
1080 }
1081
1082 pub fn knownString(si: Symbol.Index) String.Optional {
1083 return switch (si) {
1084 .null, _ => .none,
1085 inline else => |tag| @field(String.Optional, "." ++ @tagName(tag)),
1086 };
1087 }
1088
1089 pub fn flushMoved(si: Symbol.Index, coff: *Coff) !void {
1090 const sym = si.get(coff);
1091 sym.rva = coff.computeNodeRva(sym.ni.unwrap().?) + sym.nodeOffset(coff);
1092 try si.applyLocationRelocs(coff);
1093 try si.applyTargetRelocs(coff, .none);
1094
1095 var alias_sym = sym;
1096 while (alias_sym.flags.extra_tag == .next_alias_si) {
1097 const alias_si = alias_sym.extra.next_alias_si;
1098 alias_sym = alias_si.get(coff);
1099 assert(alias_sym.ni == sym.ni);
1100 alias_sym.rva = sym.rva;
1101 try alias_si.applyTargetRelocs(coff, .none);
1102 }
1103 }
1104
1105 pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void {
1106 const sym = si.get(coff);
1107 const index = si.sti(coff).unwrap().?;
1108 var ri = sym.target_relocs;
1109 while (ri != .none) {
1110 const reloc = ri.get(coff);
1111 assert(reloc.target == si);
1112 if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry|
1113 coff.targetStore(&entry.symbol_table_index, index);
1114 ri = reloc.prev;
1115 }
1116 }
1117
1118 pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) !void {
1119 const sym = si.get(coff);
1120 switch (sym.loc_relocs) {
1121 .none => {},
1122 else => |loc_relocs| {
1123 for (coff.relocs.items[@backingInt(loc_relocs)..]) |*reloc| {
1124 if (reloc.loc != si) break;
1125 if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore(
1126 &entry.virtual_address,
1127 @intCast(coff.computeSymbolSectionOffset(sym, .image) + reloc.offset),
1128 );
1129 try reloc.apply(coff);
1130 }
1131 },
1132 }
1133 }
1134
1135 pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) !void {
1136 const sym = si.get(coff);
1137
1138 var ri = sym.target_relocs;
1139 while (ri != end) {
1140 const reloc = ri.get(coff);
1141 assert(reloc.target == si);
1142 try reloc.apply(coff);
1143 ri = reloc.prev;
1144 }
1145 }
1146
1147 pub fn deleteLocationRelocs(si: Symbol.Index, coff: *Coff) void {
1148 const sym = si.get(coff);
1149 switch (sym.loc_relocs) {
1150 .none => {},
1151 else => |loc_relocs| {
1152 for (coff.relocs.items[@backingInt(loc_relocs)..]) |*reloc| {
1153 if (reloc.loc != si) break;
1154 reloc.delete(coff);
1155 }
1156 sym.loc_relocs = .none;
1157 },
1158 }
1159 }
1160 };
1161
1162 comptime {
1163 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 32);
1164 }
1165};
1166
1167pub const Reloc = extern struct {
1168 offset: u64,
1169 addend: i64,
1170 type: Reloc.Type,
1171 sri: Section.RelocationIndex,
1172 prev: Reloc.Index,
1173 next: Reloc.Index,
1174 loc: Symbol.Index,
1175 target: Symbol.Index,
1176 flags: packed struct(u8) {
1177 /// Indicates the addend is not known and should be recovered from the location itself.
1178 /// COFF relocation tables don't encode the addend, only the location.
1179 recover_addend: bool,
1180 /// Set if this reloc is in the free list.
1181 /// When set, `prev` / `next` refer to other relocs in the free list.
1182 /// All other fields are undefined.
1183 free: bool,
1184 _: u6 = 0,
1185 },
1186
1187 pub const Type = extern union {
1188 AMD64: std.coff.IMAGE.REL.AMD64,
1189 ARM: std.coff.IMAGE.REL.ARM,
1190 ARM64: std.coff.IMAGE.REL.ARM64,
1191 SH: std.coff.IMAGE.REL.SH,
1192 PPC: std.coff.IMAGE.REL.PPC,
1193 I386: std.coff.IMAGE.REL.I386,
1194 IA64: std.coff.IMAGE.REL.IA64,
1195 MIPS: std.coff.IMAGE.REL.MIPS,
1196 M32R: std.coff.IMAGE.REL.M32R,
1197 u16: u16,
1198 };
1199
1200 pub const Index = enum(u32) {
1201 none = std.math.maxInt(u32),
1202 _,
1203
1204 pub fn wrap(i: ?u32) Reloc.Index {
1205 return @fromBackingInt(@intCast((i orelse return .none) + 1));
1206 }
1207
1208 pub fn get(ri: Reloc.Index, coff: *Coff) *Reloc {
1209 return &coff.relocs.items[@backingInt(ri)];
1210 }
1211 };
1212
1213 pub fn apply(reloc: *Reloc, coff: *Coff) !void {
1214 const loc_sym = reloc.loc.get(coff);
1215
1216 const loc_sym_ni = loc_sym.ni.unwrap() orelse return;
1217 if (loc_sym_ni.hasMoved(&coff.mf)) return;
1218
1219 const loc_slice = loc_sym_ni.slice(&coff.mf)[@intCast(reloc.offset)..];
1220 const target_endian = coff.targetEndian();
1221 const target_machine = coff.targetLoad(&coff.headerPtr().machine);
1222
1223 if (!coff.isImage()) {
1224 assert(!reloc.flags.recover_addend);
1225 switch (target_machine) {
1226 else => |machine| @panic(@tagName(machine)),
1227 .AMD64 => switch (reloc.type.AMD64) {
1228 else => |kind| @panic(@tagName(kind)),
1229 .ABSOLUTE => {},
1230 .ADDR64 => std.mem.writeInt(
1231 u64,
1232 loc_slice[0..8],
1233 @intCast(reloc.addend),
1234 target_endian,
1235 ),
1236 .ADDR32,
1237 .ADDR32NB,
1238 .SECREL,
1239 => std.mem.writeInt(
1240 u32,
1241 loc_slice[0..4],
1242 @intCast(reloc.addend),
1243 target_endian,
1244 ),
1245 .REL32,
1246 .REL32_1,
1247 .REL32_2,
1248 .REL32_3,
1249 .REL32_4,
1250 .REL32_5,
1251 => std.mem.writeInt(
1252 i32,
1253 loc_slice[0..4],
1254 @intCast(reloc.addend),
1255 target_endian,
1256 ),
1257 },
1258 .I386 => switch (reloc.type.I386) {
1259 else => |kind| @panic(@tagName(kind)),
1260 .ABSOLUTE => {},
1261 .DIR16,
1262 => std.mem.writeInt(
1263 u16,
1264 loc_slice[0..2],
1265 @intCast(reloc.addend),
1266 target_endian,
1267 ),
1268 .REL16,
1269 => std.mem.writeInt(
1270 i16,
1271 loc_slice[0..2],
1272 @intCast(reloc.addend),
1273 target_endian,
1274 ),
1275 .DIR32,
1276 .DIR32NB,
1277 .SECREL,
1278 => std.mem.writeInt(
1279 u32,
1280 loc_slice[0..4],
1281 @intCast(reloc.addend),
1282 target_endian,
1283 ),
1284 .REL32,
1285 => std.mem.writeInt(
1286 i32,
1287 loc_slice[0..4],
1288 @intCast(reloc.addend),
1289 target_endian,
1290 ),
1291 },
1292 }
1293
1294 return;
1295 } else if (reloc.flags.recover_addend) {
1296 reloc.flags.recover_addend = false;
1297 reloc.addend = switch (target_machine) {
1298 else => |machine| @panic(@tagName(machine)),
1299 .AMD64 => switch (reloc.type.AMD64) {
1300 else => |kind| @panic(@tagName(kind)),
1301 .ABSOLUTE => 0,
1302 .ADDR64 => @bitCast(std.mem.readInt(
1303 u64,
1304 loc_slice[0..8],
1305 target_endian,
1306 )),
1307 .ADDR32,
1308 .ADDR32NB,
1309 .SECREL,
1310 .REL32,
1311 .REL32_1,
1312 .REL32_2,
1313 .REL32_3,
1314 .REL32_4,
1315 .REL32_5,
1316 => std.mem.readInt(
1317 i32,
1318 loc_slice[0..4],
1319 target_endian,
1320 ),
1321 },
1322 .I386 => switch (reloc.type.I386) {
1323 else => |kind| @panic(@tagName(kind)),
1324 .ABSOLUTE => 0,
1325 .DIR16,
1326 .REL16,
1327 => std.mem.readInt(
1328 i16,
1329 loc_slice[0..2],
1330 target_endian,
1331 ),
1332 .DIR32,
1333 .DIR32NB,
1334 .SECREL,
1335 .REL32,
1336 => std.mem.readInt(
1337 i32,
1338 loc_slice[0..4],
1339 target_endian,
1340 ),
1341 },
1342 };
1343 }
1344
1345 const target_sym = reloc.target.get(coff);
1346 const is_abs = if (target_sym.ni.unwrap()) |ni| is_abs: {
1347 if (ni.hasMoved(&coff.mf)) return;
1348 break :is_abs false;
1349 } else is_abs: {
1350 if (target_sym.section_number != .ABSOLUTE) return;
1351 break :is_abs true;
1352 };
1353
1354 const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend));
1355 if (is_abs) {
1356 switch (target_machine) {
1357 else => |machine| @panic(@tagName(machine)),
1358 .AMD64 => switch (reloc.type.AMD64) {
1359 // TODO: Could wait to report these later, in reportUndefs -> reportRelocErrs,
1360 // so that this function doesn't return an err
1361 else => |kind| return coff.base.comp.link_diags.fail(
1362 "absolute symbol '{s}' targeted by invalid relocation type: {t}",
1363 .{ target_sym.gmi.name(coff).toSlice(coff), kind },
1364 ),
1365 .ABSOLUTE => {},
1366 .ADDR64 => std.mem.writeInt(
1367 u64,
1368 loc_slice[0..8],
1369 target_rva,
1370 target_endian,
1371 ),
1372 .ADDR32 => std.mem.writeInt(
1373 u32,
1374 loc_slice[0..4],
1375 @intCast(target_rva),
1376 target_endian,
1377 ),
1378 },
1379 .I386 => switch (reloc.type.I386) {
1380 else => |kind| return coff.base.comp.link_diags.fail(
1381 "absolute symbol '{s}' targeted by invalid relocation type: {t}",
1382 .{ target_sym.gmi.name(coff).toSlice(coff), kind },
1383 ),
1384 .ABSOLUTE => {},
1385 .DIR16 => std.mem.writeInt(
1386 u16,
1387 loc_slice[0..2],
1388 @intCast(target_rva),
1389 target_endian,
1390 ),
1391 .DIR32 => std.mem.writeInt(
1392 u32,
1393 loc_slice[0..4],
1394 @intCast(target_rva),
1395 target_endian,
1396 ),
1397 },
1398 }
1399 } else {
1400 switch (target_machine) {
1401 else => |machine| @panic(@tagName(machine)),
1402 .AMD64 => switch (reloc.type.AMD64) {
1403 else => |kind| @panic(@tagName(kind)),
1404 .ABSOLUTE => {},
1405 .ADDR64 => std.mem.writeInt(
1406 u64,
1407 loc_slice[0..8],
1408 coff.optionalHeaderField(.image_base) + target_rva,
1409 target_endian,
1410 ),
1411 .ADDR32 => std.mem.writeInt(
1412 u32,
1413 loc_slice[0..4],
1414 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1415 target_endian,
1416 ),
1417 .ADDR32NB => std.mem.writeInt(
1418 u32,
1419 loc_slice[0..4],
1420 @intCast(target_rva),
1421 target_endian,
1422 ),
1423 .REL32 => std.mem.writeInt(
1424 i32,
1425 loc_slice[0..4],
1426 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
1427 target_endian,
1428 ),
1429 .REL32_1 => std.mem.writeInt(
1430 i32,
1431 loc_slice[0..4],
1432 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))),
1433 target_endian,
1434 ),
1435 .REL32_2 => std.mem.writeInt(
1436 i32,
1437 loc_slice[0..4],
1438 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))),
1439 target_endian,
1440 ),
1441 .REL32_3 => std.mem.writeInt(
1442 i32,
1443 loc_slice[0..4],
1444 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))),
1445 target_endian,
1446 ),
1447 .REL32_4 => std.mem.writeInt(
1448 i32,
1449 loc_slice[0..4],
1450 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))),
1451 target_endian,
1452 ),
1453 .REL32_5 => std.mem.writeInt(
1454 i32,
1455 loc_slice[0..4],
1456 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))),
1457 target_endian,
1458 ),
1459 .SECREL => std.mem.writeInt(
1460 u32,
1461 loc_slice[0..4],
1462 @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
1463 target_endian,
1464 ),
1465 },
1466 .I386 => switch (reloc.type.I386) {
1467 else => |kind| @panic(@tagName(kind)),
1468 .ABSOLUTE => {},
1469 .DIR16 => std.mem.writeInt(
1470 u16,
1471 loc_slice[0..2],
1472 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1473 target_endian,
1474 ),
1475 .REL16 => std.mem.writeInt(
1476 i16,
1477 loc_slice[0..2],
1478 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))),
1479 target_endian,
1480 ),
1481 .DIR32 => std.mem.writeInt(
1482 u32,
1483 loc_slice[0..4],
1484 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1485 target_endian,
1486 ),
1487 .DIR32NB => std.mem.writeInt(
1488 u32,
1489 loc_slice[0..4],
1490 @intCast(target_rva),
1491 target_endian,
1492 ),
1493 .REL32 => std.mem.writeInt(
1494 i32,
1495 loc_slice[0..4],
1496 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
1497 target_endian,
1498 ),
1499 .SECREL => std.mem.writeInt(
1500 u32,
1501 loc_slice[0..4],
1502 @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
1503 target_endian,
1504 ),
1505 },
1506 }
1507 }
1508 }
1509
1510 pub fn delete(reloc: *Reloc, coff: *Coff) void {
1511 log.debug("deleteReloc({d})", .{reloc - coff.relocs.items.ptr});
1512 if (reloc.sri != .none) {
1513 const loc_sym = reloc.loc.get(coff);
1514 const entry = reloc.sri.entry(coff, loc_sym.section_number).?;
1515
1516 // On every supported architecture, a reloc type of 0 is .ABSOLUTE, and is a no-op
1517 @memset(std.mem.asBytes(entry), 0);
1518 }
1519
1520 switch (reloc.prev) {
1521 .none => {},
1522 else => |prev| prev.get(coff).next = reloc.next,
1523 }
1524 switch (reloc.next) {
1525 .none => {
1526 const target = reloc.target.get(coff);
1527 assert(target.target_relocs.get(coff) == reloc);
1528 target.target_relocs = reloc.prev;
1529 },
1530 else => |next| next.get(coff).prev = reloc.prev,
1531 }
1532
1533 reloc.* = undefined;
1534 reloc.flags = .{
1535 .recover_addend = false,
1536 .free = true,
1537 };
1538
1539 const ri: Reloc.Index = .wrap(@intCast(reloc - coff.relocs.items.ptr));
1540 if (coff.last_free_reloc == .none) {
1541 assert(coff.first_free_reloc == .none);
1542 coff.first_free_reloc = ri;
1543 coff.last_free_reloc = ri;
1544 } else {
1545 coff.last_free_reloc.get(coff).next = ri;
1546 reloc.prev = coff.last_free_reloc;
1547 reloc.next = .none;
1548 coff.last_free_reloc = ri;
1549 }
1550 }
1551
1552 comptime {
1553 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Reloc) == 40);
1554 }
1555};
1556
1557pub fn open(
1558 arena: std.mem.Allocator,
1559 comp: *Compilation,
1560 path: std.Build.Cache.Path,
1561 options: link.File.OpenOptions,
1562) !*Coff {
1563 return create(arena, comp, path, options);
1564}
1565pub fn createEmpty(
1566 arena: std.mem.Allocator,
1567 comp: *Compilation,
1568 path: std.Build.Cache.Path,
1569 options: link.File.OpenOptions,
1570) !*Coff {
1571 return create(arena, comp, path, options);
1572}
1573fn create(
1574 arena: std.mem.Allocator,
1575 comp: *Compilation,
1576 path: std.Build.Cache.Path,
1577 options: link.File.OpenOptions,
1578) !*Coff {
1579 const target = &comp.root_mod.resolved_target.result;
1580 assert(target.ofmt == .coff);
1581 if (target.cpu.arch.endian() != comptime targetEndian(undefined))
1582 return error.UnsupportedCOFFArchitecture;
1583 const machine = target.toCoffMachine();
1584 const timestamp: u32 = 0;
1585 const major_subsystem_version = options.major_subsystem_version orelse 6;
1586 const minor_subsystem_version = options.minor_subsystem_version orelse 0;
1587 const magic: std.coff.OptionalHeader.Magic = switch (target.ptrBitWidth()) {
1588 0...32 => .PE32,
1589 33...64 => .@"PE32+",
1590 else => return error.UnsupportedCOFFArchitecture,
1591 };
1592 const section_align: Alignment = switch (machine) {
1593 .AMD64, .I386 => @fromBackingInt(@intCast(12)),
1594 .SH3, .SH3DSP, .SH4, .SH5 => @fromBackingInt(@intCast(12)),
1595 .MIPS16, .MIPSFPU, .MIPSFPU16, .WCEMIPSV2 => @fromBackingInt(@intCast(12)),
1596 .POWERPC, .POWERPCFP => @fromBackingInt(@intCast(12)),
1597 .ALPHA, .ALPHA64 => @fromBackingInt(@intCast(13)),
1598 .IA64 => @fromBackingInt(@intCast(13)),
1599 .ARM => @fromBackingInt(@intCast(12)),
1600 else => return error.UnsupportedCOFFArchitecture,
1601 };
1602
1603 const io = comp.io;
1604
1605 const coff = try arena.create(Coff);
1606 const file = try path.root_dir.handle.createFile(io, path.sub_path, .{
1607 .read = true,
1608 .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode),
1609 });
1610 errdefer file.close(io);
1611 coff.* = .{
1612 .base = .{
1613 .tag = .coff2,
1614
1615 .comp = comp,
1616 .emit = path,
1617
1618 .file = file,
1619 .gc_sections = false,
1620 .print_gc_sections = false,
1621 .build_id = .none,
1622 .allow_shlib_undefined = false,
1623 .stack_size = 0,
1624 },
1625 .options = options,
1626 .mf = try .init(file, comp.gpa, io),
1627 .nodes = .empty,
1628 .members = .empty,
1629 .pending_members = .empty,
1630 .lib_string_table = .empty,
1631 .lib_string_len = 0,
1632 .long_names_table = .{
1633 .entries = .empty,
1634 },
1635 .import_table = .{
1636 .ni = undefined,
1637 .entries = .empty,
1638 .iat_symbol_indices = .empty,
1639 },
1640 .export_table = .{
1641 .ni = undefined,
1642 .export_directory_table_ni = undefined,
1643 .export_address_table_si = .null,
1644 .name_pointer_table_ni = undefined,
1645 .ordinal_table_ni = undefined,
1646 .name_table_ni = undefined,
1647 .entries = .empty,
1648 },
1649 .symbol_table = .{
1650 .ni = undefined,
1651 .strings_ni = undefined,
1652 .strings = .empty,
1653 .symbols = .empty,
1654 .pending_symbol_index = 0,
1655 .pending_shrink = false,
1656 },
1657 .inputs = .empty,
1658 .input_archives = .empty,
1659 .input_archive_members = .empty,
1660 .input_archive_symbols = .empty,
1661 .input_archive_symbol_indices = .empty,
1662 .pending_input = null,
1663 .pending_default_libs = .empty,
1664 .alternate_names = .empty,
1665 .input_objects = .empty,
1666 .input_symbols = .empty,
1667 .input_sections = .empty,
1668 .input_section_pending_index = 0,
1669 .inputs_complete = false,
1670 .exports_complete = false,
1671 .pending_special_symbol = .entry,
1672 .strings = .empty,
1673 .string_bytes = .empty,
1674 .section_table = .empty,
1675 .pseudo_section_table = .empty,
1676 .object_section_table = .empty,
1677 .section_merges = .empty,
1678 .section_merge_pending_index = 0,
1679 .symbols = .empty,
1680 .globals = .empty,
1681 .global_pending_index = 0,
1682 .navs = .empty,
1683 .uavs = .empty,
1684 .lazy = comptime .initFill(.{
1685 .map = .empty,
1686 .pending_index = 0,
1687 }),
1688 .pending_uavs = .empty,
1689 .relocs = .empty,
1690 .first_free_reloc = .none,
1691 .last_free_reloc = .none,
1692 .const_prog_node = .none,
1693 .synth_prog_node = .none,
1694 .symbol_prog_node = .none,
1695 .member_prog_node = .none,
1696 .input_prog_node = .none,
1697 };
1698 errdefer coff.deinit();
1699
1700 {
1701 const strings = std.enums.values(String);
1702 try coff.strings.ensureTotalCapacityContext(comp.gpa, @intCast(strings.len), .{
1703 .bytes = &coff.string_bytes,
1704 });
1705 for (strings) |string| assert(try coff.getOrPutString(@tagName(string)) == string);
1706 }
1707
1708 try coff.initHeaders(
1709 machine,
1710 timestamp,
1711 major_subsystem_version,
1712 minor_subsystem_version,
1713 magic,
1714 if (options.subsystem) |s| switch (s) {
1715 .console => .WINDOWS_CUI,
1716 .windows => .WINDOWS_GUI,
1717 else => return error.UnsupportedCOFFSubsystem,
1718 } else .WINDOWS_CUI,
1719 section_align,
1720 std.fs.path.basename(path.sub_path),
1721 );
1722 try coff.initBuiltins();
1723 return coff;
1724}
1725
1726pub fn deinit(coff: *Coff) void {
1727 const gpa = coff.base.comp.gpa;
1728 coff.mf.deinit(gpa);
1729 coff.nodes.deinit(gpa);
1730 coff.pending_members.deinit(gpa);
1731 coff.lib_string_table.deinit(gpa);
1732 coff.long_names_table.entries.deinit(gpa);
1733 coff.import_table.entries.deinit(gpa);
1734 coff.import_table.iat_symbol_indices.deinit(gpa);
1735 coff.export_table.entries.deinit(gpa);
1736 coff.symbol_table.strings.deinit(gpa);
1737 coff.symbol_table.symbols.deinit(gpa);
1738 coff.inputs.deinit(gpa);
1739 coff.input_archives.deinit(gpa);
1740 coff.input_archive_members.deinit(gpa);
1741 coff.input_archive_symbols.deinit(gpa);
1742 coff.input_archive_symbol_indices.deinit(gpa);
1743 for (coff.pending_default_libs.items) |l| gpa.free(l.path);
1744 coff.pending_default_libs.deinit(gpa);
1745 coff.alternate_names.deinit(gpa);
1746 coff.input_objects.deinit(gpa);
1747 coff.input_symbols.deinit(gpa);
1748 coff.input_sections.deinit(gpa);
1749 coff.strings.deinit(gpa);
1750 coff.string_bytes.deinit(gpa);
1751 coff.section_table.deinit(gpa);
1752 coff.pseudo_section_table.deinit(gpa);
1753 coff.object_section_table.deinit(gpa);
1754 coff.symbols.deinit(gpa);
1755 coff.globals.deinit(gpa);
1756 coff.navs.deinit(gpa);
1757 coff.uavs.deinit(gpa);
1758 for (&coff.lazy.values) |*lazy| lazy.map.deinit(gpa);
1759 coff.pending_uavs.deinit(gpa);
1760 coff.relocs.deinit(gpa);
1761 coff.* = undefined;
1762}
1763
1764fn isImage(coff: *const Coff) bool {
1765 const comp = coff.base.comp;
1766 return switch (comp.config.output_mode) {
1767 .Exe => true,
1768 .Lib => switch (comp.config.link_mode) {
1769 .static => false,
1770 .dynamic => true,
1771 },
1772 .Obj => false,
1773 };
1774}
1775
1776fn isArchive(coff: *const Coff) bool {
1777 const comp = coff.base.comp;
1778 return switch (comp.config.output_mode) {
1779 .Exe => false,
1780 .Lib => switch (comp.config.link_mode) {
1781 .static => true,
1782 .dynamic => false,
1783 },
1784 .Obj => false,
1785 };
1786}
1787
1788fn isExe(coff: *const Coff) bool {
1789 return coff.base.comp.config.output_mode == .Exe;
1790}
1791
1792fn isObj(coff: *const Coff) bool {
1793 return coff.base.comp.config.output_mode == .Obj;
1794}
1795
1796fn hasCoffHeader(coff: *const Coff) bool {
1797 return coff.base.comp.zcu != null or !coff.isArchive();
1798}
1799
1800fn sectionParent(coff: *Coff) MappedFile.Node.Index {
1801 assert(coff.hasCoffHeader());
1802 return if (coff.isArchive()) Node.known.zcu_member else Node.known.file;
1803}
1804
1805fn initHeaders(
1806 coff: *Coff,
1807 machine: std.coff.IMAGE.FILE.MACHINE,
1808 timestamp: u32,
1809 major_subsystem_version: u16,
1810 minor_subsystem_version: u16,
1811 magic: std.coff.OptionalHeader.Magic,
1812 subsystem: std.coff.Subsystem,
1813 section_align: Alignment,
1814 file_name: []const u8,
1815) !void {
1816 const comp = coff.base.comp;
1817 const gpa = comp.gpa;
1818 const target_endian = coff.targetEndian();
1819 const file_align: Alignment = comptime .fromByteUnits(default_file_alignment);
1820 const is_image = coff.isImage();
1821 const is_archive = coff.isArchive();
1822 const target = &comp.root_mod.resolved_target.result;
1823 const optional_header_size: u16 = if (is_image) switch (magic) {
1824 _ => unreachable,
1825 inline else => |ct_magic| @sizeOf(@field(std.coff.OptionalHeader, @tagName(ct_magic))),
1826 } else 0;
1827 const data_directories_size: u16 = if (is_image)
1828 @sizeOf(std.coff.ImageDataDirectory) * std.coff.IMAGE.DIRECTORY_ENTRY.len
1829 else
1830 0;
1831
1832 var expected_nodes_len: usize = Node.known_count;
1833 if (coff.hasCoffHeader()) {
1834 // Sections
1835 expected_nodes_len += 4;
1836
1837 if (is_image) {
1838 // Pseudo-sections and import / export table
1839 expected_nodes_len += 9;
1840 if (comp.config.link_libc and target.abi == .msvc)
1841 expected_nodes_len += 1;
1842 } else
1843 // Symbol table
1844 expected_nodes_len += 2;
1845
1846 // TLS section
1847 if (comp.config.any_non_single_threaded) {
1848 if (!is_image) expected_nodes_len += 1;
1849 expected_nodes_len += 1;
1850 }
1851 }
1852 defer assert(coff.nodes.len == expected_nodes_len);
1853
1854 try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len);
1855 coff.nodes.appendAssumeCapacity(.file);
1856
1857 const header_ni = Node.known.header;
1858 assert(header_ni == try Node.known.file.addOnlyHeaderChild(gpa, &coff.mf, .{
1859 .alignment = coff.mf.flags.block_size,
1860 }));
1861 coff.nodes.appendAssumeCapacity(.header);
1862
1863 const coff_parent_ni: MappedFile.Node.Index = if (is_archive) parent: {
1864 assert(try Node.known.file.addHeaderChildAfter(gpa, &coff.mf, .wrap(header_ni), .{
1865 .size = std.coff.archive_signature.len,
1866 .alignment = .@"4",
1867 }) == Node.known.signature);
1868 coff.nodes.appendAssumeCapacity(.signature);
1869 const signature_slice = Node.known.signature.slice(&coff.mf);
1870 @memcpy(signature_slice, std.coff.archive_signature);
1871
1872 const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null);
1873 try coff.members.ensureTotalCapacity(gpa, initial_member_count);
1874
1875 assert(Member.Index.first == try coff.addMemberAssumeCapacity(.first_linker, @sizeOf(u32)));
1876 coff.targetStore(coff.firstLinkerMemberNumSymbolsPtr(), 0);
1877
1878 assert(Member.Index.second == try coff.addMemberAssumeCapacity(.second_linker, 2 * @sizeOf(u32)));
1879 coff.targetStore(coff.secondLinkerMemberNumMembersPtr(), 0);
1880 coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), 0);
1881
1882 assert(Member.Index.longnames == try coff.addMemberAssumeCapacity(.longnames, 0));
1883
1884 const first_linker_member = Member.Index.first.get(coff);
1885 const second_linker_member = Member.Index.second.get(coff);
1886 const longnames_member = Member.Index.longnames.get(coff);
1887
1888 try first_linker_member.initHeader(coff, "", timestamp);
1889 try second_linker_member.initHeader(coff, "", timestamp);
1890 try longnames_member.initHeader(coff, "/", timestamp);
1891
1892 if (comp.zcu) |zcu| {
1893 const zcu_mi = try coff.addMemberAssumeCapacity(.coff, @sizeOf(std.coff.Header));
1894 const zcu_member = zcu_mi.get(coff);
1895 try zcu_member.initHeader(coff, zcu.main_mod.fully_qualified_name, timestamp);
1896
1897 assert(try zcu_member.content_ni.addOnlyHeaderChild(gpa, &coff.mf, .{
1898 .size = @sizeOf(std.coff.Header),
1899 .alignment = .@"4",
1900 }) == Node.known.coff_header);
1901 coff.nodes.appendAssumeCapacity(.coff_header);
1902
1903 break :parent zcu_member.content_ni;
1904 }
1905
1906 // If we're not generating any code, no more known nodes are used
1907
1908 // These placeholder nodes are placed before the first member - if there are
1909 // no other members then the last linker member (longnames) needs to expand
1910 // to fill the padding at the end of the file.
1911 while (coff.nodes.len < Node.known_count) {
1912 _ = try Node.known.header.addHeaderChildAfter(gpa, &coff.mf, .none, .{});
1913 coff.nodes.appendAssumeCapacity(.placeholder);
1914 }
1915
1916 return;
1917 } else parent: {
1918 assert(try header_ni.addOnlyHeaderChild(gpa, &coff.mf, .{
1919 .size = if (is_image) msdos_stub.len + std.coff.pe_signature.len else 0,
1920 .alignment = .@"4",
1921 }) == Node.known.signature);
1922 coff.nodes.appendAssumeCapacity(.signature);
1923 if (is_image) {
1924 const signature_slice = Node.known.signature.slice(&coff.mf);
1925 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
1926 @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
1927 }
1928
1929 // TODO: Not ideal to have this many placeholder nodes - use two distinct `Node.known` types?
1930 while (true) {
1931 const placeholder_ni = try Node.known.file.addHeaderChildAfter(gpa, &coff.mf, .none, .{});
1932 coff.nodes.appendAssumeCapacity(.placeholder);
1933 if (placeholder_ni == Node.known.zcu_member) break;
1934 }
1935
1936 assert(try header_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(Node.known.signature), .{
1937 .size = @sizeOf(std.coff.Header),
1938 .alignment = .@"4",
1939 }) == Node.known.coff_header);
1940 coff.nodes.appendAssumeCapacity(.coff_header);
1941
1942 break :parent header_ni;
1943 };
1944
1945 {
1946 const coff_header = coff.headerPtr();
1947 coff_header.* = .{
1948 .machine = machine,
1949 .number_of_sections = 0,
1950 .time_date_stamp = timestamp,
1951 .pointer_to_symbol_table = 0,
1952 .number_of_symbols = 0,
1953 .size_of_optional_header = optional_header_size + data_directories_size,
1954 .flags = .{
1955 .RELOCS_STRIPPED = is_image,
1956 .EXECUTABLE_IMAGE = is_image,
1957 .DEBUG_STRIPPED = true,
1958 .@"32BIT_MACHINE" = magic == .PE32,
1959 .LARGE_ADDRESS_AWARE = magic == .@"PE32+",
1960 .DLL = comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic,
1961 },
1962 };
1963 if (target_endian != native_endian) std.mem.byteSwapAllFields(std.coff.Header, coff_header);
1964 }
1965
1966 const optional_header_ni = Node.known.optional_header;
1967 assert(optional_header_ni == try coff_parent_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(Node.known.coff_header), .{
1968 .size = optional_header_size,
1969 .alignment = .@"4",
1970 }));
1971 coff.nodes.appendAssumeCapacity(.optional_header);
1972 if (is_image) {
1973 coff.targetStore(&coff.optionalHeaderStandardPtr().magic, magic);
1974 switch (coff.optionalHeaderPtr()) {
1975 .PE32 => |optional_header| {
1976 optional_header.* = .{
1977 .standard = .{
1978 .magic = .PE32,
1979 .major_linker_version = 0,
1980 .minor_linker_version = 0,
1981 .size_of_code = 0,
1982 .size_of_initialized_data = 0,
1983 .size_of_uninitialized_data = 0,
1984 .address_of_entry_point = 0,
1985 .base_of_code = 0,
1986 },
1987 .base_of_data = 0,
1988 .image_base = switch (coff.base.comp.config.output_mode) {
1989 .Exe => 0x400000,
1990 .Lib => switch (coff.base.comp.config.link_mode) {
1991 .static => 0,
1992 .dynamic => 0x10000000,
1993 },
1994 .Obj => 0,
1995 },
1996 .section_alignment = @intCast(section_align.toByteUnits()),
1997 .file_alignment = @intCast(file_align.toByteUnits()),
1998 .major_operating_system_version = 6,
1999 .minor_operating_system_version = 0,
2000 .major_image_version = 0,
2001 .minor_image_version = 0,
2002 .major_subsystem_version = major_subsystem_version,
2003 .minor_subsystem_version = minor_subsystem_version,
2004 .win32_version_value = 0,
2005 .size_of_image = 0,
2006 .size_of_headers = 0,
2007 .checksum = 0,
2008 .subsystem = subsystem,
2009 .dll_flags = .{
2010 .HIGH_ENTROPY_VA = true,
2011 .DYNAMIC_BASE = true,
2012 .TERMINAL_SERVER_AWARE = true,
2013 .NX_COMPAT = true,
2014 },
2015 .size_of_stack_reserve = default_size_of_stack_reserve,
2016 .size_of_stack_commit = default_size_of_stack_commit,
2017 .size_of_heap_reserve = default_size_of_heap_reserve,
2018 .size_of_heap_commit = default_size_of_heap_commit,
2019 .loader_flags = 0,
2020 .number_of_rva_and_sizes = std.coff.IMAGE.DIRECTORY_ENTRY.len,
2021 };
2022 if (target_endian != native_endian)
2023 std.mem.byteSwapAllFields(std.coff.OptionalHeader.PE32, optional_header);
2024 },
2025 .@"PE32+" => |optional_header| {
2026 optional_header.* = .{
2027 .standard = .{
2028 .magic = .@"PE32+",
2029 .major_linker_version = 0,
2030 .minor_linker_version = 0,
2031 .size_of_code = 0,
2032 .size_of_initialized_data = 0,
2033 .size_of_uninitialized_data = 0,
2034 .address_of_entry_point = 0,
2035 .base_of_code = 0,
2036 },
2037 .image_base = switch (coff.base.comp.config.output_mode) {
2038 .Exe => 0x140000000,
2039 .Lib => switch (coff.base.comp.config.link_mode) {
2040 .static => 0,
2041 .dynamic => 0x180000000,
2042 },
2043 .Obj => 0,
2044 },
2045 .section_alignment = @intCast(section_align.toByteUnits()),
2046 .file_alignment = @intCast(file_align.toByteUnits()),
2047 .major_operating_system_version = 6,
2048 .minor_operating_system_version = 0,
2049 .major_image_version = 0,
2050 .minor_image_version = 0,
2051 .major_subsystem_version = major_subsystem_version,
2052 .minor_subsystem_version = minor_subsystem_version,
2053 .win32_version_value = 0,
2054 .size_of_image = 0,
2055 .size_of_headers = 0,
2056 .checksum = 0,
2057 .subsystem = subsystem,
2058 .dll_flags = .{
2059 .HIGH_ENTROPY_VA = true,
2060 .DYNAMIC_BASE = true,
2061 .TERMINAL_SERVER_AWARE = true,
2062 .NX_COMPAT = true,
2063 },
2064 .size_of_stack_reserve = default_size_of_stack_reserve,
2065 .size_of_stack_commit = default_size_of_stack_commit,
2066 .size_of_heap_reserve = default_size_of_heap_reserve,
2067 .size_of_heap_commit = default_size_of_heap_commit,
2068 .loader_flags = 0,
2069 .number_of_rva_and_sizes = std.coff.IMAGE.DIRECTORY_ENTRY.len,
2070 };
2071 if (target_endian != native_endian)
2072 std.mem.byteSwapAllFields(std.coff.OptionalHeader.@"PE32+", optional_header);
2073 },
2074 }
2075 }
2076
2077 const data_directories_ni = Node.known.data_directories;
2078 assert(data_directories_ni == try coff_parent_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(optional_header_ni), .{
2079 .size = data_directories_size,
2080 .alignment = .@"4",
2081 }));
2082 coff.nodes.appendAssumeCapacity(.data_directories);
2083 if (is_image) {
2084 const data_directories = coff.dataDirectorySlice();
2085 @memset(data_directories, .{ .virtual_address = 0, .size = 0 });
2086 if (target_endian != native_endian) std.mem.byteSwapAllFields(
2087 [std.coff.IMAGE.DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory,
2088 data_directories,
2089 );
2090 }
2091
2092 const section_table_ni = Node.known.section_table;
2093 assert(section_table_ni == try coff_parent_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(data_directories_ni), .{
2094 .alignment = .@"4",
2095 }));
2096 coff.nodes.appendAssumeCapacity(.section_table);
2097
2098 assert(coff.nodes.len == Node.known_count);
2099
2100 if (!is_image) {
2101 // TODO: These two nodes could be inside one movable node?
2102 coff.symbol_table.ni = try coff_parent_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(section_table_ni), .{
2103 .alignment = .@"2",
2104 .moved = true,
2105 });
2106 coff.nodes.appendAssumeCapacity(.symbol_table);
2107
2108 coff.symbol_table.strings_ni = try coff_parent_ni.addHeaderChildAfter(gpa, &coff.mf, .wrap(coff.symbol_table.ni), .{
2109 .size = @sizeOf(u32),
2110 .resized = true,
2111 });
2112 coff.nodes.appendAssumeCapacity(.string_table);
2113 coff.targetStore(coff.symbolTableStringLenPtr(), @sizeOf(u32));
2114 }
2115
2116 try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count);
2117 assert(coff.addSymbolAssumeCapacity() == .null);
2118
2119 // TODO: How do we tell MappedFile not to allocate physical space for .bss?
2120 // TODO: Could have a node flag 'virtual' that can never have slice* or fileLocation called on it
2121 // TODO: Instead of it's own section, place .bss as a pseudo-section at the end of .text in the extra space
2122 assert(try coff.addSection(.@".bss", .{
2123 .CNT_UNINITIALIZED_DATA = true,
2124 .MEM_READ = true,
2125 .MEM_WRITE = true,
2126 }) == .bss);
2127 assert(try coff.addSection(.@".data", .{
2128 .CNT_INITIALIZED_DATA = true,
2129 .MEM_READ = true,
2130 .MEM_WRITE = true,
2131 }) == .data);
2132 assert(try coff.addSection(.@".rdata", .{
2133 .CNT_INITIALIZED_DATA = true,
2134 .MEM_READ = true,
2135 }) == .rdata);
2136 assert(try coff.addSection(.@".text", .{
2137 .CNT_CODE = true,
2138 .MEM_EXECUTE = true,
2139 .MEM_READ = true,
2140 }) == .text);
2141
2142 if (is_image) {
2143 if (comp.config.link_libc and target.abi == .msvc) {
2144 // This section contains a function pointer table used by control flow guard:
2145 // https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
2146 // The page containing it is set to PAGE_READONLY during startup, so this can't
2147 // be merged into .data this protection would overlap writable memory.
2148 _ = try coff.addSection(.@".fptable", .{
2149 .CNT_INITIALIZED_DATA = true,
2150 .MEM_READ = true,
2151 .MEM_WRITE = true,
2152 });
2153 }
2154
2155 // TODO: Lazily initialize this instead, avoid the extra logic for this in flushMoved / flushResized
2156 const import_table_parent_ni = (try coff.objectSectionMapIndex(
2157 .@".idata",
2158 coff.mf.flags.block_size,
2159 .{ .read = true, .initialized = true },
2160 )).symbol(coff).node(coff);
2161 coff.import_table.ni = try import_table_parent_ni.addFloatingChild(gpa, &coff.mf, .{
2162 .alignment = .@"4",
2163 });
2164 coff.nodes.appendAssumeCapacity(.import_directory_table);
2165
2166 coff.export_table.ni = (try coff.pseudoSectionMapIndex(
2167 .@".edata",
2168 .of(std.coff.ExportDirectoryTable),
2169 .{ .read = true, .initialized = true },
2170 )).symbol(coff).node(coff);
2171
2172 coff.export_table.export_directory_table_ni = try coff.export_table.ni.addHeaderChildAfter(gpa, &coff.mf, coff.export_table.ni.last(&coff.mf), .{
2173 .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1,
2174 .moved = true,
2175 });
2176 coff.nodes.appendAssumeCapacity(.export_directory_table);
2177
2178 const name_index = @sizeOf(std.coff.ExportDirectoryTable);
2179 const table_slice = coff.export_table.export_directory_table_ni.slice(&coff.mf);
2180 @memcpy(table_slice[name_index..][0..file_name.len], file_name[0..file_name.len]);
2181 @memset(table_slice[name_index + file_name.len ..], 0);
2182
2183 const export_address_table_ni = try coff.export_table.ni.addFloatingChild(gpa, &coff.mf, .{
2184 .alignment = .of(std.coff.ExportAddressTableEntry),
2185 .moved = true,
2186 });
2187 coff.nodes.appendAssumeCapacity(.export_address_table);
2188
2189 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2190 coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity();
2191
2192 const export_address_table_sym = coff.export_table.export_address_table_si.get(coff);
2193 export_address_table_sym.ni = .wrap(export_address_table_ni);
2194 assert(export_address_table_sym.loc_relocs == .none);
2195 export_address_table_sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
2196 export_address_table_sym.section_number =
2197 coff.getNode(coff.export_table.ni).pseudo_section.symbol(coff).get(coff).section_number;
2198
2199 coff.export_table.name_pointer_table_ni = try coff.export_table.ni.addFloatingChild(gpa, &coff.mf, .{
2200 .alignment = .of(std.coff.ExportNamePointerTableEntry),
2201 .moved = true,
2202 });
2203 coff.nodes.appendAssumeCapacity(.export_name_pointer_table);
2204
2205 coff.export_table.ordinal_table_ni = try coff.export_table.ni.addFloatingChild(gpa, &coff.mf, .{
2206 .alignment = .of(std.coff.ExportOrdinalTableEntry),
2207 .moved = true,
2208 });
2209 coff.nodes.appendAssumeCapacity(.export_ordinal_table);
2210
2211 coff.export_table.name_table_ni = try coff.export_table.ni.addFloatingChild(gpa, &coff.mf, .{
2212 .alignment = .of(u8),
2213 .moved = true,
2214 });
2215 coff.nodes.appendAssumeCapacity(.export_name_table);
2216
2217 const export_directory_table = coff.exportDirectoryTable();
2218 export_directory_table.* = .{
2219 .flags = 0,
2220 .time_date_stamp = timestamp,
2221 .major_version = 0,
2222 .minor_version = 0,
2223 .name_rva = 0,
2224 .ordinal_base = 1,
2225 .number_of_entries = 0,
2226 .number_of_names = 0,
2227 .export_address_table_rva = 0,
2228 .name_pointer_table_rva = 0,
2229 .ordinal_table_rva = 0,
2230 };
2231 if (target_endian != native_endian)
2232 std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table);
2233 }
2234
2235 if (comp.config.any_non_single_threaded) {
2236 if (!is_image)
2237 _ = try coff.addSection(.@".tls$", .{
2238 .CNT_INITIALIZED_DATA = true,
2239 .MEM_READ = true,
2240 .MEM_WRITE = true,
2241 });
2242
2243 // While tls variables allocated at runtime are writable, the template itself is not.
2244 // In images, the template is in a .tls pseudo section in .rdata.
2245 // In objects / archives, this section is part of the above .tls$ section. The suffix
2246 // is maintained so merging can occur with other input tls symbols when linked later.
2247 _ = try coff.pseudoSectionMapIndex(
2248 if (is_image) .@".tls" else .@".tls$",
2249 coff.mf.flags.block_size,
2250 .{ .read = true, .write = !is_image, .initialized = true },
2251 );
2252 }
2253}
2254
2255pub fn initBuiltins(coff: *Coff) !void {
2256 const comp = coff.base.comp;
2257 const gpa = comp.gpa;
2258 const target = &comp.root_mod.resolved_target.result;
2259 if (coff.isImage()) {
2260 const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data });
2261 const sym = si.get(coff);
2262 sym.ni = .wrap(Node.known.header);
2263 }
2264
2265 defer coff.flushSectionMerges() catch unreachable;
2266 if (coff.isImage() and target.isMinGW() and comp.config.link_libc) {
2267 try coff.symbols.ensureUnusedCapacity(gpa, 8);
2268 try coff.globals.ensureUnusedCapacity(gpa, 2);
2269 try coff.nodes.ensureUnusedCapacity(gpa, 8);
2270 try coff.section_merges.ensureUnusedCapacity(gpa, 2);
2271
2272 const lists: []const struct { global: []const u8, start: String, end: String } = &.{
2273 .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" },
2274 .{ .global = "__DTOR_LIST__", .start = .@".dtors", .end = .@".dtors$ZZZ" },
2275 };
2276
2277 // We need to explicitly merge these into .rdata as in objects they can be marked
2278 // as MEM_WRITE, and would have mismatced section flags.
2279 try coff.section_merges.put(gpa, .@".ctors", .@".rdata");
2280 try coff.section_merges.put(gpa, .@".dtors", .@".rdata");
2281
2282 for (lists) |list| {
2283 const addr_info = coff.targetAddrInfo();
2284
2285 // Any .(c|d)tor$(.*) input sections will merge in between these sections
2286 const start_osmi = try coff.objectSectionMapIndex(
2287 list.start,
2288 addr_info.alignment,
2289 .{ .read = true, .initialized = true },
2290 );
2291 const end_osmi = try coff.objectSectionMapIndex(
2292 list.end,
2293 addr_info.alignment,
2294 .{ .read = true, .initialized = true },
2295 );
2296
2297 // Additional nodes are used here, instead of just adding the sentinel
2298 // directly to the section data, since once input sections are added
2299 // as children, they would overwrite that data.
2300 const start_sym = start_osmi.symbol(coff).get(coff);
2301 const list_len_si = try coff.globalSymbol(.{ .name = list.global, .type = .data });
2302 const list_len_sym = list_len_si.get(coff);
2303 list_len_sym.setExtra(.{ .size = addr_info.size });
2304 list_len_sym.ni = .wrap(try start_sym.ni.unwrap().?.addHeaderChildAfter(gpa, &coff.mf, .none, .{
2305 .size = addr_info.size,
2306 }));
2307 coff.nodes.appendAssumeCapacity(.{ .builtin = list_len_si });
2308 list_len_sym.section_number = start_sym.section_number;
2309
2310 const start_slice = list_len_sym.ni.unwrap().?.slice(&coff.mf);
2311 switch (addr_info.magic) {
2312 _ => unreachable,
2313 inline .PE32, .@"PE32+" => |t| {
2314 const addr: *TargetAddr(t) = @ptrCast(@alignCast(start_slice));
2315 // For __CTOR_LIST__ -1 indicates that the list is null terminated.
2316 // For __DTOR_LIST__, this value is ignored, the list is always null terminated
2317 coff.targetStore(addr, std.math.maxInt(TargetAddr(t)));
2318 },
2319 }
2320
2321 const end_sym = end_osmi.symbol(coff).get(coff);
2322 const list_end_si = coff.addSymbolAssumeCapacity();
2323 const list_end_sym = list_end_si.get(coff);
2324 list_end_sym.setExtra(.{ .size = addr_info.size });
2325 list_end_sym.ni = .wrap(try end_sym.ni.unwrap().?.addHeaderChildAfter(gpa, &coff.mf, .none, .{
2326 .size = addr_info.size,
2327 }));
2328 coff.nodes.appendAssumeCapacity(.{ .builtin = list_end_si });
2329 list_end_sym.section_number = start_sym.section_number;
2330
2331 @memset(list_end_sym.ni.unwrap().?.slice(&coff.mf), 0);
2332
2333 try list_len_si.flushMoved(coff);
2334 try list_end_si.flushMoved(coff);
2335 }
2336 }
2337}
2338
2339pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void {
2340 prog_node.increaseEstimatedTotalItems(3);
2341 coff.const_prog_node = prog_node.start("Constants", coff.pending_uavs.count());
2342 coff.synth_prog_node = prog_node.start("Synthetics", count: {
2343 var count =
2344 coff.globals.count() - coff.global_pending_index +
2345 coff.section_merges.count() - coff.section_merge_pending_index;
2346
2347 for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index;
2348 break :count count;
2349 });
2350 if (!isImage(coff)) {
2351 prog_node.increaseEstimatedTotalItems(2);
2352 coff.symbol_prog_node = prog_node.start(
2353 "Symbols",
2354 coff.symbol_table.symbols.count() - coff.symbol_table.pending_symbol_index,
2355 );
2356 coff.member_prog_node = prog_node.start("Members", coff.pending_members.count());
2357 }
2358 coff.input_prog_node = prog_node.start(
2359 "Inputs",
2360 coff.input_sections.items.len - coff.input_section_pending_index,
2361 );
2362 coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len);
2363}
2364
2365pub fn endProgress(coff: *Coff) void {
2366 coff.mf.update_prog_node.end();
2367 coff.mf.update_prog_node = .none;
2368 coff.input_prog_node.end();
2369 coff.input_prog_node = .none;
2370 if (!coff.isImage()) {
2371 coff.member_prog_node.end();
2372 coff.member_prog_node = .none;
2373 coff.symbol_prog_node.end();
2374 coff.symbol_prog_node = .none;
2375 }
2376 coff.synth_prog_node.end();
2377 coff.synth_prog_node = .none;
2378 coff.const_prog_node.end();
2379 coff.const_prog_node = .none;
2380}
2381
2382fn getNode(coff: *const Coff, ni: MappedFile.Node.Index) Node {
2383 return coff.nodes.get(@backingInt(ni));
2384}
2385fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
2386 const parent_rva = parent_rva: {
2387 const parent_si = switch (coff.getNode(ni.parent(&coff.mf).unwrap().?)) {
2388 .file,
2389 .header,
2390 .signature,
2391 .archive_member_header,
2392 .archive_member,
2393 .coff_header,
2394 .optional_header,
2395 .data_directories,
2396 .section_table,
2397 .export_name_table,
2398 .placeholder,
2399 .symbol_table,
2400 .string_table,
2401 .relocation_table,
2402 .relocation_table_entry,
2403 .input_section,
2404 .builtin,
2405 => unreachable,
2406 .image_section => |si| si,
2407 .import_directory_table => break :parent_rva coff.targetLoad(
2408 &coff.dataDirectoryPtr(.IMPORT).virtual_address,
2409 ),
2410 .import_lookup_table => |import_index| break :parent_rva coff.targetLoad(
2411 &coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva,
2412 ),
2413 .import_address_table => |import_index| break :parent_rva coff.targetLoad(
2414 &coff.importDirectoryEntryPtr(import_index).import_address_table_rva,
2415 ),
2416 .import_hint_name_table => |import_index| break :parent_rva coff.targetLoad(
2417 &coff.importDirectoryEntryPtr(import_index).name_rva,
2418 ),
2419 .export_directory_table => break :parent_rva coff.targetLoad(
2420 &coff.dataDirectoryPtr(.EXPORT).virtual_address,
2421 ),
2422 .export_address_table => break :parent_rva coff.targetLoad(
2423 &coff.exportDirectoryTable().export_address_table_rva,
2424 ),
2425 .export_name_pointer_table => break :parent_rva coff.targetLoad(
2426 &coff.exportDirectoryTable().name_pointer_table_rva,
2427 ),
2428 .export_ordinal_table => break :parent_rva coff.targetLoad(
2429 &coff.exportDirectoryTable().ordinal_table_rva,
2430 ),
2431 inline .pseudo_section,
2432 .object_section,
2433 .import_thunk,
2434 .nav,
2435 .uav,
2436 .lazy_code,
2437 .lazy_const_data,
2438 => |mi| mi.symbol(coff),
2439 };
2440 break :parent_rva parent_si.get(coff).rva;
2441 };
2442 const offset, _ = ni.location(&coff.mf).resolve(&coff.mf);
2443 return @intCast(parent_rva + offset);
2444}
2445
2446fn computeSymbolSectionOffset(
2447 coff: *Coff,
2448 sym: *const Symbol,
2449 relative_to: enum { image, pseudo },
2450) u32 {
2451 var section_offset: u32 = sym.nodeOffset(coff);
2452 var parent_ni = sym.ni.unwrap().?;
2453 while (true) {
2454 const offset, _ = parent_ni.location(&coff.mf).resolve(&coff.mf);
2455 section_offset += @intCast(offset);
2456 parent_ni = parent_ni.parent(&coff.mf).unwrap().?;
2457 switch (coff.getNode(parent_ni)) {
2458 else => unreachable,
2459 .image_section => break,
2460 .pseudo_section => if (relative_to == .pseudo) break,
2461 .object_section,
2462 => {},
2463 }
2464 }
2465
2466 return section_offset;
2467}
2468
2469pub inline fn targetEndian(_: *const Coff) std.lang.Endian {
2470 return .little;
2471}
2472
2473fn targetAddrInfo(coff: *Coff) struct {
2474 size: u8,
2475 alignment: Alignment,
2476 magic: std.coff.OptionalHeader.Magic,
2477} {
2478 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
2479 switch (magic) {
2480 _ => unreachable,
2481 .PE32 => return .{ .size = 4, .alignment = .@"4", .magic = magic },
2482 .@"PE32+" => return .{ .size = 8, .alignment = .@"8", .magic = magic },
2483 }
2484}
2485
2486fn TargetAddr(comptime magic: std.coff.OptionalHeader.Magic) type {
2487 return switch (magic) {
2488 _ => comptime unreachable,
2489 .PE32 => u32,
2490 .@"PE32+" => u64,
2491 };
2492}
2493
2494fn targetLoad(coff: *const Coff, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
2495 const Child = @typeInfo(@TypeOf(ptr)).pointer.child;
2496 return switch (@typeInfo(Child)) {
2497 else => @compileError(@typeName(Child)),
2498 .int => std.mem.toNative(Child, ptr.*, coff.targetEndian()),
2499 .@"enum" => |@"enum"| @fromBackingInt(@intCast(coff.targetLoad(@as(*@"enum".tag_type, @ptrCast(ptr))))),
2500 .@"struct" => |@"struct"| @bitCast(
2501 coff.targetLoad(@as(*@"struct".backing_integer.?, @ptrCast(ptr))),
2502 ),
2503 };
2504}
2505fn targetStore(coff: *const Coff, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).pointer.child) void {
2506 const Child = @typeInfo(@TypeOf(ptr)).pointer.child;
2507 return switch (@typeInfo(Child)) {
2508 else => @compileError(@typeName(Child)),
2509 .int => ptr.* = std.mem.nativeTo(Child, val, coff.targetEndian()),
2510 .@"enum" => |@"enum"| coff.targetStore(
2511 @as(*@"enum".tag_type, @ptrCast(ptr)),
2512 @backingInt(val),
2513 ),
2514 .@"struct" => |@"struct"| coff.targetStore(
2515 @as(*@"struct".backing_integer.?, @ptrCast(ptr)),
2516 @bitCast(val),
2517 ),
2518 };
2519}
2520
2521pub fn headerPtr(coff: *Coff) *std.coff.Header {
2522 assert(coff.hasCoffHeader());
2523 return @ptrCast(@alignCast(Node.known.coff_header.slice(&coff.mf)));
2524}
2525
2526pub fn firstLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 {
2527 assert(coff.isArchive());
2528 return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)));
2529}
2530
2531pub fn firstLinkerMemberOffsetsSlice(coff: *Coff) []u32 {
2532 const len = std.mem.toNative(u32, coff.firstLinkerMemberNumSymbolsPtr().*, .big);
2533 return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. len * @sizeOf(u32)]));
2534}
2535
2536pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *align(2) u32 {
2537 assert(coff.isArchive());
2538 return @ptrCast(@alignCast(Node.known.second_linker_member.slice(&coff.mf)));
2539}
2540
2541pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []align(2) u32 {
2542 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2543 return @ptrCast(@alignCast(
2544 Node.known.second_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. num_members * @sizeOf(u32)],
2545 ));
2546}
2547
2548pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *align(2) u32 {
2549 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2550 return @ptrCast(@alignCast(
2551 Node.known.second_linker_member.slice(&coff.mf)[(1 + num_members) * @sizeOf(u32) ..],
2552 ));
2553}
2554
2555pub fn secondLinkerMemberIndicesSlice(coff: *Coff) []u16 {
2556 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2557 const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
2558 return @ptrCast(@alignCast(
2559 Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) ..][0 .. num_symbols * @sizeOf(u16)],
2560 ));
2561}
2562
2563pub fn secondLinkerMemberStringsSlice(coff: *Coff) []u8 {
2564 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2565 const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
2566 return @ptrCast(@alignCast(
2567 Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) + num_symbols * @sizeOf(u16) ..],
2568 ));
2569}
2570
2571pub fn optionalHeaderStandardPtr(coff: *Coff) *std.coff.OptionalHeader {
2572 return @ptrCast(@alignCast(
2573 Node.known.optional_header.slice(&coff.mf)[0..@sizeOf(std.coff.OptionalHeader)],
2574 ));
2575}
2576
2577pub const OptionalHeaderPtr = union(std.coff.OptionalHeader.Magic) {
2578 PE32: *std.coff.OptionalHeader.PE32,
2579 @"PE32+": *std.coff.OptionalHeader.@"PE32+",
2580};
2581pub fn optionalHeaderPtr(coff: *Coff) OptionalHeaderPtr {
2582 assert(coff.isImage());
2583 const slice = Node.known.optional_header.slice(&coff.mf);
2584 return switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
2585 _ => unreachable,
2586 inline else => |magic| @unionInit(
2587 OptionalHeaderPtr,
2588 @tagName(magic),
2589 @ptrCast(@alignCast(slice)),
2590 ),
2591 };
2592}
2593pub fn optionalHeaderField(
2594 coff: *Coff,
2595 comptime field: std.meta.FieldEnum(std.coff.OptionalHeader.@"PE32+"),
2596) @FieldType(std.coff.OptionalHeader.@"PE32+", @tagName(field)) {
2597 assert(coff.isImage());
2598 return switch (coff.optionalHeaderPtr()) {
2599 inline else => |optional_header| coff.targetLoad(&@field(optional_header, @tagName(field))),
2600 };
2601}
2602
2603pub fn dataDirectorySlice(
2604 coff: *Coff,
2605) *[std.coff.IMAGE.DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory {
2606 assert(coff.isImage());
2607 return @ptrCast(@alignCast(Node.known.data_directories.slice(&coff.mf)));
2608}
2609pub fn dataDirectoryPtr(
2610 coff: *Coff,
2611 entry: std.coff.IMAGE.DIRECTORY_ENTRY,
2612) *std.coff.ImageDataDirectory {
2613 return &coff.dataDirectorySlice()[@backingInt(entry)];
2614}
2615
2616pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader {
2617 return @ptrCast(@alignCast(
2618 Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.count() * @sizeOf(std.coff.SectionHeader)],
2619 ));
2620}
2621
2622pub fn symbolTableEntryStoragePtr(coff: *Coff, index: u32) *[std.coff.Symbol.sizeOf()]u8 {
2623 assert(!coff.isImage());
2624 const offset = index * std.coff.Symbol.sizeOf();
2625 return @ptrCast(@alignCast(coff.symbol_table.ni.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()]));
2626}
2627
2628pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.Symbol {
2629 if (sti.unwrap()) |index|
2630 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, index)))
2631 else
2632 return null;
2633}
2634
2635pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.SectionDefinition {
2636 if (symbolTableEntryPtr(coff, sti)) |entry| {
2637 assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1);
2638 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
2639 } else {
2640 return null;
2641 }
2642}
2643
2644pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.WeakExternalDefinition {
2645 if (symbolTableEntryPtr(coff, sti)) |entry| {
2646 assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1);
2647 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
2648 } else {
2649 return null;
2650 }
2651}
2652
2653pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 {
2654 return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)]));
2655}
2656
2657pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry {
2658 assert(coff.isImage());
2659 return @ptrCast(@alignCast(coff.import_table.ni.slice(&coff.mf)));
2660}
2661pub fn importDirectoryEntryPtr(
2662 coff: *Coff,
2663 import_index: ImportTable.Index,
2664) *std.coff.ImportDirectoryEntry {
2665 return &coff.importDirectoryTableSlice()[@backingInt(import_index)];
2666}
2667
2668pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable {
2669 return @ptrCast(@alignCast(coff.export_table.export_directory_table_ni.slice(&coff.mf)));
2670}
2671
2672pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry {
2673 const debug = coff.export_table.name_pointer_table_ni.slice(&coff.mf);
2674 _ = debug;
2675
2676 return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf)));
2677}
2678
2679pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry {
2680 return @ptrCast(@alignCast(coff.export_table.ordinal_table_ni.slice(&coff.mf)));
2681}
2682
2683fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
2684 defer coff.symbols.addOneAssumeCapacity().* = .{
2685 .ni = .none,
2686 .rva = 0,
2687 .value = .{ .none = {} },
2688 .extra = .{ .size = 0 },
2689 .flags = .{
2690 .value_tag = .none,
2691 .extra_tag = .size,
2692 .type = .unknown,
2693 .dll_storage_class = .default,
2694 .weak_external_strat = .none,
2695 },
2696 .loc_relocs = .none,
2697 .target_relocs = .none,
2698 .section_number = .UNDEFINED,
2699 .gmi = .none,
2700 };
2701 return @fromBackingInt(@intCast(coff.symbols.items.len));
2702}
2703
2704fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index {
2705 const si = coff.addSymbolAssumeCapacity();
2706 return si;
2707}
2708
2709fn getOrPutString(coff: *Coff, string: []const u8) !String {
2710 try coff.ensureUnusedStringCapacity(string.len);
2711 return coff.getOrPutStringAssumeCapacity(string);
2712}
2713fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
2714 return (try coff.getOrPutString(string orelse return .none)).toOptional();
2715}
2716fn getString(coff: *Coff, string: []const u8) String.Optional {
2717 if (coff.strings.getKeyAdapted(
2718 string,
2719 std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes },
2720 )) |key|
2721 return @as(String, @fromBackingInt(@intCast(key))).toOptional()
2722 else
2723 return .none;
2724}
2725
2726/// If the name does not fit in the symbol header, adds it to the symbol table string table.
2727/// If the caller knows this name already has a String associated with it, they can avoid
2728/// a redundant call to `getOrPutString` by specifying `opt_string`.
2729/// The lifetime of the return value matches that of `name`.
2730fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !SymbolTable.SymbolName {
2731 assert(!coff.isImage());
2732 const gpa = coff.base.comp.gpa;
2733
2734 return if (name.len > header_name_max_len) name: {
2735 const string = opt_string orelse try coff.getOrPutString(name);
2736 const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string);
2737 if (!string_gop.found_existing) {
2738 const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1];
2739 string_gop.value_ptr.* = @fromBackingInt(@intCast(string_index));
2740
2741 try coff.symbol_table.strings_ni.resizeLeaf(gpa, &coff.mf, string_index + name.len + 1);
2742 const slice = coff.symbol_table.strings_ni.slice(&coff.mf);
2743 @memcpy(slice[@intCast(string_index)..][0..name.len], name);
2744 slice[@intCast(string_index + name.len)] = 0;
2745 }
2746
2747 break :name .{ .long = string_gop.value_ptr.* };
2748 } else .{ .short = name };
2749}
2750
2751/// `len` does not include null terminators
2752fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void {
2753 const gpa = coff.base.comp.gpa;
2754 try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes });
2755 try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1);
2756}
2757
2758/// `total_len` includes null terminators
2759fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void {
2760 const gpa = coff.base.comp.gpa;
2761 try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes });
2762 try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings);
2763}
2764
2765fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
2766 const gop = coff.strings.getOrPutAssumeCapacityAdapted(
2767 string,
2768 std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes },
2769 );
2770 if (!gop.found_existing) {
2771 gop.key_ptr.* = @intCast(coff.string_bytes.items.len);
2772 gop.value_ptr.* = {};
2773 coff.string_bytes.appendSliceAssumeCapacity(string);
2774 coff.string_bytes.appendAssumeCapacity(0);
2775 }
2776 return @fromBackingInt(@intCast(gop.key_ptr.*));
2777}
2778
2779const GlobalOptions = struct {
2780 name: []const u8,
2781 lib_name: ?[]const u8 = null,
2782 type: Symbol.Type = .unknown,
2783 dll_storage_class: Symbol.DllStorageClass = .default,
2784};
2785
2786fn getOrPutGlobalSymbol(
2787 coff: *Coff,
2788 opts: GlobalOptions,
2789) !std.array_hash_map.Auto(String, Global).GetOrPutResult {
2790 const comp = coff.base.comp;
2791 const gpa = comp.gpa;
2792 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2793
2794 const lib_name: String.Optional = if (opts.lib_name) |lib_name| lib_name: {
2795 const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name);
2796 if (is_libc) {
2797 // This is guaranteed by Sema.handleExternLibName
2798 if (!comp.config.link_libc) unreachable;
2799
2800 // TODO: The user has requested this symbol come from libc, but this logic allows
2801 // it to come from anywhere. We need to know what inputs are libc inputs,
2802 // and set a flag to only search them for this symbol.
2803 break :lib_name .none;
2804 }
2805
2806 break :lib_name (try coff.getOrPutString(lib_name)).toOptional();
2807 } else .none;
2808
2809 const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name));
2810 if (!sym_gop.found_existing) {
2811 const si = coff.addSymbolAssumeCapacity();
2812 const sym = si.get(coff);
2813 sym.gmi = .wrap(@intCast(sym_gop.index));
2814 sym.flags.type = opts.type;
2815 sym.flags.dll_storage_class = opts.dll_storage_class;
2816 sym_gop.value_ptr.* = .{
2817 .si = si,
2818 .lib_name = lib_name,
2819 };
2820 coff.synth_prog_node.increaseEstimatedTotalItems(1);
2821
2822 log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si });
2823 }
2824
2825 return sym_gop;
2826}
2827
2828fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index {
2829 if (coff.globals.get(
2830 coff.getString(name).unwrap() orelse return .null,
2831 )) |global| if (global.si.get(coff).ni != .none) return global.si;
2832 return .null;
2833}
2834
2835pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
2836 const gop = try coff.getOrPutGlobalSymbol(opts);
2837 return gop.value_ptr.si;
2838}
2839
2840pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
2841 assert(!coff.isImage());
2842 const sym = si.get(coff);
2843
2844 assert(sym.ni != .none or sym.gmi != .none);
2845 const gpa = coff.base.comp.gpa;
2846 const gop = try coff.symbol_table.symbols.getOrPut(gpa, si);
2847 if (!gop.found_existing) {
2848 coff.symbol_prog_node.increaseEstimatedTotalItems(1);
2849 gop.value_ptr.* = .none;
2850 }
2851}
2852
2853fn navSection(
2854 coff: *Coff,
2855 zcu: *Zcu,
2856 nav_resolved: @typeInfo(@FieldType(InternPool.Nav, "resolved")).optional.child,
2857) !Symbol.Index {
2858 const ip = &zcu.intern_pool;
2859 const default: String, const attributes: ObjectSectionAttributes =
2860 if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{
2861 .@".tls$", .{ .read = true, .write = true, .initialized = true },
2862 } else if (ip.isFunctionType(nav_resolved.type)) .{
2863 .@".text", .{ .read = true, .execute = true },
2864 } else if (nav_resolved.@"const") .{
2865 .@".rdata", .{ .read = true, .initialized = true },
2866 } else .{
2867 .@".data", .{ .read = true, .write = true, .initialized = true },
2868 };
2869
2870 return (try coff.objectSectionMapIndex(
2871 (try coff.getOrPutOptionalString(nav_resolved.@"linksection".toSlice(ip))).unwrap() orelse default,
2872 switch (nav_resolved.@"linksection") {
2873 .none => coff.mf.flags.block_size,
2874 else => switch (nav_resolved.@"align") {
2875 .none => .fromIp(Type.fromInterned(ip.typeOf(nav_resolved.value)).abiAlignment(zcu)),
2876 else => |a| .fromIp(a),
2877 },
2878 },
2879 attributes,
2880 )).symbol(coff);
2881}
2882fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex {
2883 const gpa = zcu.gpa;
2884 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2885 const sym_gop = try coff.navs.getOrPut(gpa, nav_index);
2886 if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
2887 return @fromBackingInt(@intCast(sym_gop.index));
2888}
2889pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index {
2890 const ip = &zcu.intern_pool;
2891 const nav = ip.getNav(nav_index);
2892 if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{
2893 .name = @"extern".name.toSlice(ip),
2894 .lib_name = @"extern".lib_name.toSlice(ip),
2895 // TODO: Threadlocal as well?
2896 .type = if (ip.isFunctionType(nav.resolved.?.type)) .code else .data,
2897 .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default,
2898 });
2899 const nmi = try coff.navMapIndex(zcu, nav_index);
2900 return nmi.symbol(coff);
2901}
2902
2903fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex {
2904 const gpa = coff.base.comp.gpa;
2905 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2906 const sym_gop = try coff.uavs.getOrPut(gpa, uav_val);
2907 if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
2908 return @fromBackingInt(@intCast(sym_gop.index));
2909}
2910pub fn uavSymbol(coff: *Coff, uav_val: InternPool.Index) !Symbol.Index {
2911 const umi = try coff.uavMapIndex(uav_val);
2912 return umi.symbol(coff);
2913}
2914
2915pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index {
2916 const gpa = coff.base.comp.gpa;
2917 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2918 const sym_gop = try coff.lazy.getPtr(lazy.kind).map.getOrPut(gpa, lazy.ty);
2919 if (!sym_gop.found_existing) {
2920 sym_gop.value_ptr.* = try coff.initSymbolAssumeCapacity();
2921 coff.synth_prog_node.increaseEstimatedTotalItems(1);
2922 }
2923 return sym_gop.value_ptr.*;
2924}
2925
2926pub fn getNavVAddr(
2927 coff: *Coff,
2928 pt: Zcu.PerThread,
2929 nav: InternPool.Nav.Index,
2930 reloc_info: link.File.RelocInfo,
2931) link.Error!u64 {
2932 return coff.getVAddr(reloc_info, try coff.navSymbol(pt.zcu, nav));
2933}
2934
2935pub fn getUavVAddr(
2936 coff: *Coff,
2937 uav: InternPool.Index,
2938 reloc_info: link.File.RelocInfo,
2939) link.Error!u64 {
2940 return coff.getVAddr(reloc_info, try coff.uavSymbol(uav));
2941}
2942
2943pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) link.Error!u64 {
2944 try coff.addReloc(
2945 @fromBackingInt(@intCast(@backingInt(reloc_info.parent.atom_index))),
2946 reloc_info.offset,
2947 target_si,
2948 .{ .known = reloc_info.addend },
2949 switch (coff.targetLoad(&coff.headerPtr().machine)) {
2950 else => unreachable,
2951 .AMD64 => .{ .AMD64 = .ADDR64 },
2952 .I386 => .{ .I386 = .DIR32 },
2953 },
2954 );
2955
2956 var vaddr: u64 = target_si.get(coff).rva;
2957 if (coff.isImage()) vaddr += coff.optionalHeaderField(.image_base);
2958 return vaddr;
2959}
2960
2961/// Caller guarantees there is capacity for one member and two nodes
2962fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: u64) !Member.Index {
2963 const comp = coff.base.comp;
2964 const gpa = comp.gpa;
2965
2966 const header_ni = try Node.known.file.addHeaderChildAfter(gpa, &coff.mf, Node.known.file.last(&coff.mf), .{
2967 .size = @sizeOf(std.coff.ArchiveMemberHeader),
2968 .alignment = .@"2",
2969 .moved = true,
2970 });
2971
2972 // The actual alignment required by the spec is 2, but to allow aligned access to
2973 // the various COFF data structures in-place during linking we overalign
2974 const content_align: Alignment = switch (kind) {
2975 .first_linker, .second_linker, .longnames, .coff => .@"4",
2976 else => .@"2",
2977 };
2978 const content_ni = try Node.known.file.addHeaderChildAfter(gpa, &coff.mf, .wrap(header_ni), .{
2979 .alignment = content_align,
2980 .size = content_align.forward(size),
2981 .resized = size > 0,
2982 });
2983
2984 const mi: Member.Index = @fromBackingInt(@intCast(coff.members.items.len));
2985 coff.members.appendAssumeCapacity(.{
2986 .kind = kind,
2987 .header_ni = header_ni,
2988 .content_ni = content_ni,
2989 .first_linker_indices = .empty,
2990 });
2991
2992 coff.nodes.appendAssumeCapacity(.{ .archive_member_header = mi });
2993 coff.nodes.appendAssumeCapacity(.{ .archive_member = mi });
2994
2995 switch (kind) {
2996 .first_linker, .second_linker, .longnames => {},
2997 else => {
2998 const new_num_members = coff.members.items.len - Member.Index.known_count;
2999 coff.targetStore(
3000 coff.secondLinkerMemberNumMembersPtr(),
3001 @intCast(new_num_members),
3002 );
3003
3004 const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1];
3005 const old_header_size = new_num_members * @sizeOf(u32);
3006 const trailing_size: usize = @intCast(old_size - old_header_size);
3007 try Node.known.second_linker_member.resizeLeaf(gpa, &coff.mf, old_size + @sizeOf(u32));
3008
3009 const slice = Node.known.second_linker_member.slice(&coff.mf);
3010 @memmove(
3011 slice[old_header_size + @sizeOf(u32) ..][0..trailing_size],
3012 slice[old_header_size..][0..trailing_size],
3013 );
3014
3015 // Offset will be written by flushMoved on header_ni
3016 },
3017 }
3018
3019 switch (kind) {
3020 .first_linker,
3021 .longnames,
3022 .import,
3023 => {},
3024 .second_linker,
3025 .coff,
3026 => {
3027 try coff.pending_members.ensureTotalCapacity(
3028 gpa,
3029 coff.pending_members.capacity() + 1,
3030 );
3031 coff.member_prog_node.increaseEstimatedTotalItems(1);
3032 },
3033 }
3034
3035 return mi;
3036}
3037
3038fn appendMemberSymbolString(
3039 coff: *Coff,
3040 strings_ni: MappedFile.Node.Index,
3041 new_size: u64,
3042 name: []const u8,
3043 offset: u64,
3044) !void {
3045 try strings_ni.resizeLeaf(&coff.mf, coff.base.comp.gpa, new_size);
3046 const name_slice = strings_ni.slice(&coff.mf)[offset..][0 .. name.len + 1];
3047 @memcpy(name_slice[0..name.len], name);
3048 name_slice[name.len] = 0;
3049}
3050
3051fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
3052 const gpa = coff.base.comp.gpa;
3053 const member = mi.get(coff);
3054 assert(member.kind == .coff);
3055
3056 const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name });
3057 if (gop.found_existing) return;
3058
3059 const mfli: Member.FirstLinkerIndex = blk: {
3060 const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr();
3061 const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big);
3062 num_symbols_ptr.* = std.mem.nativeTo(u32, num_symbols + 1, .big);
3063 break :blk @fromBackingInt(@intCast(num_symbols));
3064 };
3065
3066 gop.value_ptr.* = mfli;
3067
3068 // Linker member fields are not modeled as nodes because MappedFile
3069 // can't guarantee that they will be tightly packed after resizing
3070
3071 const name_slice = name.toSlice(coff);
3072 const new_string_table_size: u32 = @intCast(coff.lib_string_len + name_slice.len + 1);
3073 defer coff.lib_string_len = new_string_table_size;
3074
3075 {
3076 const old_header_size: usize = @intCast(@sizeOf(u32) + @backingInt(mfli) * @sizeOf(u32));
3077 const new_header_size: usize = @intCast(old_header_size + @sizeOf(u32));
3078 try Node.known.first_linker_member.resizeLeaf(gpa, &coff.mf, Alignment.@"4".forward(new_header_size + new_string_table_size));
3079
3080 const slice = Node.known.first_linker_member.slice(&coff.mf);
3081 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
3082 @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
3083 slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
3084
3085 // New offset entry is written in flushMember
3086 }
3087
3088 {
3089 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
3090 const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + @backingInt(mfli) * @sizeOf(u16);
3091 const new_header_size = old_header_size + @sizeOf(u16);
3092 try Node.known.second_linker_member.resizeLeaf(gpa, &coff.mf, Alignment.@"4".forward(new_header_size + new_string_table_size));
3093
3094 const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
3095 const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
3096 std.mem.lessThan(
3097 u8,
3098 name_slice,
3099 coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff),
3100 )
3101 else
3102 false);
3103
3104 try coff.lib_string_table.append(gpa, name);
3105
3106 const slice = Node.known.second_linker_member.slice(&coff.mf);
3107 coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), @backingInt(mfli) + 1);
3108 if (!needs_sort) {
3109 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
3110 @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
3111 slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
3112 } else if (!old_needs_sort) {
3113 // The entire string table is rebuilt in flushMember after sorting
3114 coff.pending_members.putAssumeCapacity(Member.Index.second, {});
3115 }
3116
3117 // Indices in this table are 1-based
3118 const index_ptr: *u16 = @ptrCast(@alignCast(slice[old_header_size..]));
3119 coff.targetStore(index_ptr, @intCast(@backingInt(mi) - Member.Index.known_count + 1));
3120 }
3121
3122 coff.pending_members.putAssumeCapacity(mi, {});
3123 coff.member_prog_node.increaseEstimatedTotalItems(1);
3124}
3125
3126fn flushSymbolTableEntry(coff: *Coff, index: u32) !void {
3127 assert(!coff.isImage());
3128 const gpa = coff.base.comp.gpa;
3129
3130 const si = coff.symbol_table.symbols.keys()[index];
3131 const sti = &coff.symbol_table.symbols.values()[index];
3132
3133 const sym = si.get(coff);
3134 assert(sym.ni != .none or sym.gmi != .none);
3135
3136 const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: {
3137 const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType =
3138 if (sym.gmi != .none) blk: {
3139 const name = sym.gmi.name(coff);
3140 break :blk .{
3141 try coff.getOrPutSymbolName(name.toSlice(coff), name),
3142 @intFromBool(sym.flags.weak_external_strat != .none),
3143 if (Symbol.Index.text.get(coff).section_number == sym.section_number)
3144 .FUNCTION
3145 else
3146 .NULL,
3147 };
3148 } else blk: switch (coff.getNode(sym.ni.unwrap().?)) {
3149 .image_section => .{
3150 try coff.getOrPutSymbolName(&sym.section_number.header(coff).name, null),
3151 1,
3152 .NULL,
3153 },
3154 .nav => |nmi| {
3155 const zcu = coff.base.comp.zcu.?;
3156 const ip = &zcu.intern_pool;
3157 const nav = ip.getNav(nmi.navIndex(coff));
3158 break :blk .{
3159 try coff.getOrPutSymbolName(nav.fqn.toSlice(ip), null),
3160 0,
3161 if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL,
3162 };
3163 },
3164 .uav => |umi| {
3165 var name_buf: [std.fmt.count("__anon_{d}", .{std.math.maxInt(u32)})]u8 = undefined;
3166 const name = std.mem.print(&name_buf, "__anon_{d}", .{umi}) catch unreachable;
3167 break :blk .{
3168 try coff.getOrPutSymbolName(name, null),
3169 0,
3170 .NULL,
3171 };
3172 },
3173 inline .lazy_code, .lazy_const_data => |mi, tag| {
3174 const lazy_sym = mi.lazySymbol(coff);
3175 var name_buf: [
3176 std.fmt.count("__lazy_const_data_{d}", .{std.math.maxInt(u32)})
3177 ]u8 = undefined;
3178 const name = std.mem.print(&name_buf, "__lazy_{t}_{d}", .{
3179 lazy_sym.kind, mi,
3180 }) catch unreachable;
3181
3182 const string = try coff.getOrPutString(name);
3183 break :blk .{
3184 try coff.getOrPutSymbolName(string.toSlice(coff), string),
3185 0,
3186 if (tag == .lazy_code) .FUNCTION else .NULL,
3187 };
3188 },
3189 else => {
3190 log.err("TODO implement symbol table init for {s} ({d})", .{ @tagName(coff.getNode(sym.ni.unwrap().?)), si });
3191 unreachable;
3192 },
3193 };
3194
3195 const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
3196 const new_num_symbols = old_num_symbols + 1 + num_aux_symbols;
3197 coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
3198
3199 try coff.symbol_table.ni.resizeLeaf(gpa, &coff.mf, new_num_symbols * std.coff.Symbol.sizeOf());
3200
3201 sti.* = .wrap(old_num_symbols);
3202 si.flushSymbolTableIndex(coff);
3203
3204 const entry = coff.symbolTableEntryPtr(sti.*).?;
3205 symbol_name.store(coff, &entry.name);
3206
3207 entry.section_number = @fromBackingInt(@intCast(@backingInt(sym.section_number)));
3208 entry.type = .{
3209 .complex_type = complex_type,
3210 .base_type = .NULL,
3211 };
3212
3213 entry.storage_class = if (sym.gmi != .none)
3214 .EXTERNAL
3215 else if (sym.flags.extra_tag == .next_alias_si) storage: {
3216 var alias_sym = sym;
3217 const weak_external = while (alias_sym.flags.extra_tag == .next_alias_si) {
3218 const alias_si = alias_sym.extra.next_alias_si;
3219 alias_sym = alias_si.get(coff);
3220 assert(alias_sym.ni == sym.ni);
3221 if (alias_sym.flags.weak_external_strat != .none)
3222 break true;
3223 } else false;
3224 break :storage if (weak_external) .EXTERNAL else .STATIC;
3225 } else .STATIC;
3226
3227 entry.number_of_aux_symbols = num_aux_symbols;
3228 if (coff.targetEndian() != native_endian)
3229 std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry);
3230
3231 if (num_aux_symbols > 0) aux_init: {
3232 if (sym.gmi != .none) {
3233 entry.section_number = .UNDEFINED;
3234 entry.storage_class = .WEAK_EXTERNAL;
3235
3236 const tag_index = sym.value.weak_alias_si.sti(coff).unwrap().?;
3237 const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(sti.*).?;
3238 aux_ptr.* = .{
3239 .tag_index = tag_index,
3240 .flag = switch (sym.flags.weak_external_strat) {
3241 .none => unreachable,
3242 .no_library => .SEARCH_NOLIBRARY,
3243 .library => .SEARCH_LIBRARY,
3244 .alias => .SEARCH_ALIAS,
3245 .anti_dependency => .ANTI_DEPENDENCY,
3246 },
3247 .unused = @splat(0),
3248 };
3249 if (coff.targetEndian() != native_endian)
3250 std.mem.byteSwapAllFieldsAligned(std.coff.WeakExternalDefinition, .@"2", aux_ptr);
3251
3252 break :aux_init;
3253 } else switch (coff.getNode(sym.ni.unwrap().?)) {
3254 .image_section => |sec_si| {
3255 assert(si == sec_si);
3256 const header = sym.section_number.header(coff);
3257 const aux_ptr = coff.symbolTableSectionAuxEntryPtr(sti.*).?;
3258 aux_ptr.* = .{
3259 .length = @intCast(sym.ni.unwrap().?.location(&coff.mf).resolve(&coff.mf)[1]),
3260 .number_of_relocations = header.number_of_relocations,
3261 .number_of_linenumbers = header.number_of_linenumbers,
3262 .checksum = 0,
3263 .number = 0,
3264 .selection = .NONE,
3265 .unused = @splat(0),
3266 };
3267 if (coff.targetEndian() != native_endian)
3268 std.mem.byteSwapAllFieldsAligned(std.coff.SectionDefinition, .@"2", aux_ptr);
3269
3270 break :aux_init;
3271 },
3272 else => {},
3273 }
3274
3275 unreachable;
3276 }
3277
3278 break :entry entry;
3279 };
3280
3281 coff.targetStore(&entry.value, switch (sym.section_number) {
3282 .UNDEFINED => if (entry.storage_class == .WEAK_EXTERNAL) 0 else sym.size(coff),
3283 .ABSOLUTE,
3284 .DEBUG,
3285 => unreachable,
3286 else => switch (coff.getNode(sym.ni.unwrap().?)) {
3287 .image_section => 0,
3288 else => coff.computeSymbolSectionOffset(sym, .image),
3289 },
3290 });
3291
3292 log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sti.* });
3293}
3294
3295fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {
3296 const member = iami.member(coff);
3297 assert(!member.flags.is_loaded);
3298 defer member.flags.is_loaded = true;
3299 switch (member.content) {
3300 .import => unreachable,
3301 .object => |file_location| {
3302 if (file_location.size == 0) return;
3303 const comp = coff.base.comp;
3304 const io = comp.io;
3305 const path = member.iai.path(coff);
3306 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3307 defer file.close(io);
3308 var buffer: [4096]u8 = undefined;
3309 var fr = file.reader(io, &buffer);
3310 const offset = file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader);
3311 try fr.seekTo(offset);
3312 log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) });
3313 try coff.loadObject(path, member.name.toSlice(coff), &fr, .{
3314 .offset = offset,
3315 .size = file_location.size,
3316 });
3317 },
3318 }
3319}
3320
3321fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
3322 const file_loc = isi.fileLocation(coff);
3323 if (file_loc.size == 0) return;
3324 const comp = coff.base.comp;
3325 const io = comp.io;
3326 const gpa = comp.gpa;
3327 const ioi = isi.input(coff);
3328 const path = ioi.path(coff);
3329 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3330 defer file.close(io);
3331 var fr = file.reader(io, &.{});
3332 try fr.seekTo(file_loc.offset);
3333 var nw: MappedFile.Node.Writer = undefined;
3334 const si = isi.symbol(coff);
3335 si.node(coff).writer(gpa, &coff.mf, &nw);
3336 defer nw.deinit();
3337 log.debug("flushInputSection({f}{f}, {s}, {d}, n{d})", .{
3338 path,
3339 fmtMemberNameString(ioi.memberName(coff)),
3340 si.get(coff).section_number.name(coff).toSlice(coff),
3341 si,
3342 si.node(coff),
3343 });
3344 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
3345 return error.EndOfStream;
3346 try si.applyLocationRelocs(coff);
3347}
3348
3349fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
3350 assert(coff.hasCoffHeader());
3351
3352 const gpa = coff.base.comp.gpa;
3353 try coff.nodes.ensureUnusedCapacity(gpa, 1);
3354 try coff.section_table.ensureUnusedCapacity(gpa, 1);
3355 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3356 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
3357
3358 const coff_header = coff.headerPtr();
3359 const section_index = coff.targetLoad(&coff_header.number_of_sections);
3360 const section_table_len = section_index + 1;
3361 coff.targetStore(&coff_header.number_of_sections, section_table_len);
3362 try Node.known.section_table.resizeLeaf(
3363 gpa,
3364 &coff.mf,
3365 @sizeOf(std.coff.SectionHeader) * section_table_len,
3366 );
3367
3368 const ni = try coff.sectionParent().addFloatingChild(gpa, &coff.mf, .{
3369 .alignment = coff.mf.flags.block_size,
3370 .moved = true,
3371 .bubbles_moved = false,
3372 });
3373
3374 const si = coff.addSymbolAssumeCapacity();
3375 coff.section_table.putAssumeCapacity(name, .{
3376 .si = si,
3377 .relocation_table_ni = .none,
3378 });
3379 coff.nodes.appendAssumeCapacity(.{ .image_section = si });
3380 const section_table = coff.sectionTableSlice();
3381
3382 const virtual_size, const rva = if (coff.isImage()) block: {
3383 const virtual_size = coff.optionalHeaderField(.section_alignment);
3384 const rva: u32 = switch (section_index) {
3385 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]),
3386 else => coff.section_table.values()[section_index - 1].si.get(coff).rva +
3387 coff.targetLoad(&section_table[section_index - 1].virtual_size),
3388 };
3389
3390 break :block .{ virtual_size, rva };
3391 } else .{ 0, 0 };
3392
3393 {
3394 const sym = si.get(coff);
3395 sym.ni = .wrap(ni);
3396 sym.rva = rva;
3397 sym.section_number = @fromBackingInt(@intCast(section_table_len));
3398 }
3399 const section = &section_table[section_index];
3400 section.* = .{
3401 .name = undefined,
3402 .virtual_size = virtual_size,
3403 .virtual_address = rva,
3404 .size_of_raw_data = 0,
3405 .pointer_to_raw_data = 0,
3406 .pointer_to_relocations = 0,
3407 .pointer_to_linenumbers = 0,
3408 .number_of_relocations = 0,
3409 .number_of_linenumbers = 0,
3410 .flags = flags,
3411 };
3412 if (coff.targetEndian() != native_endian)
3413 std.mem.byteSwapAllFields(std.coff.SectionHeader, section);
3414
3415 const name_slice = name.toSlice(coff);
3416 if (coff.isImage()) {
3417 @memcpy(section.name[0..name_slice.len], name_slice);
3418 @memset(section.name[name_slice.len..], 0);
3419 switch (coff.optionalHeaderPtr()) {
3420 inline else => |optional_header| coff.targetStore(
3421 &optional_header.size_of_image,
3422 @intCast(rva + virtual_size),
3423 ),
3424 }
3425 } else {
3426 (try coff.getOrPutSymbolName(name_slice, name)).store(coff, &section.name);
3427 try coff.pendingSymbolTableEntry(si);
3428 }
3429
3430 return si;
3431}
3432
3433const ObjectSectionAttributes = packed struct {
3434 read: bool = false,
3435 write: bool = false,
3436 execute: bool = false,
3437 shared: bool = false,
3438 nopage: bool = false,
3439 nocache: bool = false,
3440 discard: bool = false,
3441 remove: bool = false,
3442 initialized: bool = false,
3443 uninitialized: bool = false,
3444
3445 pub fn fromFlags(flags: std.coff.SectionHeader.Flags) ObjectSectionAttributes {
3446 return .{
3447 .read = flags.MEM_READ,
3448 .write = flags.MEM_WRITE,
3449 .execute = flags.MEM_EXECUTE,
3450 .shared = flags.MEM_SHARED,
3451 .nopage = flags.MEM_NOT_PAGED,
3452 .nocache = flags.MEM_NOT_CACHED,
3453 .discard = flags.MEM_DISCARDABLE,
3454 .remove = flags.LNK_REMOVE,
3455 .initialized = flags.CNT_INITIALIZED_DATA,
3456 .uninitialized = flags.CNT_UNINITIALIZED_DATA,
3457 };
3458 }
3459
3460 pub fn asFlags(attr: ObjectSectionAttributes) std.coff.SectionHeader.Flags {
3461 return .{
3462 .MEM_READ = attr.read,
3463 .MEM_WRITE = attr.write,
3464 .MEM_EXECUTE = attr.execute,
3465 .MEM_SHARED = attr.shared,
3466 .MEM_NOT_PAGED = attr.nopage,
3467 .MEM_NOT_CACHED = attr.nocache,
3468 .MEM_DISCARDABLE = attr.discard,
3469 .LNK_REMOVE = attr.remove,
3470 .CNT_INITIALIZED_DATA = attr.uninitialized,
3471 .CNT_UNINITIALIZED_DATA = attr.uninitialized,
3472 };
3473 }
3474};
3475
3476fn pseudoSectionMapIndex(
3477 coff: *Coff,
3478 name: String,
3479 alignment: Alignment,
3480 attributes: ObjectSectionAttributes,
3481) !Node.PseudoSectionMapIndex {
3482 const gpa = coff.base.comp.gpa;
3483 const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name);
3484 const psmi: Node.PseudoSectionMapIndex = @fromBackingInt(@intCast(pseudo_section_gop.index));
3485 const parent_sn = if (!pseudo_section_gop.found_existing) sn: {
3486 const effective_name = coff.section_merges.get(name) orelse name;
3487 const parent = if (coff.section_table.get(effective_name)) |existing_sec|
3488 existing_sec.si
3489 else if (coff.isImage()) parent: {
3490 const parent: Symbol.Index = if (attributes.uninitialized)
3491 .bss
3492 else if (attributes.execute)
3493 .text
3494 else if (attributes.write)
3495 .data
3496 else
3497 .rdata;
3498
3499 break :parent parent;
3500 } else try coff.addSection(effective_name, attributes.asFlags());
3501
3502 try coff.nodes.ensureUnusedCapacity(gpa, 1);
3503 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3504 const ni = try parent.node(coff).addFloatingChild(gpa, &coff.mf, .{ .alignment = alignment });
3505 const si = coff.addSymbolAssumeCapacity();
3506 pseudo_section_gop.value_ptr.* = si;
3507 const sym = si.get(coff);
3508 sym.ni = .wrap(ni);
3509 sym.rva = coff.computeNodeRva(ni);
3510 sym.section_number = parent.get(coff).section_number;
3511 assert(sym.loc_relocs == .none);
3512 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
3513 coff.nodes.appendAssumeCapacity(.{ .pseudo_section = psmi });
3514 break :sn sym.section_number;
3515 } else pseudo_section_gop.value_ptr.get(coff).section_number;
3516
3517 try coff.verifyParentSectionAttributes(
3518 parent_sn,
3519 name,
3520 .pseudo,
3521 .fromFlags(parent_sn.header(coff).flags),
3522 attributes,
3523 );
3524
3525 return psmi;
3526}
3527
3528fn objectSectionParentName(coff: *Coff, name: []const u8) []const u8 {
3529 // In images we want to sort object sections into the final root section name.
3530 // Otherwise, we want to keep the full name so that this sort can occur correctly when
3531 // the object is finally linked into an image.
3532 return if (coff.isImage())
3533 name[0 .. std.mem.findScalar(u8, name, '$') orelse name.len]
3534 else
3535 name;
3536}
3537
3538fn objectSectionMapIndex(
3539 coff: *Coff,
3540 name: String,
3541 alignment: Alignment,
3542 attributes: ObjectSectionAttributes,
3543) !Node.ObjectSectionMapIndex {
3544 const gpa = coff.base.comp.gpa;
3545 const name_slice = name.toSlice(coff);
3546 // TODO: Should this be a section merge instead?
3547 const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: {
3548 // In images, the .tls section is a read-only template
3549 var attr = attributes;
3550 attr.write = false;
3551 break :attr attr;
3552 } else attributes;
3553
3554 const object_section_gop = try coff.object_section_table.getOrPut(gpa, name);
3555 const osmi: Node.ObjectSectionMapIndex = @fromBackingInt(@intCast(object_section_gop.index));
3556 const sym = if (!object_section_gop.found_existing) sym: {
3557 try coff.ensureUnusedStringCapacity(name_slice.len);
3558 const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice));
3559 const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff);
3560 try coff.nodes.ensureUnusedCapacity(gpa, 1);
3561 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3562 const parent_ni = parent.node(coff);
3563 var prev_oni: MappedFile.Node.Index.Optional = .none;
3564 {
3565 var child_oni = parent_ni.first(&coff.mf);
3566 while (child_oni.unwrap()) |child_ni| : (child_oni = child_ni.next(&coff.mf)) {
3567 switch (std.mem.order(
3568 u8,
3569 name_slice,
3570 coff.getNode(child_ni).object_section.name(coff).toSlice(coff),
3571 )) {
3572 .lt => break,
3573 .eq => unreachable,
3574 .gt => prev_oni = .wrap(child_ni),
3575 }
3576 }
3577 }
3578 const ni = try parent_ni.addHeaderChildAfter(gpa, &coff.mf, prev_oni, .{
3579 .alignment = alignment,
3580 });
3581 const si = coff.addSymbolAssumeCapacity();
3582 object_section_gop.value_ptr.* = si;
3583 const sym = si.get(coff);
3584 sym.ni = .wrap(ni);
3585 sym.rva = coff.computeNodeRva(ni);
3586 sym.section_number = parent.get(coff).section_number;
3587 assert(sym.loc_relocs == .none);
3588 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
3589 coff.nodes.appendAssumeCapacity(.{ .object_section = osmi });
3590 break :sym sym;
3591 } else object_section_gop.value_ptr.get(coff);
3592
3593 const parent_ni = sym.ni.unwrap().?.parent(&coff.mf).unwrap().?;
3594 const parent_alignment = parent_ni.alignment(&coff.mf);
3595 if (alignment.compare(.gt, parent_alignment)) {
3596 log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment });
3597 try parent_ni.realign(gpa, &coff.mf, alignment);
3598 }
3599
3600 const old_alignment = sym.ni.unwrap().?.alignment(&coff.mf);
3601 if (alignment.compare(.gt, old_alignment)) {
3602 log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment });
3603 try sym.ni.unwrap().?.realign(gpa, &coff.mf, alignment);
3604 }
3605
3606 try coff.verifyParentSectionAttributes(
3607 sym.section_number,
3608 name,
3609 .object,
3610 .fromFlags(sym.section_number.header(coff).flags),
3611 effective_attributes,
3612 );
3613
3614 return osmi;
3615}
3616
3617fn verifyParentSectionAttributes(
3618 coff: *Coff,
3619 parent: Symbol.SectionNumber,
3620 child_name: String,
3621 child_kind: enum { pseudo, object },
3622 parent_attrs: ObjectSectionAttributes,
3623 child_attrs: ObjectSectionAttributes,
3624) !void {
3625 if (parent_attrs == child_attrs) return;
3626
3627 const was_merged = switch (child_kind) {
3628 .pseudo => coff.section_merges.contains(child_name),
3629 .object => if (coff.getString(
3630 coff.objectSectionParentName(child_name.toSlice(coff)),
3631 ).unwrap()) |pseudo_name|
3632 coff.section_merges.contains(pseudo_name)
3633 else
3634 false,
3635 };
3636
3637 // The section was intentionally merged by the user or builtin rule
3638 if (was_merged) return;
3639
3640 const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?;
3641 const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs)));
3642 var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
3643 try err.addMsg("{t} section '{s}' was placed in parent section '{s}' with mismatched flags", .{
3644 child_kind,
3645 child_name.toSlice(coff),
3646 parent.name(coff).toSlice(coff),
3647 });
3648
3649 inline for (@typeInfo(ObjectSectionAttributes).@"struct".field_names) |field| {
3650 if (@field(child_attrs, field) != @field(parent_attrs, field)) {
3651 err.addNote("flags.{s} was {d} in {s}, but {d} in {s}", .{
3652 field,
3653 @intFromBool(@field(child_attrs, field)),
3654 child_name.toSlice(coff),
3655 @intFromBool(@field(parent_attrs, field)),
3656 parent.name(coff).toSlice(coff),
3657 });
3658 }
3659 }
3660
3661 return error.AlreadyReported;
3662}
3663
3664const RelocAddend = union(enum) {
3665 known: i64,
3666 /// Relocs tables in input objects don't include the addend.
3667 /// The value needs to be recovered from the reloc location.
3668 pending: void,
3669};
3670
3671// TODO: There should be an API where the caller can indicate how many contiguous relocs they need
3672// and it should attempt to allocate these from from the free list if available. We can cache
3673// the run length of each segment on Reloc when `free` is set.
3674pub fn addReloc(
3675 coff: *Coff,
3676 loc_si: Symbol.Index,
3677 offset: u64,
3678 target_si: Symbol.Index,
3679 addend: RelocAddend,
3680 @"type": Reloc.Type,
3681) link.Error!void {
3682 const diags = &coff.base.comp.link_diags;
3683 try coff.ensureUnusedRelocCapacity(loc_si, 1);
3684 coff.addRelocAssumeCapacity(loc_si, offset, target_si, addend, @"type") catch |err| switch (err) {
3685 error.MappedFileIo => return diags.fail(
3686 "failed to write output file: {t}",
3687 .{coff.mf.io_err.?},
3688 ),
3689 else => |e| return e,
3690 };
3691}
3692
3693fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void {
3694 const gpa = coff.base.comp.gpa;
3695 try coff.relocs.ensureUnusedCapacity(gpa, len);
3696 if (isImage(coff)) return;
3697 switch (loc_si.get(coff).section_number) {
3698 .UNDEFINED, .ABSOLUTE, .DEBUG => {},
3699 else => |loc_sn| {
3700 const section = loc_sn.section(coff);
3701 if (section.relocation_table_ni == .none)
3702 try coff.nodes.ensureUnusedCapacity(gpa, 1);
3703 },
3704 }
3705}
3706
3707fn addRelocAssumeCapacity(
3708 coff: *Coff,
3709 loc_si: Symbol.Index,
3710 offset: u64,
3711 target_si: Symbol.Index,
3712 addend: RelocAddend,
3713 @"type": Reloc.Type,
3714) !void {
3715 const gpa = coff.base.comp.gpa;
3716 const target = target_si.get(coff);
3717
3718 const ri: Reloc.Index = @fromBackingInt(@intCast(coff.relocs.items.len));
3719 log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{
3720 loc_si,
3721 loc_si.get(coff).section_number,
3722 offset,
3723 target_si,
3724 target_si.get(coff).section_number,
3725 if (addend == .pending) 0 else addend.known,
3726 if (addend == .pending) "p" else "k",
3727 ri,
3728 });
3729
3730 const sri: Section.RelocationIndex = if (isImage(coff))
3731 .none
3732 else switch (loc_si.get(coff).section_number) {
3733 .UNDEFINED,
3734 .ABSOLUTE,
3735 .DEBUG,
3736 => .none,
3737 else => |loc_sn| sri: {
3738 // The target may not have a node yet, or it could be an extern that will never
3739 // have a node. In that case, flushGlobal will create the symbol table entry.
3740 const existing_sti = target_si.sti(coff);
3741 const sti: SymbolTable.Index = if (existing_sti != .none)
3742 existing_sti
3743 else if (target.ni != .none) sti: {
3744 try coff.pendingSymbolTableEntry(target_si);
3745 break :sti .none;
3746 } else .none;
3747
3748 const sri: Section.RelocationIndex = blk: {
3749 // TODO: Once the API for using free relocs exist, if this is about to consume a
3750 // free reloc, then we can use the existing (cleared) .sri on the reloc
3751
3752 const section = loc_sn.section(coff);
3753 const header = loc_sn.header(coff);
3754 const old_num_relocations = coff.targetLoad(&header.number_of_relocations);
3755 const new_num_relocations = old_num_relocations + 1;
3756 const new_size = @as(u32, new_num_relocations) * std.coff.Relocation.sizeOf();
3757
3758 coff.targetStore(&header.number_of_relocations, new_num_relocations);
3759 if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr|
3760 coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations);
3761
3762 if (section.relocation_table_ni.unwrap()) |relocation_table_ni| {
3763 try relocation_table_ni.resizeLeaf(gpa, &coff.mf, new_size);
3764 } else {
3765 section.relocation_table_ni = .wrap(try coff.sectionParent().addFloatingChild(gpa, &coff.mf, .{
3766 .size = new_size,
3767 .alignment = .@"2",
3768 .moved = true,
3769 .resized = true,
3770 }));
3771 coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn });
3772 }
3773
3774 // TODO: These need to allocate from a free list, once deleting relocs from the table is supported
3775 break :blk .wrap(old_num_relocations);
3776 };
3777
3778 const entry = sri.entry(coff, loc_sn).?;
3779 if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index);
3780
3781 // applyLocationRelocs updates `virtual_address`
3782 // flushSymbolTableIndex updates `symbol_table_index`
3783 coff.targetStore(&entry.type, @"type".u16);
3784
3785 break :sri sri;
3786 },
3787 };
3788
3789 coff.relocs.addOneAssumeCapacity().* = .{
3790 .type = @"type",
3791 .prev = target.target_relocs,
3792 .next = .none,
3793 .loc = loc_si,
3794 .target = target_si,
3795 .sri = sri,
3796 .offset = offset,
3797 .addend = if (addend == .pending) 0 else addend.known,
3798 .flags = .{
3799 .recover_addend = addend == .pending,
3800 .free = false,
3801 },
3802 };
3803 switch (target.target_relocs) {
3804 .none => {},
3805 else => |target_ri| target_ri.get(coff).next = ri,
3806 }
3807 target.target_relocs = ri;
3808}
3809
3810fn failLoadInput(
3811 coff: *Coff,
3812 err: LoadInputError,
3813 fr: *Io.File.Reader,
3814 path: std.Build.Cache.Path,
3815) link.Error {
3816 const diags = &coff.base.comp.link_diags;
3817 switch (err) {
3818 else => |e| return e,
3819 error.MappedFileIo => return diags.fail(
3820 "failed to write output file: {t}",
3821 .{coff.mf.io_err.?},
3822 ),
3823 error.EndOfStream => return diags.failParse(
3824 path,
3825 "unexpected eof",
3826 .{},
3827 ),
3828 error.AccessDenied,
3829 error.Unexpected,
3830 error.Unseekable,
3831 => |e| return diags.fail(
3832 "failed to read \"{f}\": {t}",
3833 .{ path.fmtEscapeString(), e },
3834 ),
3835 error.PermissionDenied,
3836 error.SystemResources,
3837 error.Streaming,
3838 => |e| return diags.fail(
3839 "failed to stat \"{f}\": {t}",
3840 .{ path.fmtEscapeString(), e },
3841 ),
3842 error.ReadFailed => switch (fr.err.?) {
3843 error.Canceled => |e| return e,
3844 else => |e| return diags.fail(
3845 "failed to read \"{f}\": {t}",
3846 .{ path.fmtEscapeString(), e },
3847 ),
3848 },
3849 }
3850}
3851
3852pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
3853 const comp = coff.base.comp;
3854 const io = comp.io;
3855
3856 const path = input.path() orelse unreachable;
3857 const gop = try coff.inputs.getOrPut(comp.gpa, path);
3858 if (gop.found_existing) return;
3859 errdefer _ = coff.inputs.swapRemove(path);
3860
3861 var buf: [4096]u8 = undefined;
3862 switch (input) {
3863 .object => |object| {
3864 var fr = object.file.reader(io, &buf);
3865 coff.loadObject(object.path, null, &fr, .{
3866 .offset = fr.logicalPos(),
3867 .size = fr.getSize() catch |err|
3868 return coff.failLoadInput(err, &fr, object.path),
3869 }) catch |err| return coff.failLoadInput(err, &fr, object.path);
3870 },
3871 .archive => |archive| {
3872 var fr = archive.file.reader(io, &buf);
3873 coff.loadArchive(archive.path, &fr) catch |err|
3874 return coff.failLoadInput(err, &fr, archive.path);
3875 },
3876 .res => |res| {
3877 var fr = res.file.reader(io, &buf);
3878 coff.loadRes(res.path, &fr) catch |err|
3879 return coff.failLoadInput(err, &fr, res.path);
3880 },
3881 .dso => |dso| {
3882 var fr = dso.file.reader(io, &buf);
3883 coff.loadDll(dso.path, &fr) catch |err|
3884 return coff.failLoadInput(err, &fr, dso.path);
3885 },
3886 .dso_exact => unreachable,
3887 }
3888}
3889
3890fn fmtMemberNameString(memberName: ?[]const u8) std.fmt.Alt(?[]const u8, memberNameStringEscape) {
3891 return .{ .data = memberName };
3892}
3893
3894fn memberNameStringEscape(memberName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
3895 try w.print("({f})", .{std.zig.fmtString(memberName orelse return)});
3896}
3897
3898fn inputSectionHeaderNameSlice(
3899 coff: *Coff,
3900 header: *const std.coff.SectionHeader,
3901 string_table: []const u8,
3902 path: std.Build.Cache.Path,
3903 section_i: usize,
3904) ![]const u8 {
3905 const diags = &coff.base.comp.link_diags;
3906 return if (header.name[0] == '/') name: {
3907 const offset_str = std.mem.sliceTo(header.name[1..], 0);
3908 const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
3909 return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{
3910 section_i,
3911 header.name[0 .. offset_str.len + 1],
3912 });
3913
3914 if (name_offset > string_table.len)
3915 return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset });
3916
3917 break :name std.mem.sliceTo(string_table[name_offset..], 0);
3918 } else std.mem.sliceTo(&header.name, 0);
3919}
3920
3921fn loadObject(
3922 coff: *Coff,
3923 path: std.Build.Cache.Path,
3924 member_name: ?[]const u8,
3925 fr: *Io.File.Reader,
3926 fl: MappedFile.Node.FileLocation,
3927) LoadInputError!void {
3928 const comp = coff.base.comp;
3929 const gpa = comp.gpa;
3930 const diags = &comp.link_diags;
3931 const r = &fr.interface;
3932 const target = &comp.root_mod.resolved_target.result;
3933 const target_endian = coff.targetEndian();
3934 const is_archive = coff.isArchive();
3935 assert(!coff.isObj());
3936 // We want to evaluate new merges as we see them in .drectve sections to avoid redundant work
3937 assert(coff.section_merge_pending_index == coff.section_merges.count());
3938
3939 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) });
3940
3941 const header = try r.peekStruct(std.coff.Header, .little);
3942 if (header.machine != target.toCoffMachine())
3943 return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
3944 target.toCoffMachine(),
3945 header.machine,
3946 });
3947 if (header.number_of_sections == 0) return;
3948 if (@sizeOf(std.coff.Header) + @as(usize, header.number_of_sections) * @sizeOf(std.coff.SectionHeader) > fl.size)
3949 return diags.failParse(path, "invalid section table", .{});
3950 const unexpected_header_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{
3951 .RELOCS_STRIPPED,
3952 .EXECUTABLE_IMAGE,
3953 .AGGRESSIVE_WS_TRIM,
3954 .RESERVED,
3955 .BYTES_REVERSED_LO,
3956 .DLL,
3957 .BYTES_REVERSED_HI,
3958 };
3959 inline for (unexpected_header_flags) |flag|
3960 if (@field(header.flags, @tagName(flag)))
3961 return diags.failParse(path, "unexpected flag set: {t}", .{flag});
3962
3963 if (header.size_of_optional_header != 0)
3964 return diags.failParse(path, "unexpected optional header", .{});
3965
3966 const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf();
3967 const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len;
3968 // String table length (which includes the length field) immediately trails the symbol table
3969 if (symbol_table_end + @sizeOf(u32) > fl.size)
3970 return diags.failParse(path, "bad symbol table location", .{});
3971
3972 try fr.seekTo(fl.offset + symbol_table_end);
3973 const string_table_len = try r.peekInt(u32, target_endian);
3974 if (string_table_len < @sizeOf(u32) or
3975 symbol_table_end + string_table_len > fl.size)
3976 return diags.failParse(path, "bad string table length: 0x{x}", .{string_table_len});
3977
3978 const ioi: InputObject.Index = @fromBackingInt(@intCast(coff.input_objects.items.len));
3979 try coff.input_objects.ensureUnusedCapacity(gpa, 1);
3980 const input = coff.input_objects.addOneAssumeCapacity();
3981 input.* = .{
3982 .path = path,
3983 .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,
3984 .source_name = .none,
3985 };
3986
3987 const string_table = string_table: {
3988 const string_table = try gpa.alloc(u8, string_table_len);
3989 errdefer gpa.free(string_table);
3990 try r.readSliceAll(string_table);
3991 break :string_table string_table;
3992 };
3993 defer gpa.free(string_table);
3994
3995 try coff.ensureManyUnusedStringCapacity(
3996 header.number_of_sections + header.number_of_symbols,
3997 header.number_of_sections * 9 +
3998 header.number_of_symbols * 9 +
3999 string_table_len - @sizeOf(u32),
4000 );
4001
4002 const PendingSymbolIndex = enum(u32) {
4003 none,
4004 _,
4005
4006 pub fn wrap(i: ?u32) @This() {
4007 return @fromBackingInt(@intCast((i orelse return .none) + 1));
4008 }
4009
4010 pub fn unwrap(i: @This()) ?u32 {
4011 return switch (i) {
4012 .none => null,
4013 _ => @backingInt(i) - 1,
4014 };
4015 }
4016 };
4017
4018 const PendingInputSection = struct {
4019 header: std.coff.SectionHeader,
4020 name: String,
4021 si: Symbol.Index,
4022 parent_si: Symbol.Index,
4023 psi: PendingSymbolIndex,
4024 num_symbols: u32,
4025 comdat: std.coff.ComdatSelection,
4026 comdat_psi: PendingSymbolIndex,
4027 comdat_crc: u32,
4028 comdat_association: Symbol.SectionNumber,
4029 comdat_result: union(enum) {
4030 pending,
4031 // Root of the association chain
4032 pending_association: Symbol.SectionNumber,
4033 include,
4034 skip,
4035 },
4036 };
4037
4038 const sections: []PendingInputSection = if (coff.isImage()) sections: {
4039 const sections = try gpa.alloc(PendingInputSection, header.number_of_sections);
4040 errdefer gpa.free(sections);
4041
4042 try fr.seekTo(fl.offset + @sizeOf(std.coff.Header));
4043 for (sections, 0..) |*section, section_i| {
4044 section.* = .{
4045 .header = try r.takeStruct(std.coff.SectionHeader, target_endian),
4046 .name = undefined,
4047 .si = .null,
4048 .parent_si = .null,
4049 .psi = .none,
4050 .num_symbols = 0,
4051 .comdat = .NONE,
4052 .comdat_psi = .none,
4053 .comdat_crc = 0,
4054 .comdat_association = .UNDEFINED,
4055 .comdat_result = .pending,
4056 };
4057
4058 const section_name_slice = if (section.header.name[0] == '/') name: {
4059 const offset_str = std.mem.sliceTo(section.header.name[1..], 0);
4060 const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
4061 return diags.failParse(path, "ill-formed section name offset in section {d}: '{s}'", .{
4062 section_i,
4063 section.header.name[0 .. offset_str.len + 1],
4064 });
4065
4066 if (name_offset > string_table.len)
4067 return diags.failParse(
4068 path,
4069 "out-of-bounds section name offset in section {d}: {d}",
4070 .{ section_i, name_offset },
4071 );
4072
4073 break :name std.mem.sliceTo(string_table[name_offset..], 0);
4074 } else std.mem.sliceTo(&section.header.name, 0);
4075 section.name = coff.getOrPutStringAssumeCapacity(section_name_slice);
4076
4077 if (section.header.pointer_to_linenumbers +
4078 @as(u32, section.header.number_of_linenumbers) * std.coff.LineNumber.sizeOf() > fl.size)
4079 return diags.failParse(path, "bad line numbers location in section {d} `{s}`", .{
4080 section_i,
4081 section_name_slice,
4082 });
4083
4084 if (section.header.pointer_to_relocations +
4085 @as(u32, section.header.number_of_relocations) * std.coff.Relocation.sizeOf() > fl.size)
4086 return diags.failParse(path, "bad relocations location in section {d} `{s}`", .{
4087 section_i,
4088 section_name_slice,
4089 });
4090
4091 if (section.header.pointer_to_raw_data + section.header.size_of_raw_data > fl.size)
4092 return diags.failParse(path, "bad raw data location in section {d} `{s}`", .{
4093 section_i,
4094 section_name_slice,
4095 });
4096 }
4097
4098 break :sections sections;
4099 } else &.{};
4100 defer gpa.free(sections);
4101
4102 const mi = if (is_archive) mi: {
4103 try coff.nodes.ensureUnusedCapacity(gpa, 2);
4104 try coff.members.ensureUnusedCapacity(gpa, 1);
4105 const path_str = try path.toString(gpa);
4106 defer gpa.free(path_str);
4107
4108 const mi = try coff.addMemberAssumeCapacity(.coff, fl.size);
4109 const member = mi.get(coff);
4110 try member.initHeader(coff, path_str, header.time_date_stamp);
4111
4112 {
4113 // TODO: This should be deferred to an idle task (but resize it here!)
4114 var nw: MappedFile.Node.Writer = undefined;
4115 member.content_ni.writer(gpa, &coff.mf, &nw);
4116 defer nw.deinit();
4117
4118 try fr.seekTo(fl.offset);
4119 const written = nw.interface.sendFileAll(fr, .limited64(fl.size)) catch |err| switch (err) {
4120 error.WriteFailed => return nw.err.?,
4121 else => |e| return e,
4122 };
4123
4124 if (written != fl.size) return error.EndOfStream;
4125 }
4126
4127 break :mi mi;
4128 } else undefined;
4129
4130 try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
4131 const symbol_size = std.coff.Symbol.sizeOf();
4132
4133 const PendingSymbol = struct {
4134 name: String,
4135 value: union(enum) {
4136 // Size of the section
4137 section: u32,
4138 // If section is absolute, the symbol value.
4139 // Otherwise, offset within the section.
4140 static: u32,
4141 // If section is undefined, the symbol size.
4142 // If section is absolute, the symbol value.
4143 // Otherwise offset within the section.
4144 external: u32,
4145 // The index of the target symbol of this weak external
4146 weak_external: u32,
4147 // Trails .weak_external
4148 weak_external_aux: WeakExternalStrat,
4149 },
4150 section_number: Symbol.SectionNumber,
4151 si: Symbol.Index,
4152 // If a weak external targets this symbol, the index of the weak external
4153 weak_external_psi: PendingSymbolIndex,
4154 };
4155
4156 var num_global_symbols: u32 = 0;
4157 var pending_symbols: std.array_hash_map.Auto(u32, PendingSymbol) = .empty;
4158 defer pending_symbols.deinit(gpa);
4159 if (!is_archive)
4160 try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
4161
4162 var section_merges: std.ArrayList(struct {
4163 from: String,
4164 to: String,
4165 }) = .empty;
4166 defer section_merges.deinit(gpa);
4167
4168 // Discover symbol names and COMDAT symbol mappings
4169 var symbol_i: u32 = 0;
4170 var num_included_symbols: u32 = 0;
4171 while (symbol_i < header.number_of_symbols) {
4172 var symbol: std.coff.Symbol = undefined;
4173 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size));
4174 if (target_endian != native_endian)
4175 std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
4176
4177 const aux_symbols = if (symbol.number_of_aux_symbols > 0)
4178 try r.take(symbol_size * symbol.number_of_aux_symbols)
4179 else
4180 &.{};
4181 defer symbol_i += symbol.number_of_aux_symbols + 1;
4182
4183 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
4184 const index = std.mem.readInt(u32, symbol.name[4..], target_endian);
4185 if (index >= string_table.len)
4186 return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_i});
4187 break :name string_table[index..];
4188 } else &symbol.name, 0);
4189
4190 if (is_archive) {
4191 if (switch (symbol.storage_class) {
4192 .WEAK_EXTERNAL => true,
4193 .EXTERNAL => symbol.section_number != .UNDEFINED,
4194 else => false,
4195 }) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
4196
4197 continue;
4198 }
4199
4200 switch (symbol.section_number) {
4201 .UNDEFINED, .DEBUG, .ABSOLUTE => {},
4202 else => |sn| if (@backingInt(sn) > sections.len)
4203 return diags.failParse(path, "out-of-bounds section number {d} in symbol 0x{x}", .{ sn, symbol_i }),
4204 }
4205
4206 const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count()));
4207 const section_number: Symbol.SectionNumber = @fromBackingInt(@intCast(@backingInt(symbol.section_number)));
4208
4209 const values: []const @FieldType(PendingSymbol, "value") = pending_symbols: switch (symbol.storage_class) {
4210 .STATIC, .LABEL => |storage_class| switch (section_number) {
4211 // TODO: Do we need to do anything with @feat.00?
4212 // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392
4213 .UNDEFINED,
4214 .DEBUG,
4215 => &.{},
4216 .ABSOLUTE => &.{.{ .static = symbol.value }},
4217 else => |sn| {
4218 const section = &sections[sn.toIndex()];
4219
4220 // Section symbol
4221 const is_section = storage_class == .STATIC and
4222 symbol.value == 0 and
4223 symbol.type == std.coff.SymType{
4224 .complex_type = .NULL,
4225 .base_type = .NULL,
4226 } and
4227 symbol.number_of_aux_symbols > 0;
4228
4229 if (is_section) {
4230 if (symbol.number_of_aux_symbols > 1)
4231 return diags.failParse(path, "invalid number of aux symbols for section symbol 0x{x}: {d}", .{
4232 symbol_i,
4233 symbol.number_of_aux_symbols,
4234 });
4235
4236 var section_def: std.coff.SectionDefinition = undefined;
4237 @memcpy(std.mem.asBytes(&section_def)[0..symbol_size], aux_symbols[0..symbol_size]);
4238 if (target_endian != native_endian)
4239 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def);
4240
4241 if (section_def.number_of_relocations != section.header.number_of_relocations)
4242 return diags.failParse(
4243 path,
4244 "section aux symbol 0x{x} for '{s}' relocation count did not match section header: {d} vs {d}",
4245 .{ symbol_i + 1, name, section_def.number_of_relocations, section.header.number_of_relocations },
4246 );
4247
4248 if (section_def.number_of_linenumbers != section.header.number_of_linenumbers)
4249 return diags.failParse(
4250 path,
4251 "section aux symbol 0x{x} for '{s}' line number count did not match section header: {d} vs {d}",
4252 .{ symbol_i + 1, name, section_def.number_of_linenumbers, section.header.number_of_linenumbers },
4253 );
4254
4255 if (section.header.flags.LNK_COMDAT) {
4256 if (section_def.selection == .ASSOCIATIVE) {
4257 if (section_def.number == 0 or section_def.number > sections.len)
4258 return diags.failParse(
4259 path,
4260 "section aux symbol 0x{x} for '{s}' contained an invalid associated section number: 0x{x}",
4261 .{ symbol_i + 1, name, section_def.number },
4262 );
4263
4264 section.comdat_association = @fromBackingInt(@intCast(section_def.number));
4265 }
4266
4267 section.comdat = section_def.selection;
4268 section.comdat_crc = section_def.checksum;
4269 }
4270
4271 section.psi = psi;
4272 }
4273
4274 break :pending_symbols &.{if (is_section)
4275 .{ .section = section.header.size_of_raw_data }
4276 else
4277 .{ .static = symbol.value }};
4278 },
4279 },
4280 .WEAK_EXTERNAL => switch (symbol.section_number) {
4281 .UNDEFINED => {
4282 if (symbol.value != 0)
4283 return diags.failParse(
4284 path,
4285 "invalid value {d} for weak external symbol 0x{x}",
4286 .{ symbol.value, symbol_i },
4287 );
4288
4289 var weak_external: std.coff.WeakExternalDefinition = undefined;
4290 @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
4291 if (target_endian != native_endian)
4292 std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external);
4293
4294 if (weak_external.tag_index >= header.number_of_symbols)
4295 return diags.failParse(
4296 path,
4297 "invalid tag_index 0x{x} for weak external symbol 0x{x}",
4298 .{ weak_external.tag_index, symbol_i },
4299 );
4300
4301 break :pending_symbols switch (weak_external.flag) {
4302 else => |flag| &.{
4303 .{ .weak_external = weak_external.tag_index },
4304 .{ .weak_external_aux = WeakExternalStrat.fromFlag(flag) },
4305 },
4306 _ => return diags.failParse(
4307 path,
4308 "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}",
4309 .{ weak_external.flag, symbol_i },
4310 ),
4311 };
4312 },
4313 else => |sn| return diags.failParse(
4314 path,
4315 "invalid section number {d} for weak external symbol 0x{x}",
4316 .{ sn, symbol_i },
4317 ),
4318 },
4319 .EXTERNAL => switch (section_number) {
4320 .UNDEFINED,
4321 .ABSOLUTE,
4322 => &.{.{ .external = symbol.value }},
4323 .DEBUG => return diags.failParse(
4324 path,
4325 "unexpected external symbol 0x{x} in DEBUG section: '{s}'",
4326 .{ symbol_i, name },
4327 ),
4328 else => &.{.{ .external = symbol.value }},
4329 },
4330 .FILE => {
4331 if (!std.mem.eql(u8, name, ".file"))
4332 return diags.failParse(
4333 path,
4334 "unexpected symbol name '{s}' for file symbol 0x{x}",
4335 .{ name, symbol_i },
4336 );
4337
4338 var file: std.coff.FileDefinition = undefined;
4339 @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
4340
4341 input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional();
4342 break :pending_symbols &.{};
4343 },
4344 else => |storage_class| return diags.failParse(
4345 path,
4346 "TODO handle storage class {t} for symbol 0x{x}",
4347 .{ storage_class, symbol_i },
4348 ),
4349 };
4350
4351 for (values, 0..) |value, i| {
4352 if (section_number == .ABSOLUTE)
4353 num_included_symbols += 1;
4354
4355 switch (value) {
4356 .section => {},
4357 .static,
4358 .external,
4359 .weak_external,
4360 => {
4361 num_global_symbols += 1;
4362 if (section_number.hasIndex()) {
4363 const section = &sections[section_number.toIndex()];
4364 section.num_symbols += 1;
4365 if (section.header.flags.LNK_COMDAT and section.comdat_psi == .none)
4366 section.comdat_psi = psi;
4367 }
4368 },
4369 .weak_external_aux => {},
4370 }
4371
4372 const symbol_name = coff.getOrPutStringAssumeCapacity(name);
4373 pending_symbols.putAssumeCapacity(symbol_i + @as(u32, @intCast(i)), .{
4374 .name = symbol_name,
4375 .value = value,
4376 .section_number = section_number,
4377 .si = .null,
4378 .weak_external_psi = .none,
4379 });
4380 }
4381 }
4382
4383 try coff.globals.ensureUnusedCapacity(gpa, num_global_symbols);
4384 for (sections) |*section| {
4385 if (section.header.flags.LNK_INFO) {
4386 if (std.mem.eql(u8, &section.header.name, ".drectve")) {
4387 try fr.seekTo(fl.offset + section.header.pointer_to_raw_data);
4388 // TODO: Don't really want an additional buffer here, but want to limit to size_of_raw_data
4389 var buf: [128]u8 = undefined;
4390 var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf);
4391 while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) {
4392 error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}),
4393 else => |e| return e,
4394 }) |arg| {
4395 // Microsoft tools emit 3 space characters into this section even with /Zl
4396 if (arg.len == 0) continue;
4397
4398 if (std.ascii.startsWithIgnoreCase(arg, "-exclude-symbols:")) {
4399 // TODO: When implementing mingw auto-exports (if at all?), track this to not export this symbol
4400 } else if (std.ascii.startsWithIgnoreCase(arg, "/include:")) {
4401 _ = try coff.globalSymbol(.{ .name = arg["/include:".len..] });
4402 } else if (std.ascii.startsWithIgnoreCase(arg, "/alternatename:")) {
4403 var split = std.mem.splitScalar(u8, arg["/alternatename:".len..], '=');
4404 const orig = split.first();
4405 const alt = split.next() orelse
4406 return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
4407
4408 try coff.ensureManyUnusedStringCapacity(2, orig.len + alt.len + 2);
4409 const orig_str = coff.getOrPutStringAssumeCapacity(orig);
4410 const alt_str = coff.getOrPutStringAssumeCapacity(alt);
4411 const gop = try coff.alternate_names.getOrPut(gpa, orig_str);
4412 if (!gop.found_existing) {
4413 log.debug("alternateName({s}={s})", .{ orig, alt });
4414 gop.value_ptr.* = alt_str;
4415 } else if (gop.value_ptr.* != alt_str)
4416 return diags.failParse(
4417 path,
4418 "conflicting /alternatename .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
4419 .{ orig, gop.value_ptr.toSlice(coff), orig, alt },
4420 );
4421 } else if (std.ascii.startsWithIgnoreCase(arg, "/guardsym:")) {
4422 // TODO: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata
4423 } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) merge: {
4424 var split = std.mem.splitScalar(u8, arg["/merge:".len..], '=');
4425 const from = split.first();
4426 const to = split.next() orelse
4427 return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
4428 if (to.len > header_name_max_len)
4429 return diags.failParse(
4430 path,
4431 "/merge .drectve target exceeds max length of {d}: '{s}'",
4432 .{ header_name_max_len, arg },
4433 );
4434 if (std.mem.eql(u8, from, to)) break :merge;
4435
4436 try coff.ensureManyUnusedStringCapacity(2, from.len + to.len + 2);
4437 const from_str = coff.getOrPutStringAssumeCapacity(from);
4438 const to_str = coff.getOrPutStringAssumeCapacity(to);
4439
4440 {
4441 var iter = to_str;
4442 while (coff.section_merges.get(iter)) |next_to| {
4443 if (next_to == from_str)
4444 return diags.failParse(
4445 path,
4446 "/merge .drectve argument would create a cycle: {s}={s} leads to {s}={s}",
4447 .{ from, to, iter.toSlice(coff), to },
4448 );
4449
4450 iter = next_to;
4451 }
4452 }
4453
4454 try coff.section_merges.ensureUnusedCapacity(gpa, 1);
4455 const gop = coff.section_merges.getOrPutAssumeCapacity(from_str);
4456 if (!gop.found_existing) {
4457 coff.synth_prog_node.increaseEstimatedTotalItems(1);
4458 gop.value_ptr.* = to_str;
4459 } else if (gop.value_ptr.* != to_str)
4460 return diags.failParse(
4461 path,
4462 "conflicting /merge .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
4463 .{ from, gop.value_ptr.toSlice(coff), from, to },
4464 );
4465 } else if (std.ascii.startsWithIgnoreCase(arg, "/disallowlib:")) {
4466 const lib_name = arg["/disallowlib:".len..];
4467 // TODO: Track these and issue error in prelink if any match
4468 _ = lib_name;
4469 } else if (std.ascii.startsWithIgnoreCase(arg, "/defaultlib:")) {
4470 const lib_path = arg["/defaultlib:".len..];
4471 const trim = std.mem.trim(u8, lib_path, "\"");
4472 if (lib_path.len == trim.len or lib_path.len - 2 == trim.len) {
4473 if (!comp.config.link_libc or comp.libc_installation == null)
4474 return diags.failParse(path, "encountered /DEFAULTLIB .drectve argument when libc was not available: {s}", .{arg});
4475
4476 (try coff.pending_default_libs.addOne(gpa)).* = .{
4477 .path = try gpa.dupe(u8, lib_path),
4478 .ioi = ioi,
4479 };
4480 } else return diags.failParse(
4481 path,
4482 "malformed /DEFAULTLIB .drectve argument: `{s}`",
4483 .{arg},
4484 );
4485 } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
4486 }
4487 }
4488
4489 section.comdat_result = .skip;
4490 continue;
4491 }
4492
4493 if (section.header.flags.LNK_REMOVE or
4494 section.header.flags.MEM_DISCARDABLE)
4495 {
4496 // TODO: Convert .debug$* sections into PDB
4497 section.comdat_result = .skip;
4498 continue;
4499 }
4500
4501 section.comdat_result = comdat: switch (section.comdat) {
4502 .NONE => .include,
4503 .ASSOCIATIVE => {
4504 // Associative COMDAT sections have no COMDAT symbol.
4505 // They are linked if the assocated section is linked.
4506 var iter = section;
4507 var iter_sn = iter.comdat_association;
4508 while (iter.comdat == .ASSOCIATIVE) {
4509 iter = &sections[iter_sn.toIndex()];
4510 iter_sn = iter.comdat_association;
4511 if (iter == section)
4512 return diags.failParse(
4513 path,
4514 "circular COMDAT association loop detected, starting at symbol 0x{x}",
4515 .{pending_symbols.keys()[section.psi.unwrap().?]},
4516 );
4517 }
4518
4519 assert(iter != section);
4520 break :comdat switch (iter.comdat_result) {
4521 .pending => .{ .pending_association = iter_sn },
4522 else => |iter_result| iter_result,
4523 };
4524 },
4525 else => |comdat| {
4526 const psi = section.comdat_psi.unwrap() orelse section.psi.unwrap().?;
4527 const symbol = &pending_symbols.values()[psi];
4528 const si = existing: switch (symbol.value) {
4529 .weak_external => unreachable,
4530 .weak_external_aux => unreachable,
4531 .static => break :comdat .include,
4532 .section => {
4533 assert(section.comdat_psi == .none);
4534 if (coff.object_section_table.get(section.name)) |si|
4535 break :existing si
4536 else if (coff.pseudo_section_table.get(section.name)) |si|
4537 break :existing si
4538 else if (coff.section_table.get(section.name)) |s|
4539 break :existing s.si
4540 else
4541 break :comdat .include;
4542 },
4543 .external => {
4544 const global_gop = try coff.getOrPutGlobalSymbol(.{
4545 .name = symbol.name.toSlice(coff),
4546 });
4547
4548 // TODO: What if the same symbol is incorrectly defined twice in this obj?
4549 // Would need to mark this global as pending, or notice it later when .ni != none
4550 if (!global_gop.found_existing or global_gop.value_ptr.si.get(coff).ni == .none) {
4551 symbol.si = global_gop.value_ptr.si;
4552 break :comdat .include;
4553 }
4554
4555 break :existing global_gop.value_ptr.si;
4556 },
4557 };
4558
4559 const index = pending_symbols.keys()[psi];
4560 switch (comdat) {
4561 .NODUPLICATES => return coff.failMultipleDefinitions(
4562 path,
4563 member_name,
4564 symbol.name,
4565 index,
4566 si,
4567 .duplicate,
4568 ),
4569 .ANY => {
4570 symbol.si = si;
4571 break :comdat .skip;
4572 },
4573 .SAME_SIZE => {
4574 // TODO: Verify that this node isn't resized after creation
4575 _, const size = si.get(coff).ni.unwrap().?.location(&coff.mf).resolve(&coff.mf);
4576 if (size == section.header.size_of_raw_data) {
4577 symbol.si = si;
4578 break :comdat .skip;
4579 }
4580
4581 return coff.failMultipleDefinitions(
4582 path,
4583 member_name,
4584 symbol.name,
4585 index,
4586 si,
4587 .{ .size = .{ .a = size, .b = section.header.size_of_raw_data } },
4588 );
4589 },
4590 .EXACT_MATCH => {
4591 const sym = si.get(coff);
4592 const existing_crc = switch (coff.getNode(sym.ni.unwrap().?)) {
4593 .input_section => |isi| isi.inputSection(coff).crc,
4594 else => Crc32.hash(sym.ni.unwrap().?.sliceConst(&coff.mf)),
4595 };
4596
4597 if (existing_crc == section.comdat_crc) {
4598 symbol.si = si;
4599 break :comdat .skip;
4600 }
4601
4602 return coff.failMultipleDefinitions(
4603 path,
4604 member_name,
4605 symbol.name,
4606 index,
4607 si,
4608 .{ .crc = .{ .a = existing_crc, .b = section.comdat_crc } },
4609 );
4610 },
4611 .LARGEST => {
4612 // TODO: Resize existing .ni and replace with this section's contents
4613 // TODO: This will be tricky, what to do about existing InputSection?
4614 unreachable;
4615 },
4616 .NONE, .ASSOCIATIVE, _ => unreachable,
4617 }
4618 },
4619 };
4620 }
4621
4622 try coff.flushSectionMerges();
4623
4624 // Resolve pending associations, create parent sections
4625 var num_included_sections: u16 = 0;
4626 var num_included_relocs: u32 = 0;
4627 for (sections) |*section| {
4628 comdat: switch (section.comdat_result) {
4629 .pending_association => |root_assoc_sn| {
4630 const root_result = sections[root_assoc_sn.toIndex()].comdat_result;
4631 assert(root_result != .pending_association);
4632 section.comdat_result = root_result;
4633 continue :comdat root_result;
4634 },
4635 .include => {},
4636 .skip => {
4637 assert(switch (section.comdat) {
4638 .NONE, .ASSOCIATIVE => true,
4639 else => if (section.comdat_psi.unwrap()) |psi|
4640 pending_symbols.values()[psi].si != .null
4641 else
4642 pending_symbols.values()[section.psi.unwrap().?].si != .null,
4643 });
4644 continue;
4645 },
4646 .pending => unreachable,
4647 }
4648
4649 // Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid
4650 const section_name = section.name.toSlice(coff);
4651 if (std.mem.startsWith(u8, section_name, ".pdata"))
4652 continue;
4653
4654 num_included_sections += 1;
4655 num_included_symbols += section.num_symbols;
4656 num_included_relocs += section.header.number_of_relocations;
4657
4658 section.parent_si = (try coff.objectSectionMapIndex(
4659 section.name,
4660 .fromByteUnits(section.header.flags.ALIGN.toByteUnits() orelse 1),
4661 .fromFlags(section.header.flags),
4662 )).symbol(coff);
4663 }
4664
4665 try coff.nodes.ensureUnusedCapacity(gpa, num_included_sections);
4666 try coff.relocs.ensureUnusedCapacity(gpa, num_included_relocs);
4667 try coff.symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
4668 try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections);
4669
4670 for (sections) |*section| {
4671 if (section.parent_si == .null) continue;
4672
4673 const alignment: Alignment = .fromByteUnits(section.header.flags.ALIGN.toByteUnits() orelse 1);
4674 const ni = try section.parent_si.node(coff).addFloatingChild(gpa, &coff.mf, .{
4675 .size = alignment.forward(section.header.size_of_raw_data),
4676 .alignment = alignment,
4677 .moved = true,
4678 });
4679 coff.nodes.appendAssumeCapacity(.{ .input_section = @fromBackingInt(@intCast(coff.input_sections.items.len)) });
4680
4681 section.si = coff.addSymbolAssumeCapacity();
4682 if (section.psi.unwrap()) |psi|
4683 pending_symbols.values()[psi].si = section.si;
4684
4685 const sym = section.si.get(coff);
4686 sym.ni = .wrap(ni);
4687 sym.section_number = section.parent_si.get(coff).section_number;
4688
4689 coff.input_sections.addOneAssumeCapacity().* = .{
4690 .ioi = ioi,
4691 .si = section.si,
4692 .file_location = .{
4693 .offset = fl.offset + section.header.pointer_to_raw_data,
4694 .size = section.header.size_of_raw_data,
4695 },
4696 .first_li = @fromBackingInt(@intCast(coff.input_symbols.items.len)),
4697 .crc = section.comdat_crc,
4698 .comdat_si = if (section.comdat_psi.unwrap()) |psi|
4699 pending_symbols.values()[psi].si
4700 else
4701 .null,
4702 };
4703
4704 log.debug(
4705 "addInputSection({s}, 0x{x}) = {d}@{d}",
4706 .{ section.name.toSlice(coff), section.comdat_crc, section.si, sym.section_number },
4707 );
4708 coff.synth_prog_node.increaseEstimatedTotalItems(1);
4709 }
4710
4711 for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, i| {
4712 switch (symbol.value) {
4713 .weak_external_aux => continue,
4714 else => {},
4715 }
4716
4717 defer log.debug("addInputSymbol({s}, 0x{x}@{d}, {t}=0x{x}) = n{d} {d}@{d}", .{
4718 symbol.name.toSlice(coff),
4719 index,
4720 symbol.section_number,
4721 symbol.value,
4722 switch (symbol.value) {
4723 .weak_external_aux => unreachable,
4724 inline else => |v| v,
4725 },
4726 symbol.si.get(coff).ni,
4727 symbol.si,
4728 symbol.si.get(coff).section_number,
4729 });
4730
4731 const section = switch (symbol.section_number) {
4732 .UNDEFINED => switch (symbol.value) {
4733 .section,
4734 .static,
4735 .weak_external_aux,
4736 => unreachable,
4737 .external => {
4738 if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
4739 // If the alias itself is an undef external, we need to wait until flushing the weak
4740 // external global before creating a global for the alias, as another input could
4741 // still provide the weak external.
4742 const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
4743 weak_sym.setValue(.{ .weak_alias_name = symbol.name });
4744 weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
4745 }
4746
4747 // Deferred until referenced by a reloc in this object.
4748 // vcruntime.lib defines symbols like this (ie. memcpy_$fo$) that are not referenced
4749 continue;
4750 },
4751 .weak_external => |alias_index| {
4752 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4753 symbol.si = global_gop.value_ptr.si;
4754 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
4755 const sym = symbol.si.get(coff);
4756 const alias = pending_symbols.getPtr(alias_index) orelse
4757 return diags.failParse(
4758 path,
4759 "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}",
4760 .{
4761 index,
4762 symbol.name.toSlice(coff),
4763 fmtMemberNameString(member_name),
4764 alias_index,
4765 },
4766 );
4767
4768 if (alias.si == .null and alias_index > index) {
4769 // Resolve this once we see alias
4770 alias.weak_external_psi = .wrap(@intCast(i));
4771 } else {
4772 sym.setValue(if (alias.si.unwrap()) |alias_si| .{
4773 .weak_alias_si = alias_si,
4774 } else .{
4775 .weak_alias_name = alias.name,
4776 });
4777 sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux;
4778 }
4779 }
4780
4781 continue;
4782 },
4783 },
4784 .ABSOLUTE => {
4785 const value = sym: switch (symbol.value) {
4786 .static => |value| {
4787 symbol.si = coff.addSymbolAssumeCapacity();
4788 break :sym value;
4789 },
4790 .external => |value| {
4791 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4792 symbol.si = global_gop.value_ptr.si;
4793 if (global_gop.found_existing)
4794 return coff.failMultipleDefinitions(
4795 path,
4796 member_name,
4797 symbol.name,
4798 index,
4799 global_gop.value_ptr.si,
4800 .none,
4801 );
4802 break :sym value;
4803 },
4804 else => unreachable,
4805 };
4806
4807 const sym = symbol.si.get(coff);
4808 sym.rva = value;
4809 sym.section_number = .ABSOLUTE;
4810 continue;
4811 },
4812 .DEBUG => continue,
4813 else => |sn| &sections[sn.toIndex()],
4814 };
4815
4816 if (section.si == .null)
4817 continue;
4818
4819 if (symbol.si == .null) {
4820 switch (symbol.value) {
4821 .section => unreachable,
4822 .static => {
4823 symbol.si = coff.addSymbolAssumeCapacity();
4824 },
4825 .external => {
4826 assert(index != section.comdat_psi.unwrap());
4827 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4828 symbol.si = global_gop.value_ptr.si;
4829
4830 const sym = symbol.si.get(coff);
4831 if (global_gop.found_existing and sym.ni != .none)
4832 return coff.failMultipleDefinitions(
4833 path,
4834 member_name,
4835 symbol.name,
4836 index,
4837 global_gop.value_ptr.si,
4838 .none,
4839 );
4840 },
4841 .weak_external,
4842 .weak_external_aux,
4843 => unreachable,
4844 }
4845
4846 if (section.comdat_psi.unwrap() == @as(u32, @intCast(i)))
4847 coff.getNode(section.si.get(coff).ni.unwrap().?).input_section.inputSection(coff).comdat_si = symbol.si;
4848 }
4849
4850 if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
4851 assert(symbol.si != .null);
4852 const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
4853 weak_sym.setValue(.{ .weak_alias_si = symbol.si });
4854 weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
4855 }
4856
4857 if (section.si != symbol.si) {
4858 const sym = symbol.si.get(coff);
4859 assert(sym.ni == .none);
4860 sym.ni = section.si.get(coff).ni;
4861 switch (symbol.value) {
4862 .section => |v| sym.setExtra(.{ .size = v }),
4863 .static => |v| sym.setValue(.{ .node_offset = v }),
4864 .external => |v| switch (symbol.section_number) {
4865 .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable,
4866 else => sym.setValue(.{ .node_offset = v }),
4867 },
4868 .weak_external,
4869 .weak_external_aux,
4870 => unreachable,
4871 }
4872
4873 sym.section_number = section.si.get(coff).section_number;
4874 }
4875 }
4876
4877 const relocation_size = std.coff.Relocation.sizeOf();
4878 for (sections) |section| {
4879 if (section.si == .null) continue;
4880
4881 const loc_sym = section.si.get(coff);
4882 assert(loc_sym.loc_relocs == .none);
4883 loc_sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
4884
4885 if (section.header.number_of_relocations == 0) continue;
4886
4887 try fr.seekTo(fl.offset + section.header.pointer_to_relocations);
4888 for (0..section.header.number_of_relocations) |reloc_i| {
4889 var reloc: std.coff.Relocation = undefined;
4890 @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
4891 if (target_endian != native_endian)
4892 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
4893
4894 const symbol = pending_symbols.getPtr(reloc.symbol_table_index) orelse
4895 return diags.failParse(
4896 path,
4897 "relocation 0x{x} in section '{s}' of {f}{f} targets invalid symbol index 0x{x}",
4898 .{
4899 reloc_i,
4900 section.name.toSlice(coff),
4901 path.fmtEscapeString(),
4902 fmtMemberNameString(member_name),
4903 reloc.symbol_table_index,
4904 },
4905 );
4906
4907 if (symbol.si == .null) {
4908 assert(symbol.section_number == .UNDEFINED);
4909 switch (symbol.value) {
4910 .external => |size| {
4911 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4912 symbol.si = global_gop.value_ptr.si;
4913 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
4914 const sym = symbol.si.get(coff);
4915 sym.setExtra(.{ .size = @max(sym.size(coff), size) });
4916 }
4917 },
4918 else => unreachable,
4919 }
4920 }
4921
4922 assert(symbol.si != .null);
4923 try coff.addReloc(
4924 section.si,
4925 reloc.virtual_address - section.header.virtual_address,
4926 symbol.si,
4927 .pending,
4928 .{ .u16 = reloc.type },
4929 );
4930 }
4931 }
4932
4933 // Set up contiguous symbol ranges in `input_symbols` for both symbols we just created,
4934 // and symbols that were previously created as undefined, but we just defined.
4935 const SortContext = struct {
4936 v: []const PendingSymbol,
4937
4938 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
4939 const lhs = &ctx.v[a_index];
4940 const rhs = &ctx.v[b_index];
4941 if (lhs.section_number == rhs.section_number)
4942 return @backingInt(lhs.si) < @backingInt(rhs.si);
4943 return @backingInt(lhs.section_number) < @backingInt(rhs.section_number);
4944 }
4945 };
4946
4947 pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() });
4948
4949 try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
4950 var prev_sn: Symbol.SectionNumber = .DEBUG;
4951 var include_section = false;
4952 for (pending_symbols.values()) |symbol| {
4953 // The symbol may have not been included, or it's an undefined external / aux
4954 if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;
4955
4956 if (prev_sn != symbol.section_number) {
4957 prev_sn = symbol.section_number;
4958 if (symbol.section_number.hasIndex()) {
4959 const section = &sections[symbol.section_number.toIndex()];
4960 include_section = section.comdat_result == .include;
4961 if (include_section) {
4962 const isi = coff.getNode(section.si.get(coff).ni.unwrap().?).input_section;
4963 isi.inputSection(coff).first_li = @fromBackingInt(@intCast(coff.input_symbols.items.len));
4964 }
4965 }
4966 }
4967
4968 if (include_section) {
4969 assert(coff.getNode(symbol.si.get(coff).ni.unwrap().?) == .input_section);
4970 symbol.si.get(coff).setExtra(.{ .isli = @fromBackingInt(@intCast(coff.input_symbols.items.len)) });
4971 coff.input_symbols.addOneAssumeCapacity().* = .{
4972 .si = symbol.si,
4973 .name = symbol.name,
4974 };
4975 }
4976 }
4977}
4978
4979fn failMultipleDefinitions(
4980 coff: *Coff,
4981 path: std.Build.Cache.Path,
4982 member_name: ?[]const u8,
4983 name: String,
4984 index: u32,
4985 existing_si: Symbol.Index,
4986 comdat_reason: union(enum) {
4987 none: void,
4988 duplicate: void,
4989 size: struct { a: u64, b: u64 },
4990 crc: struct { a: u32, b: u32 },
4991 },
4992) error{ AlreadyReported, OutOfMemory } {
4993 const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none));
4994 var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
4995 try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)});
4996
4997 switch (coff.getNode(existing_si.get(coff).ni.unwrap().?)) {
4998 .input_section => |isi| {
4999 const other_ioi = isi.input(coff);
5000 err.addNote("first seen in input '{f}{f}'", .{
5001 other_ioi.path(coff).fmtEscapeString(),
5002 fmtMemberNameString(other_ioi.memberName(coff)),
5003 });
5004 },
5005 .nav, .uav => err.addNote("first seen in module '{s}'", .{
5006 coff.base.comp.zcu.?.root_mod.fully_qualified_name,
5007 }),
5008 else => unreachable,
5009 }
5010
5011 err.addNote("defined again in input '{f}{f}' (0x{x}))", .{ path, fmtMemberNameString(member_name), index });
5012 switch (comdat_reason) {
5013 .none => {},
5014 .duplicate => err.addNote("COMDAT rule requires no duplicates", .{}),
5015 .size => |s| err.addNote(
5016 "COMDAT rule require duplicates to have the same size ({d} vs {d})",
5017 .{ s.a, s.b },
5018 ),
5019 .crc => |s| err.addNote(
5020 "COMDAT rule require duplicates to have the same CRC (0x{x} vs 0x{x})",
5021 .{ s.a, s.b },
5022 ),
5023 }
5024
5025 return error.AlreadyReported;
5026}
5027
5028const ArchiveMemberHeader = struct {
5029 name: []const u8,
5030 size: u34,
5031};
5032
5033/// Return value lifetime is that of `header`
5034fn parseArchiveMemberHeader(
5035 diags: *link.Diags,
5036 path: std.Build.Cache.Path,
5037 header: *const std.coff.ArchiveMemberHeader,
5038 opt_longnames: ?[]const u8,
5039) !ArchiveMemberHeader {
5040 return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) {
5041 error.BadName => return diags.failParse(path, "malformed member name: '{s}'", .{&header.name}),
5042 error.BadSize => return diags.failParse(path, "malformed member size: '{s}'", .{&header.size}),
5043 error.BadEndOfHeader => return diags.failParse(path, "end of header was invalid", .{}),
5044 error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
5045 };
5046}
5047
5048fn parseArchiveMemberHeaderInner(
5049 header: *const std.coff.ArchiveMemberHeader,
5050 opt_longnames: ?[]const u8,
5051) !ArchiveMemberHeader {
5052 const name = try header.parseName(opt_longnames);
5053 const size = header.parseSize() catch return error.BadSize;
5054
5055 if (!std.mem.eql(u8, &header.end_of_header, std.coff.archive_end_of_header))
5056 return error.BadEndOfHeader;
5057
5058 return .{
5059 .name = name,
5060 .size = size,
5061 };
5062}
5063
5064fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5065 const comp = coff.base.comp;
5066 const gpa = comp.gpa;
5067 const diags = &comp.link_diags;
5068 const r = &fr.interface;
5069 const target_endian = coff.targetEndian();
5070
5071 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
5072
5073 const signature = try r.take(std.coff.archive_signature.len);
5074 if (!std.mem.eql(u8, signature, std.coff.archive_signature))
5075 return diags.failParse(path, "bad signature", .{});
5076
5077 var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
5078 var opt_longnames: ?[]const u8 = null;
5079 defer if (opt_longnames) |l| gpa.free(l);
5080
5081 var members: std.ArrayList(struct {
5082 offset: u32,
5083 iami: ?InputArchive.Member.Index,
5084 }) = .empty;
5085 defer members.deinit(gpa);
5086 var symbol_member_indices: std.ArrayList(u32) = .empty;
5087 defer symbol_member_indices.deinit(gpa);
5088
5089 const iai: InputArchive.Index = @fromBackingInt(@intCast(coff.input_archives.items.len));
5090 (try coff.input_archives.addOne(gpa)).* = .{
5091 .path = path,
5092 };
5093
5094 const first_iami = coff.input_archive_members.items.len;
5095 const first_iamsi = coff.input_archive_symbols.items.len;
5096 const first_symbol_indices_index = coff.input_archive_symbol_indices.count();
5097
5098 errdefer {
5099 for (coff.input_archive_symbol_indices.values()) |*v| {
5100 if (@backingInt(v.last) < first_iamsi) continue;
5101 if (@backingInt(v.first) >= first_iamsi) continue;
5102
5103 var iter = v.first;
5104 v.last = while (iter != v.last) {
5105 const sym = &coff.input_archive_symbols.items[@backingInt(iter)];
5106 if (@backingInt(sym.next) >= first_iamsi) {
5107 sym.next = iter;
5108 break iter;
5109 }
5110
5111 iter = sym.next;
5112 } else unreachable;
5113 }
5114
5115 // New entries in this map will only have pointed to iamsi we also just added
5116 coff.input_archive_symbol_indices.shrinkRetainingCapacity(first_symbol_indices_index);
5117 coff.input_archive_symbols.shrinkRetainingCapacity(first_iamsi);
5118 coff.input_archive_members.shrinkRetainingCapacity(first_iami);
5119 _ = coff.input_archives.pop();
5120 }
5121
5122 var pos = fr.logicalPos();
5123 const size = try fr.getSize();
5124 while (pos < size) : (pos = fr.logicalPos()) {
5125 if ((pos & 1) != 0) try r.discardAll(1);
5126 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
5127 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
5128
5129 const member_end = fr.logicalPos() + res.size;
5130 if (member_end > size)
5131 return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name });
5132
5133 log.debug("loadArchiveMember({s})", .{res.name});
5134
5135 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
5136 .first_linker => {
5137 if (!std.mem.eql(u8, res.name, "/"))
5138 return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name});
5139
5140 try fr.seekTo(fr.logicalPos() + res.size);
5141 opt_expected_kind = .second_linker;
5142 continue;
5143 },
5144 .second_linker => {
5145 if (!std.mem.eql(u8, res.name, "/"))
5146 return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name});
5147
5148 const num_members = try r.takeInt(u32, target_endian);
5149 pos = fr.logicalPos();
5150 if (pos + num_members * @sizeOf(u32) > member_end)
5151 return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members});
5152
5153 try members.ensureTotalCapacity(gpa, num_members);
5154 for (0..num_members) |_|
5155 members.addOneAssumeCapacity().* = .{
5156 .offset = try r.takeInt(u32, target_endian),
5157 .iami = null,
5158 };
5159
5160 const num_symbols = try r.takeInt(u32, target_endian);
5161 pos = fr.logicalPos();
5162 if (pos + num_symbols * @sizeOf(u16) > member_end)
5163 return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols});
5164
5165 try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
5166 for (0..num_symbols) |_|
5167 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1;
5168
5169 pos = fr.logicalPos();
5170 try coff.ensureManyUnusedStringCapacity(num_symbols, @intCast(member_end - pos));
5171 try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members);
5172 try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols);
5173 try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols);
5174
5175 var symbol_i: u32 = 0;
5176 while (pos < member_end and symbol_i < num_symbols) : ({
5177 pos = fr.logicalPos();
5178 symbol_i += 1;
5179 }) {
5180 const name = if (r.takeDelimiter(0) catch |err| switch (err) {
5181 error.StreamTooLong => null,
5182 else => |e| return e,
5183 }) |n| n else return diags.failParse(path, "unterminated string found in second linker member", .{});
5184
5185 const string = coff.getOrPutStringAssumeCapacity(name);
5186 const iamsi: InputArchive.Member.Symbol.Index = @fromBackingInt(@intCast(coff.input_archive_symbols.items.len));
5187 const symbol_gop = coff.input_archive_symbol_indices.getOrPutAssumeCapacity(string);
5188 if (!symbol_gop.found_existing) {
5189 symbol_gop.value_ptr.* = .{
5190 .first = iamsi,
5191 .last = iamsi,
5192 };
5193 } else {
5194 coff.input_archive_symbols.items[@backingInt(symbol_gop.value_ptr.last)].next = iamsi;
5195 symbol_gop.value_ptr.last = iamsi;
5196 }
5197
5198 const iami = members.items[symbol_member_indices.items[symbol_i]].iami orelse iami: {
5199 const iami: InputArchive.Member.Index = @fromBackingInt(@intCast(coff.input_archive_members.items.len));
5200 const member_offset = members.items[symbol_member_indices.items[symbol_i]].offset;
5201 coff.input_archive_members.addOneAssumeCapacity().* = .{
5202 .iai = iai,
5203 .name = undefined,
5204 .content = .{
5205 .object = .{
5206 .offset = member_offset,
5207 .size = undefined,
5208 },
5209 },
5210 .flags = .{
5211 .is_loaded = false,
5212 },
5213 };
5214
5215 members.items[symbol_member_indices.items[symbol_i]].iami = iami;
5216 break :iami iami;
5217 };
5218
5219 log.debug("loadArchiveMemberSymbol({s}) = ({d}, {d}, {d})", .{ name, iai, iami, iamsi });
5220
5221 coff.input_archive_symbols.addOneAssumeCapacity().* = .{
5222 .iami = iami,
5223 .next = iamsi,
5224 };
5225 }
5226
5227 if (symbol_i != num_symbols)
5228 return diags.failParse(
5229 path,
5230 " expected {d} entries in second linker member string table, but found {d}",
5231 .{ num_symbols, symbol_i },
5232 );
5233
5234 try fr.seekTo(member_end);
5235 opt_expected_kind = .longnames;
5236 continue;
5237 },
5238 .longnames => {
5239 // This member is optional
5240 if (std.mem.eql(u8, res.name, "//"))
5241 opt_longnames = try r.readAlloc(gpa, @intCast(res.size));
5242
5243 opt_expected_kind = null;
5244 break;
5245 },
5246 else => unreachable,
5247 };
5248 }
5249
5250 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
5251 .first_linker => return diags.failParse(path, "missing first linker member", .{}),
5252 .second_linker => return diags.failParse(path, "missing second linker member", .{}),
5253 else => {},
5254 };
5255
5256 // Validate / read names and sizes of all the referenced members, enumerate imports
5257 for (coff.input_archive_members.items[first_iami..]) |*member| {
5258 try fr.seekTo(member.content.object.offset);
5259
5260 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
5261 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
5262
5263 try coff.ensureUnusedStringCapacity(res.name.len);
5264 member.name = coff.getOrPutStringAssumeCapacity(res.name);
5265
5266 const member_sig = try r.peek(4);
5267 const machine: std.coff.IMAGE.FILE.MACHINE =
5268 @fromBackingInt(@intCast(std.mem.readInt(u16, member_sig[0..2], target_endian)));
5269 const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);
5270
5271 log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{
5272 res.name,
5273 member.content.object.offset,
5274 res.size,
5275 });
5276
5277 const expected_machine = comp.root_mod.resolved_target.result.toCoffMachine();
5278 if (machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff) {
5279 const import_header = try r.takeStruct(std.coff.ImportHeader, target_endian);
5280 const strings = r.take(import_header.size_of_data) catch |err| switch (err) {
5281 error.EndOfStream => return diags.failParse(path, "invalid data size in import header '{s}'", .{res.name}),
5282 else => |e| return e,
5283 };
5284
5285 var split = std.mem.splitScalar(u8, strings, 0);
5286 const symbol_name = split.next() orelse
5287 return diags.failParse(path, "invalid symbol name string in import header '{s}'", .{res.name});
5288 var lib_name = split.next() orelse
5289 return diags.failParse(path, "invalid dll name string in import header '{s}' ('{s}')", .{ res.name, symbol_name });
5290
5291 if (import_header.machine != expected_machine)
5292 return diags.failParse(path, "machine mismatch in import header '{s}' ('{s}'): expected {t}, found {t}", .{
5293 res.name,
5294 symbol_name,
5295 expected_machine,
5296 machine,
5297 });
5298
5299 const ext = ".dll";
5300 if (!std.mem.endsWith(u8, lib_name, ext))
5301 return diags.failParse(
5302 path,
5303 "unexpected extension for import '{s} ('{s}'): '{s}'",
5304 .{ res.name, symbol_name, lib_name },
5305 );
5306
5307 lib_name = lib_name[0 .. lib_name.len - ext.len];
5308 log.debug("verifyArchiveImportHeader({s}, {s}, {s}) = {t} ({t})", .{
5309 res.name,
5310 symbol_name,
5311 lib_name,
5312 import_header.types.type,
5313 import_header.types.name_type,
5314 });
5315
5316 try coff.ensureManyUnusedStringCapacity(2, strings.len - ext.len);
5317 member.content = .{
5318 .import = .{
5319 .symbol_name = coff.getOrPutStringAssumeCapacity(symbol_name),
5320 .lib_name = coff.getOrPutStringAssumeCapacity(lib_name),
5321 .import_ordinal_hint = import_header.hint,
5322 .type = import_header.types.type,
5323 .name_type = import_header.types.name_type,
5324 },
5325 };
5326 } else {
5327 member.content.object.size = res.size;
5328 // Microsoft's CRT contains members that set .UNKNOWN but do have undef symbols
5329 if (machine != expected_machine and machine != .UNKNOWN) {
5330 return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{
5331 res.name,
5332 expected_machine,
5333 machine,
5334 });
5335 }
5336 }
5337 }
5338}
5339
5340fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5341 const comp = coff.base.comp;
5342 const gpa = comp.gpa;
5343 const diags = &comp.link_diags;
5344 const r = &fr.interface;
5345
5346 log.debug("loadRes({f})", .{path.fmtEscapeString()});
5347
5348 _ = gpa;
5349 _ = diags;
5350 _ = r;
5351}
5352
5353fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5354 const comp = coff.base.comp;
5355 const gpa = comp.gpa;
5356 const diags = &comp.link_diags;
5357 const r = &fr.interface;
5358
5359 log.debug("loadDll({f})", .{path.fmtEscapeString()});
5360
5361 _ = gpa;
5362 _ = diags;
5363 _ = r;
5364}
5365
5366pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
5367 const sub_prog_node = prog_node.start("COFF Prelink", 0);
5368 defer sub_prog_node.end();
5369
5370 const base = coff.base;
5371 const comp = base.comp;
5372
5373 log.debug("prelink()", .{});
5374
5375 if (coff.pending_default_libs.items.len > 0) {
5376 // Libs provided by /DEFAULTLIB arguments in objects are searched after all other inputs
5377 const gpa = comp.gpa;
5378 const arena = comp.arena;
5379 const target = &comp.root_mod.resolved_target.result;
5380
5381 defer {
5382 for (coff.pending_default_libs.items) |l| gpa.free(l.path);
5383 coff.pending_default_libs.clearAndFree(gpa);
5384 }
5385
5386 assert(comp.config.link_libc);
5387 const libc_installation = comp.libc_installation.?;
5388 const all_paths: [3]?[]const u8 = .{
5389 libc_installation.crt_dir,
5390 libc_installation.msvc_lib_dir,
5391 libc_installation.kernel32_lib_dir,
5392 };
5393 const search_paths = all_paths[0..if (target.abi == .msvc or target.abi == .itanium) 3 else 1];
5394 lib: for (coff.pending_default_libs.items) |lib| {
5395 if (!std.mem.eql(u8, std.fs.path.extension(lib.path), ".lib"))
5396 return comp.link_diags.failParse(
5397 lib.ioi.path(coff),
5398 "/DEFAULTLIB library '{s}' had unexpected extension",
5399 .{lib.path},
5400 );
5401
5402 log.debug("loadDefaultLib({s}, {f})", .{ lib.path, lib.ioi.path(coff) });
5403 for (search_paths) |opt_path| if (opt_path) |search_path| {
5404 const lib_path = try Path.initCwd(search_path).join(arena, lib.path);
5405 const archive = link.openObject(comp.io, lib_path, false, false) catch |err| switch (err) {
5406 error.FileNotFound => {
5407 arena.free(lib_path.sub_path);
5408 continue;
5409 },
5410 else => |e| return comp.link_diags.failParse(
5411 lib.ioi.path(coff),
5412 "error opening /DEFAULTLIB library '{s}': {t}",
5413 .{ lib.path, e },
5414 ),
5415 };
5416 errdefer archive.file.close(comp.io);
5417
5418 coff.loadInput(.{ .archive = archive }) catch |err| switch (err) {
5419 else => |e| return comp.link_diags.failParse(
5420 lib.ioi.path(coff),
5421 "error loading /DEFAULTLIB library '{s}': {t}",
5422 .{ lib.path, e },
5423 ),
5424 };
5425
5426 break :lib;
5427 };
5428
5429 return comp.link_diags.failParse(
5430 lib.ioi.path(coff),
5431 "/DEFAULTLIB library '{s}' was not found",
5432 .{lib.path},
5433 );
5434 }
5435 }
5436
5437 coff.inputs_complete = true;
5438 if (comp.zcu == null)
5439 coff.exports_complete = true;
5440}
5441
5442pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void {
5443 coff.updateNavInner(pt, nav_index) catch |err| switch (err) {
5444 error.MappedFileIo => return coff.base.cgFail(
5445 nav_index,
5446 "linker failed to update variable: {t}",
5447 .{coff.mf.io_err.?},
5448 ),
5449 else => |e| return e,
5450 };
5451}
5452fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
5453 const zcu = pt.zcu;
5454 const gpa = zcu.gpa;
5455 const ip = &zcu.intern_pool;
5456
5457 const nav = ip.getNav(nav_index);
5458 if (ip.indexToKey(nav.resolved.?.value) == .@"extern") return;
5459 if (!Type.fromInterned(nav.resolved.?.type).hasRuntimeBits(zcu)) return;
5460
5461 const nmi = try coff.navMapIndex(zcu, nav_index);
5462 const si = nmi.symbol(coff);
5463 log.debug("updateNav({f}) = {d}", .{ nav.fqn.fmt(ip), si });
5464 const ni = ni: {
5465 switch (si.get(coff).ni) {
5466 .none => {
5467 const sec_si = try coff.navSection(zcu, nav.resolved.?);
5468 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5469 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
5470 const ni = try sec_si.node(coff).addFloatingChild(gpa, &coff.mf, .{
5471 .alignment = .fromIp(zcu.navAlignment(nav_index)),
5472 .moved = true,
5473 });
5474 coff.nodes.appendAssumeCapacity(.{ .nav = nmi });
5475 const sym = si.get(coff);
5476 sym.ni = .wrap(ni);
5477 sym.section_number = sec_si.get(coff).section_number;
5478 },
5479 else => si.deleteLocationRelocs(coff),
5480 }
5481 const sym = si.get(coff);
5482 assert(sym.loc_relocs == .none);
5483 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
5484 if (!isImage(coff) and sym.target_relocs != .none)
5485 try coff.pendingSymbolTableEntry(si);
5486
5487 break :ni sym.ni.unwrap().?;
5488 };
5489
5490 {
5491 var nw: MappedFile.Node.Writer = undefined;
5492 ni.writer(gpa, &coff.mf, &nw);
5493 defer nw.deinit();
5494 codegen.generateSymbol(
5495 &coff.base,
5496 pt,
5497 .fromInterned(nav.resolved.?.value),
5498 &nw.interface,
5499 .{ .atom_index = @fromBackingInt(@intCast(@backingInt(si))) },
5500 ) catch |err| switch (err) {
5501 error.WriteFailed => return nw.err.?,
5502 else => |e| return e,
5503 };
5504
5505 si.get(coff).setSize(coff, @intCast(nw.interface.end));
5506 try si.applyLocationRelocs(coff);
5507 }
5508
5509 if (nav.resolved.?.@"linksection".unwrap()) |_| {
5510 try ni.resizeLeaf(gpa, &coff.mf, si.get(coff).extra.size);
5511 }
5512
5513 // The NAV's node is done---now generate any UAVs or lazy code/data which the NAV needs.
5514 try coff.genPending(pt);
5515}
5516
5517pub fn updateContainerType(
5518 coff: *Coff,
5519 pt: Zcu.PerThread,
5520 ty: InternPool.Index,
5521 success: bool,
5522) link.Error!void {
5523 if (!success) return;
5524 var lazy_it = coff.lazy.iterator();
5525 while (lazy_it.next()) |lazy| if (lazy.value.map.getIndex(ty)) |lmi| {
5526 if (lazy.value.pending_index <= lmi) continue;
5527 // This type has changed on this incremental update, so update the lazy code/data.
5528 try coff.genLazy(pt, .{ .kind = lazy.key, .index = @intCast(lmi) });
5529 };
5530}
5531
5532pub fn lowerUav(
5533 coff: *Coff,
5534 pt: Zcu.PerThread,
5535 uav_val: InternPool.Index,
5536 uav_align: InternPool.Alignment,
5537) link.Error!link.File.SymbolId {
5538 const zcu = pt.zcu;
5539 const gpa = zcu.gpa;
5540
5541 try coff.pending_uavs.ensureUnusedCapacity(gpa, 1);
5542 const umi = try coff.uavMapIndex(uav_val);
5543 const si = umi.symbol(coff);
5544 const need_update: bool = update: {
5545 const existing_ni = si.get(coff).ni.unwrap() orelse break :update true;
5546 break :update Alignment.compare(.fromIp(uav_align), .gt, existing_ni.alignment(&coff.mf));
5547 };
5548 if (need_update) {
5549 const gop = coff.pending_uavs.getOrPutAssumeCapacity(umi);
5550 if (gop.found_existing) {
5551 gop.value_ptr.alignment = gop.value_ptr.alignment.max(uav_align);
5552 } else {
5553 gop.value_ptr.* = .{
5554 .alignment = uav_align,
5555 };
5556 coff.const_prog_node.increaseEstimatedTotalItems(1);
5557 }
5558 }
5559 return @fromBackingInt(@intCast(@backingInt(si)));
5560}
5561
5562pub fn updateFunc(
5563 coff: *Coff,
5564 pt: Zcu.PerThread,
5565 func_index: InternPool.Index,
5566 mir: *const codegen.AnyMir,
5567) link.Error!void {
5568 coff.updateFuncInner(pt, func_index, mir) catch |err| switch (err) {
5569 else => |e| return e,
5570 error.MappedFileIo => return coff.base.cgFail(
5571 pt.zcu.funcInfo(func_index).owner_nav,
5572 "linker failed to update function: {t}",
5573 .{coff.mf.io_err.?},
5574 ),
5575 };
5576}
5577fn updateFuncInner(
5578 coff: *Coff,
5579 pt: Zcu.PerThread,
5580 func_index: InternPool.Index,
5581 mir: *const codegen.AnyMir,
5582) !void {
5583 const zcu = pt.zcu;
5584 const gpa = zcu.gpa;
5585 const ip = &zcu.intern_pool;
5586 const func = zcu.funcInfo(func_index);
5587 const nav = ip.getNav(func.owner_nav);
5588
5589 const nmi = try coff.navMapIndex(zcu, func.owner_nav);
5590 const si = nmi.symbol(coff);
5591 log.debug("updateFunc({f}) = {d}", .{ nav.fqn.fmt(ip), si });
5592 const ni = ni: {
5593 switch (si.get(coff).ni) {
5594 .none => {
5595 const sec_si = try coff.navSection(zcu, nav.resolved.?);
5596 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5597 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
5598 const mod = zcu.navFileScope(func.owner_nav).mod.?;
5599 const target = &mod.resolved_target.result;
5600 const ni = try sec_si.node(coff).addFloatingChild(gpa, &coff.mf, .{
5601 .alignment = switch (nav.resolved.?.@"align") {
5602 .none => switch (mod.optimize_mode) {
5603 .debug,
5604 .safe,
5605 .fast,
5606 => .fromIp(target_util.defaultFunctionAlignment(target)),
5607 .small => .fromIp(target_util.minFunctionAlignment(target)),
5608 },
5609 else => |a| .fromIp(a.maxStrict(target_util.minFunctionAlignment(target))),
5610 },
5611 .moved = true,
5612 });
5613 coff.nodes.appendAssumeCapacity(.{ .nav = nmi });
5614 const sym = si.get(coff);
5615 sym.ni = .wrap(ni);
5616 sym.section_number = sec_si.get(coff).section_number;
5617 },
5618 else => si.deleteLocationRelocs(coff),
5619 }
5620 const sym = si.get(coff);
5621 assert(sym.loc_relocs == .none);
5622 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
5623 if (!isImage(coff) and sym.target_relocs != .none)
5624 try coff.pendingSymbolTableEntry(si);
5625 break :ni sym.ni.unwrap().?;
5626 };
5627
5628 var nw: MappedFile.Node.Writer = undefined;
5629 ni.writer(gpa, &coff.mf, &nw);
5630 defer nw.deinit();
5631 codegen.emitFunction(
5632 &coff.base,
5633 pt,
5634 func_index,
5635 @fromBackingInt(@intCast(@backingInt(si))),
5636 mir,
5637 &nw.interface,
5638 .none,
5639 ) catch |err| switch (err) {
5640 error.WriteFailed => return nw.err.?,
5641 else => |e| return e,
5642 };
5643
5644 si.get(coff).setSize(coff, @intCast(nw.interface.end));
5645 try si.applyLocationRelocs(coff);
5646
5647 // The NAV's node is done---now generate any UAVs or lazy code/data which the NAV needs.
5648 try coff.genPending(pt);
5649}
5650
5651pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
5652 coff.genLazyInner(pt, .{
5653 .kind = .const_data,
5654 .index = @intCast(coff.lazy.getPtr(.const_data).map.getIndex(.anyerror_type) orelse return),
5655 }) catch |err| switch (err) {
5656 else => |e| return e,
5657 error.MappedFileIo => return coff.base.comp.link_diags.fail(
5658 "updateErrorData failed: {t}",
5659 .{coff.mf.io_err.?},
5660 ),
5661 };
5662}
5663
5664fn flushImplib(
5665 coff: *Coff,
5666 implib_file: []const u8,
5667) !void {
5668 // Emitting implibs is only valid for images
5669
5670 const comp = coff.base.comp;
5671 const gpa = comp.gpa;
5672 const io = comp.io;
5673
5674 const image_name = std.mem.sliceTo(
5675 coff.export_table.ni.slice(&coff.mf)[@sizeOf(std.coff.ExportDirectoryTable)..],
5676 0,
5677 );
5678 const machine_type = coff.targetLoad(&coff.headerPtr().machine);
5679 const members = members: {
5680 const def_arena: std.heap.ArenaAllocator = .init(gpa);
5681 var def: ModuleDefinition = .{
5682 .name = image_name,
5683 .arena = def_arena,
5684 .type = .mingw,
5685 };
5686 defer def.deinit();
5687
5688 try def.exports.ensureUnusedCapacity(
5689 def.arena.allocator(),
5690 coff.export_table.entries.count(),
5691 );
5692
5693 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
5694 for (coff.export_table.entries.values(), 0..) |entry, ord| {
5695 const name = name_table_slice[entry.name_index..][0..entry.name_len];
5696 const section_number = entry.si.get(coff).section_number;
5697 const import_type: std.coff.ImportType = switch (section_number.symbol(coff)) {
5698 .data, .rdata => .DATA,
5699 .text => .CODE,
5700 else => return comp.link_diags.fail(
5701 "unsupported section for export '{s}': {s}",
5702 .{ name, &section_number.header(coff).name },
5703 ),
5704 };
5705
5706 def.exports.appendAssumeCapacity(.{
5707 .name = name,
5708 .mangled_symbol_name = null,
5709 .ext_name = null,
5710 .import_name = null,
5711 .export_as = null,
5712 .no_name = false,
5713 .ordinal = @intCast(ord),
5714 .type = import_type,
5715 .private = false,
5716 });
5717 }
5718
5719 def.fixupForImportLibraryGeneration(machine_type);
5720 break :members try implib.getMembers(gpa, def, machine_type);
5721 };
5722 defer members.deinit();
5723
5724 const lib_sub_path = try std.fs.path.join(gpa, &.{
5725 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
5726 implib_file,
5727 });
5728 defer gpa.free(lib_sub_path);
5729
5730 const lib_final_file = try coff.base.emit.root_dir.handle.createFile(io, lib_sub_path, .{ .truncate = true });
5731 defer lib_final_file.close(io);
5732 var buffer: [1024]u8 = undefined;
5733 var file_writer = lib_final_file.writer(io, &buffer);
5734 try implib.writeCoffArchive(gpa, &file_writer.interface, members);
5735 try file_writer.interface.flush();
5736}
5737
5738fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
5739 const comp = coff.base.comp;
5740 const gpa = comp.gpa;
5741 const max_notes = 4;
5742
5743 var undef_indices: std.ArrayList(u32) = .empty;
5744 for (coff.relocs.items, 0..) |reloc, reloc_i| {
5745 if (reloc.flags.free) continue;
5746 const target_sym = reloc.target.get(coff);
5747 switch (target_sym.ni) {
5748 .none => {
5749 assert(target_sym.gmi != .none);
5750 if (target_sym.section_number == .ABSOLUTE) continue;
5751 (try undef_indices.addOne(gpa)).* = @intCast(reloc_i);
5752 },
5753 else => continue,
5754 }
5755 }
5756
5757 if (undef_indices.items.len == 0) return;
5758
5759 const undefLessThan = struct {
5760 fn lessThan(ctx: *const Coff, lhs: u32, rhs: u32) bool {
5761 const reloc_l = &ctx.relocs.items[lhs];
5762 const reloc_r = &ctx.relocs.items[rhs];
5763 if (reloc_l.target == reloc_r.target)
5764 return @backingInt(reloc_l.loc) < @backingInt(reloc_r.loc)
5765 else
5766 return @backingInt(reloc_l.target) < @backingInt(reloc_r.target);
5767 }
5768 }.lessThan;
5769
5770 std.mem.sortUnstable(u32, undef_indices.items, coff, undefLessThan);
5771
5772 var start_i: usize = 0;
5773 var num_unique_references: usize = 1;
5774 for (0..undef_indices.items.len) |i| {
5775 const target = coff.relocs.items[undef_indices.items[start_i]].target;
5776 if (i == undef_indices.items.len - 1 or target != coff.relocs.items[undef_indices.items[i + 1]].target) {
5777 defer {
5778 start_i = i + 1;
5779 num_unique_references = 1;
5780 }
5781
5782 const num_full_notes = @min(max_notes, num_unique_references);
5783 var err = try comp.link_diags.addErrorWithNotes(
5784 num_full_notes + @intFromBool(num_unique_references > max_notes),
5785 );
5786 const target_sym = target.get(coff);
5787 try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.name(coff).toSlice(coff)});
5788
5789 // TODO: If lib_name is set, show the user
5790
5791 var prev_loc_si: Symbol.Index = .null;
5792 for (undef_indices.items[start_i .. i + 1]) |reference_i| {
5793 if (err.note_slot == num_full_notes) break;
5794
5795 const reloc = &coff.relocs.items[reference_i];
5796 const loc_si = reloc.loc;
5797 if (loc_si == prev_loc_si) continue;
5798 defer prev_loc_si = loc_si;
5799
5800 const loc_sym = loc_si.get(coff);
5801
5802 // TODO: Make this a helper for anything that needs to report "referenced by" notes
5803 switch (coff.getNode(loc_sym.ni.unwrap().?)) {
5804 .data_directories => {
5805 const dir: std.coff.IMAGE.DIRECTORY_ENTRY =
5806 @fromBackingInt(@intCast(reloc.offset / @sizeOf(std.coff.ImageDataDirectory)));
5807 err.addNote("referenced by data directory entry: {t}", .{dir});
5808 },
5809 .optional_header => err.addNote("referenced by optional header field", .{}),
5810 .input_section => |isi| {
5811 const other_ioi = isi.input(coff);
5812 if (loc_sym.gmi == .none) {
5813 const section = isi.inputSection(coff);
5814 const section_name = coff.getNode(loc_sym.ni.unwrap().?.parent(&coff.mf).unwrap().?)
5815 .object_section.name(coff).toSlice(coff);
5816
5817 if (section.comdat_si != .null) {
5818 const comdat_sym = section.comdat_si.get(coff);
5819 const comdat_name = if (comdat_sym.gmi != .none)
5820 comdat_sym.gmi.name(coff).toSlice(coff)
5821 else
5822 comdat_sym.extra.isli.name(coff).toSlice(coff);
5823
5824 err.addNote("referenced by input COMDAT section '{s}={s}' '{f}{f}'", .{
5825 section_name,
5826 comdat_name,
5827 other_ioi.path(coff).fmtEscapeString(),
5828 fmtMemberNameString(other_ioi.memberName(coff)),
5829 });
5830 } else {
5831 err.addNote("referenced by input section '{s}' '{f}{f}'", .{
5832 section_name,
5833 other_ioi.path(coff).fmtEscapeString(),
5834 fmtMemberNameString(other_ioi.memberName(coff)),
5835 });
5836 }
5837 } else {
5838 err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{
5839 loc_sym.gmi.name(coff).toSlice(coff),
5840 other_ioi.path(coff).fmtEscapeString(),
5841 fmtMemberNameString(other_ioi.memberName(coff)),
5842 });
5843 }
5844 },
5845 .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{
5846 gmi.name(coff).toSlice(coff),
5847 }),
5848 inline .nav,
5849 .uav,
5850 .lazy_code,
5851 .lazy_const_data,
5852 => |val, tag| {
5853 err.addNote("referenced by '{f}'", .{
5854 format: switch (tag) {
5855 .nav => {
5856 const ip = &comp.zcu.?.intern_pool;
5857 break :format ip.getNav(val.navIndex(coff)).fqn.fmt(ip);
5858 },
5859 .uav => Value.fromInterned(val.uavValue(coff)).fmtValue(.{
5860 .zcu = coff.base.comp.zcu.?,
5861 .tid = tid,
5862 }),
5863 inline .lazy_code, .lazy_const_data => Type.fromInterned(val.lazySymbol(coff).ty).fmt(.{
5864 .zcu = coff.base.comp.zcu.?,
5865 .tid = tid,
5866 }),
5867 else => unreachable,
5868 },
5869 });
5870 },
5871 else => unreachable,
5872 }
5873 }
5874
5875 if (num_unique_references > max_notes)
5876 err.addNote("referenced {d} more times", .{num_unique_references - max_notes});
5877 } else if (i != start_i and
5878 coff.relocs.items[undef_indices.items[i - 1]].loc != coff.relocs.items[undef_indices.items[i]].loc)
5879 {
5880 num_unique_references += 1;
5881 }
5882 }
5883
5884 return error.AlreadyReported;
5885}
5886
5887pub fn flush(
5888 coff: *Coff,
5889 arena: std.mem.Allocator,
5890 tid: Zcu.PerThread.Id,
5891 prog_node: std.Progress.Node,
5892) link.Error!void {
5893 _ = arena;
5894 const sub_prog_node = prog_node.start("COFF Flush", 0);
5895 defer sub_prog_node.end();
5896
5897 const comp = coff.base.comp;
5898
5899 while (try coff.resolve(tid)) {}
5900 while (try coff.idle(tid)) {}
5901
5902 // This has to occur after all other flushMoved / flushResized have resolved,
5903 // but it will also generate one more set of resizes and moves.
5904 if (coff.symbol_table.pending_shrink) {
5905 coff.symbol_table.pending_shrink = false;
5906
5907 const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
5908 coff.symbol_table.ni.resizeLeaf(
5909 comp.gpa,
5910 &coff.mf,
5911 number_of_symbols * std.coff.Symbol.sizeOf(),
5912 ) catch |err| switch (err) {
5913 else => |e| return e,
5914 error.MappedFileIo => return comp.link_diags.fail(
5915 "linker failed to compact symbol table: {t}",
5916 .{coff.mf.io_err.?},
5917 ),
5918 };
5919 }
5920 while (try coff.idle(tid)) {}
5921
5922 if (coff.isImage())
5923 try coff.reportUndefs(tid);
5924
5925 if (comp.emit_implib) |implib_file|
5926 coff.flushImplib(implib_file) catch |err|
5927 return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err });
5928
5929 coff.mf.flush() catch |err| switch (err) {
5930 error.Canceled => |e| return e,
5931 else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}),
5932 };
5933
5934 if (coff.options.enable_link_snapshots)
5935 coff.dumpStderr(tid) catch |err|
5936 return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err});
5937}
5938
5939/// Runs a single "resolution" task.
5940/// These are tasks that need to modify the node structure in some way.
5941/// They must run in a defined order with respect to linker tasks.
5942fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
5943 const comp = coff.base.comp;
5944 task: {
5945 while (coff.section_merge_pending_index < coff.section_merges.count()) {
5946 defer coff.section_merge_pending_index += 1;
5947 const sub_prog_node = coff.synth_prog_node.start(
5948 coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
5949 0,
5950 );
5951 defer sub_prog_node.end();
5952 coff.flushSectionMerge(coff.section_merge_pending_index) catch |err| switch (err) {
5953 //error.OutOfMemory => |e| return e,
5954 else => |e| return comp.link_diags.fail(
5955 "linker failed to merge section {s} into {s}: {t}",
5956 .{
5957 coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
5958 coff.section_merges.values()[coff.section_merge_pending_index].toSlice(coff),
5959 e,
5960 },
5961 ),
5962 };
5963 break :task;
5964 }
5965 if (coff.pending_input) |pending_iami| {
5966 const name_slice = pending_iami.member(coff).name.toSlice(coff);
5967 const sub_prog_node = coff.input_prog_node.start(
5968 name_slice,
5969 0,
5970 );
5971 defer sub_prog_node.end();
5972 coff.pending_input = null;
5973 coff.flushInputMember(pending_iami) catch |err| switch (err) {
5974 error.OutOfMemory => return error.OutOfMemory,
5975 else => |e| return comp.link_diags.fail(
5976 "linker failed to load archive member '{f}{f}': {t}",
5977 .{
5978 pending_iami.member(coff).iai.path(coff),
5979 fmtMemberNameString(name_slice),
5980 e,
5981 },
5982 ),
5983 };
5984 break :task;
5985 }
5986 if (coff.exports_complete and coff.global_pending_index < coff.globals.count()) {
5987 const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
5988 const sub_prog_node = coff.synth_prog_node.start(
5989 gmi.name(coff).toSlice(coff),
5990 0,
5991 );
5992 defer sub_prog_node.end();
5993 if (coff.flushGlobal(gmi) catch |err| switch (err) {
5994 else => |e| return e,
5995 error.MappedFileIo => return comp.link_diags.fail(
5996 "linker failed to lower constant: {t}",
5997 .{coff.mf.io_err.?},
5998 ),
5999 }) coff.global_pending_index += 1;
6000 break :task;
6001 }
6002 if (coff.exports_complete and coff.pending_special_symbol != .none) {
6003 coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) catch |err|
6004 switch (err) {
6005 error.OutOfMemory => |e| return e,
6006 else => |e| return comp.link_diags.fail(
6007 "linker failed to flush special symbols: {t}",
6008 .{e},
6009 ),
6010 };
6011 break :task;
6012 }
6013 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) {
6014 defer coff.symbol_table.pending_symbol_index += 1;
6015 const si = coff.symbol_table.symbols.keys()[coff.symbol_table.pending_symbol_index];
6016 const sym = si.get(coff);
6017 const sub_prog_node = coff.idleProgNode(
6018 tid,
6019 coff.symbol_prog_node,
6020 if (sym.ni.unwrap()) |sym_ni|
6021 coff.getNode(sym_ni)
6022 else
6023 .{ .import_thunk = sym.gmi },
6024 );
6025 defer sub_prog_node.end();
6026 coff.flushSymbolTableEntry(
6027 coff.symbol_table.pending_symbol_index,
6028 ) catch |err| switch (err) {
6029 error.OutOfMemory => return error.OutOfMemory,
6030 else => |e| return comp.link_diags.fail(
6031 "linker failed to flush symbol table entry: {t}",
6032 .{e},
6033 ),
6034 };
6035 break :task;
6036 }
6037 }
6038
6039 if (coff.section_merge_pending_index < coff.section_merges.count()) return true;
6040 if (coff.pending_input != null) return true;
6041 if (coff.exports_complete and coff.globals.count() > coff.global_pending_index) return true;
6042 assert(!coff.exports_complete or coff.inputs_complete);
6043 if (coff.exports_complete and coff.pending_special_symbol != .none) return true;
6044 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) return true;
6045 return false;
6046}
6047
6048pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
6049 // Idle tasks should not modify create / modify nodes, otherwise the output is not reproducible.
6050 coff.mf.nodes_lock.lock();
6051 defer coff.mf.nodes_lock.unlock();
6052
6053 const comp = coff.base.comp;
6054 task: {
6055 // TODO: Idle task for flushing obj into lib
6056 if (coff.input_section_pending_index < coff.input_sections.items.len) {
6057 const isi: Node.InputSection.Index = @fromBackingInt(@intCast(coff.input_section_pending_index));
6058 coff.input_section_pending_index += 1;
6059 const sub_prog_node = coff.idleProgNode(tid, coff.input_prog_node, coff.getNode(isi.symbol(coff).node(coff)));
6060 defer sub_prog_node.end();
6061 coff.flushInputSection(isi) catch |err| switch (err) {
6062 else => |e| {
6063 const ioi = isi.input(coff);
6064 return comp.link_diags.fail(
6065 "linker failed to read input section '{s}' from \"{f}{f}\": {t}",
6066 .{
6067 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),
6068 ioi.path(coff).fmtEscapeString(),
6069 fmtMemberNameString(ioi.memberName(coff)),
6070 e,
6071 },
6072 );
6073 },
6074 };
6075 break :task;
6076 }
6077 while (coff.mf.updates.pop()) |ni| : (coff.mf.update_prog_node.completeOne()) {
6078 if (ni.pendingDelete(&coff.mf)) continue;
6079 const clean_moved = ni.cleanMoved(&coff.mf);
6080 const clean_resized = ni.cleanResized(&coff.mf);
6081 const clean_next_moved = ni.cleanNextMoved(&coff.mf);
6082 if (!clean_moved and !clean_resized and !clean_next_moved) continue;
6083 const sub_prog_node =
6084 coff.idleProgNode(tid, coff.mf.update_prog_node, coff.getNode(ni));
6085 defer sub_prog_node.end();
6086 if (clean_moved) try coff.flushMoved(ni);
6087 if (clean_resized) try coff.flushResized(ni);
6088 break :task;
6089 }
6090 while (coff.pending_members.pop()) |pending_mi| {
6091 const sub_prog_node = coff.idleProgNode(
6092 tid,
6093 coff.symbol_prog_node,
6094 coff.getNode(pending_mi.key.get(coff).content_ni),
6095 );
6096 defer sub_prog_node.end();
6097 try coff.flushMember(pending_mi.key);
6098 break :task;
6099 }
6100 if (coff.exports_complete and coff.export_table.pending_sort) {
6101 defer coff.export_table.pending_sort = false;
6102 const sub_prog_node = coff.idleProgNode(
6103 tid,
6104 coff.synth_prog_node,
6105 coff.getNode(coff.export_table.ni),
6106 );
6107 defer sub_prog_node.end();
6108
6109 coff.flushExportsSort();
6110 break :task;
6111 }
6112 }
6113 if (coff.input_sections.items.len > coff.input_section_pending_index) return true;
6114 if (coff.mf.updates.items.len > 0) return true;
6115 if (coff.pending_members.count() > 0) return true;
6116 if (coff.exports_complete and coff.export_table.pending_sort) return true;
6117 return false;
6118}
6119
6120fn idleProgNode(
6121 coff: *Coff,
6122 tid: Zcu.PerThread.Id,
6123 prog_node: std.Progress.Node,
6124 node: Node,
6125) std.Progress.Node {
6126 var name: [std.Progress.Node.max_name_len]u8 = undefined;
6127 return prog_node.start(name: switch (node) {
6128 else => |tag| @tagName(tag),
6129 .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
6130 inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff),
6131 .input_section => |isi| {
6132 const ioi = isi.input(coff);
6133 break :name std.mem.print(&name, "{f}{f} {s}", .{
6134 ioi.path(coff).fmtEscapeString(),
6135 fmtMemberNameString(ioi.memberName(coff)),
6136 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf).unwrap().?).object_section.name(coff).toSlice(coff),
6137 }) catch &name;
6138 },
6139 .import_thunk => |gmi| gmi.name(coff).toSlice(coff),
6140 .nav => |nmi| {
6141 const ip = &coff.base.comp.zcu.?.intern_pool;
6142 break :name ip.getNav(nmi.navIndex(coff)).fqn.toSlice(ip);
6143 },
6144 .uav => |umi| std.mem.print(&name, "{f}", .{
6145 Value.fromInterned(umi.uavValue(coff)).fmtValue(.{
6146 .zcu = coff.base.comp.zcu.?,
6147 .tid = tid,
6148 }),
6149 }) catch &name,
6150 .archive_member => |mi| &mi.get(coff).headerPtr(coff).name,
6151 }, 0);
6152}
6153
6154fn genPending(coff: *Coff, pt: Zcu.PerThread) Error!void {
6155 const comp = pt.zcu.comp;
6156 while (coff.pending_uavs.pop()) |pending_uav| {
6157 const sub_prog_node = coff.idleProgNode(pt.tid, coff.const_prog_node, .{ .uav = pending_uav.key });
6158 defer sub_prog_node.end();
6159 coff.genUav(pt, pending_uav.key, pending_uav.value.alignment) catch |err| switch (err) {
6160 else => |e| return e,
6161 error.MappedFileIo => return comp.link_diags.fail(
6162 "linker failed to lower constant: {t}",
6163 .{coff.mf.io_err.?},
6164 ),
6165 };
6166 }
6167 var lazy_it = coff.lazy.iterator();
6168 while (lazy_it.next()) |lazy| while (lazy.value.pending_index < lazy.value.map.count()) {
6169 try coff.genLazy(pt, .{ .kind = lazy.key, .index = lazy.value.pending_index });
6170 lazy.value.pending_index += 1;
6171 };
6172}
6173
6174fn genUav(
6175 coff: *Coff,
6176 pt: Zcu.PerThread,
6177 umi: Node.UavMapIndex,
6178 uav_align: InternPool.Alignment,
6179) !void {
6180 const zcu = pt.zcu;
6181 const gpa = zcu.gpa;
6182
6183 const uav_val = umi.uavValue(coff);
6184 const si = umi.symbol(coff);
6185 const ni = ni: {
6186 switch (si.get(coff).ni) {
6187 .none => {
6188 const sec_si = (try coff.objectSectionMapIndex(
6189 .@".rdata",
6190 coff.mf.flags.block_size,
6191 .{ .read = true, .initialized = true },
6192 )).symbol(coff);
6193 try coff.nodes.ensureUnusedCapacity(gpa, 1);
6194 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
6195 const sym = si.get(coff);
6196 const ni = try sec_si.node(coff).addFloatingChild(gpa, &coff.mf, .{
6197 .alignment = .fromIp(uav_align),
6198 .moved = true,
6199 });
6200 coff.nodes.appendAssumeCapacity(.{ .uav = umi });
6201 sym.ni = .wrap(ni);
6202 sym.section_number = sec_si.get(coff).section_number;
6203 },
6204 else => {
6205 if (Alignment.compare(
6206 si.get(coff).ni.unwrap().?.alignment(&coff.mf),
6207 .gte,
6208 .fromIp(uav_align),
6209 )) {
6210 return;
6211 }
6212 si.deleteLocationRelocs(coff);
6213 },
6214 }
6215 const sym = si.get(coff);
6216 assert(sym.loc_relocs == .none);
6217 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6218 if (!isImage(coff) and sym.target_relocs != .none)
6219 try coff.pendingSymbolTableEntry(si);
6220
6221 break :ni sym.ni.unwrap().?;
6222 };
6223
6224 var nw: MappedFile.Node.Writer = undefined;
6225 ni.writer(gpa, &coff.mf, &nw);
6226 defer nw.deinit();
6227 codegen.generateSymbol(
6228 &coff.base,
6229 pt,
6230 .fromInterned(uav_val),
6231 &nw.interface,
6232 .{ .atom_index = @fromBackingInt(@intCast(@backingInt(si))) },
6233 ) catch |err| switch (err) {
6234 error.WriteFailed => return nw.err.?,
6235 else => |e| return e,
6236 };
6237
6238 si.get(coff).setSize(coff, @intCast(nw.interface.end));
6239 try si.applyLocationRelocs(coff);
6240}
6241
6242fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void {
6243 const si = gmi.symbol(coff);
6244 const sym = si.get(coff);
6245 const alias_sym = alias_si.get(coff);
6246 assert(sym.section_number == .UNDEFINED);
6247 assert(sym.loc_relocs == .none);
6248
6249 log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{
6250 gmi.name(coff).toSlice(coff),
6251 gmi.libName(coff).toSlice(coff),
6252 si,
6253 alias_si,
6254 if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null,
6255 });
6256
6257 var ri = sym.target_relocs;
6258 while (ri != .none) {
6259 const reloc = ri.get(coff);
6260 assert(reloc.target == si);
6261 reloc.target = alias_si;
6262 if (reloc.prev == .none) {
6263 reloc.prev = alias_sym.target_relocs;
6264 if (alias_sym.target_relocs != .none)
6265 alias_sym.target_relocs.get(coff).next = ri;
6266 break;
6267 }
6268 ri = reloc.prev;
6269 }
6270
6271 const prev_target_relocs = alias_sym.target_relocs;
6272 if (sym.target_relocs != .none)
6273 alias_sym.target_relocs = sym.target_relocs;
6274
6275 sym.target_relocs = .none;
6276 sym.gmi = alias_sym.gmi;
6277 coff.globals.values()[gmi.unwrap().?].si = alias_si;
6278 // Only apply the new relocs
6279 try alias_si.applyTargetRelocs(coff, prev_target_relocs);
6280}
6281
6282fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
6283 const comp = coff.base.comp;
6284 const gpa = comp.gpa;
6285 const name = gmi.name(coff);
6286 const si = gmi.symbol(coff);
6287
6288 log.debug(
6289 "flushGlobal({s}, {?s}) = n{d} {d}@{d}",
6290 .{
6291 name.toSlice(coff),
6292 gmi.libName(coff).toSlice(coff),
6293 si.get(coff).ni,
6294 si,
6295 si.get(coff).section_number,
6296 },
6297 );
6298
6299 if (!coff.isImage()) {
6300 try coff.pendingSymbolTableEntry(si);
6301 if (coff.isArchive() and si.get(coff).ni != .none)
6302 try coff.ensureMemberSymbol(
6303 coff.getNode(Node.known.zcu_member).archive_member,
6304 name,
6305 );
6306
6307 return true;
6308 }
6309
6310 if (si.get(coff).ni != .none)
6311 return true;
6312
6313 const Import = struct {
6314 lib_name: String,
6315 name: String.Optional,
6316 ordinal_hint: u16,
6317 kind: enum {
6318 iat_ptr,
6319 thunk,
6320 },
6321 };
6322
6323 const import: Import = import: {
6324 const sym = si.get(coff);
6325 const name_slice = name.toSlice(coff);
6326 const imp_match = std.mem.startsWith(u8, name_slice, imp_prefix);
6327
6328 // Globals may have the __imp_ prefix already if they are undef externals from another input.
6329 assert(sym.flags.dll_storage_class != .dllexport);
6330 const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport)
6331 .{ name, imp_match }
6332 else name: {
6333 try coff.ensureUnusedStringCapacity(imp_prefix.len + name_slice.len);
6334 const imp_name = try gpa.print(imp_prefix ++ "{s}", .{name_slice});
6335 defer gpa.free(imp_name);
6336 break :name .{ coff.getOrPutStringAssumeCapacity(imp_name), true };
6337 };
6338
6339 const opt_alt_search_name = coff.alternate_names.get(search_name);
6340 const search_libs = switch (sym.flags.value_tag) {
6341 .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) {
6342 .none => unreachable,
6343 .no_library => false,
6344 .library,
6345 .alias,
6346 => true,
6347 .anti_dependency => return comp.link_diags.fail(
6348 // TODO: Figure out what the purpose of this is
6349 "TODO support anti_dependency weak external: {s}",
6350 .{name.toSlice(coff)},
6351 ),
6352 },
6353 else => true,
6354 };
6355
6356 const opt_indices_lists: []const ?InputArchive.SearchList = if (search_libs) &.{
6357 coff.input_archive_symbol_indices.get(search_name),
6358 if (opt_alt_search_name) |alt| coff.input_archive_symbol_indices.get(alt) else null,
6359 } else &.{};
6360
6361 for (opt_indices_lists) |opt_indices_list| {
6362 const indices_list = opt_indices_list orelse continue;
6363 var iter: InputArchive.Member.Symbol.Index = indices_list.first;
6364 while (true) {
6365 const archive_sym = &coff.input_archive_symbols.items[@backingInt(iter)];
6366 const member = &coff.input_archive_members.items[@backingInt(archive_sym.iami)];
6367 member: switch (member.content) {
6368 .object => if (!member.flags.is_loaded) {
6369 if (gmi.libName(coff).unwrap()) |lib_name|
6370 if (!std.ascii.eqlIgnoreCase(
6371 lib_name.toSlice(coff),
6372 member.iai.path(coff).stem(),
6373 )) break :member;
6374
6375 // Try loading the input member and then retry.
6376 // This could still be a member containing imports
6377 // that use the older non-IMPORT_HEADER method.
6378 coff.pending_input = archive_sym.iami;
6379 return false;
6380 },
6381 .import => |import| {
6382 if (gmi.libName(coff).unwrap()) |lib_name|
6383 if (!std.ascii.eqlIgnoreCase(
6384 import.lib_name.toSlice(coff),
6385 lib_name.toSlice(coff),
6386 )) break :member;
6387
6388 const imp_name: String.Optional = name: switch (import.name_type) {
6389 .NAME,
6390 .NAME_NOPREFIX,
6391 .NAME_UNDECORATE,
6392 => |tag| {
6393 const symbol_name: []const u8 = import.symbol_name.toSlice(coff);
6394 const end_match = std.mem.endsWith(u8, name_slice, symbol_name);
6395 const len_delta = name_slice.len -% symbol_name.len;
6396 if (!end_match or
6397 (!imp_match and len_delta != 0) or
6398 (imp_match and len_delta != imp_prefix.len))
6399 return comp.link_diags.fail(
6400 "global '{s}' has mismatched symbol name in import header: '{s}'",
6401 .{
6402 name.toSlice(coff),
6403 import.symbol_name.toSlice(coff),
6404 },
6405 );
6406
6407 const imp_name = if (tag == .NAME) import.symbol_name else undecorated: {
6408 var imp_name = std.mem.trimStart(u8, symbol_name, "?@_");
6409 if (tag == .NAME_UNDECORATE)
6410 imp_name = std.mem.sliceTo(imp_name, '@');
6411
6412 try coff.ensureUnusedStringCapacity(imp_name.len);
6413 break :undecorated coff.getOrPutStringAssumeCapacity(imp_name);
6414 };
6415
6416 break :name imp_name.toOptional();
6417 },
6418 .ORDINAL => break :name .none,
6419 else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
6420 };
6421
6422 break :import .{
6423 .lib_name = import.lib_name,
6424 .name = imp_name,
6425 .ordinal_hint = import.import_ordinal_hint,
6426 .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr,
6427 };
6428 },
6429 }
6430
6431 if (archive_sym.next == iter) break;
6432 iter = archive_sym.next;
6433 }
6434 }
6435
6436 switch (sym.flags.value_tag) {
6437 .weak_alias_si => {
6438 try coff.aliasGlobal(gmi, sym.value.weak_alias_si);
6439 return true;
6440 },
6441 .weak_alias_name => {
6442 // Convert an unresolved weak external that itself refers to an undef external
6443 // into a (possibly new) global, so it can be resolved separately.
6444 const alias_gop = try coff.getOrPutGlobalSymbol(.{
6445 .name = sym.value.weak_alias_name.toSlice(coff),
6446 });
6447 try coff.aliasGlobal(gmi, alias_gop.value_ptr.si);
6448 return true;
6449 },
6450 else => {},
6451 }
6452
6453 // If there was an object that had the alternate name, we've attempted to load it
6454 if (opt_alt_search_name) |alt_search_name| {
6455 if (coff.globals.get(alt_search_name)) |alias_global| {
6456 try coff.aliasGlobal(gmi, alias_global.si);
6457 return true;
6458 }
6459 }
6460
6461 // Allow importing symbols with no implib entry, if a lib_name was specified.
6462 // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification,
6463 // which are not in the implib.
6464 if (sym.flags.type != .unknown) {
6465 if (gmi.libName(coff).unwrap()) |lib_name| break :import .{
6466 .lib_name = lib_name,
6467 .name = name.toOptional(),
6468 .ordinal_hint = 0,
6469 .kind = if (sym.flags.type == .code) .thunk else .iat_ptr,
6470 };
6471 }
6472
6473 return true;
6474 };
6475
6476 try coff.nodes.ensureUnusedCapacity(gpa, 4);
6477 try coff.symbols.ensureUnusedCapacity(gpa, 2);
6478
6479 const target_endian = coff.targetEndian();
6480 const addr_info = coff.targetAddrInfo();
6481 const lib_name = import.lib_name.toSlice(coff);
6482 const gop = try coff.import_table.entries.getOrPutAdapted(
6483 gpa,
6484 lib_name,
6485 ImportTable.Adapter{ .coff = coff },
6486 );
6487 const import_hint_name_align: Alignment = .@"2";
6488 if (!gop.found_existing) {
6489 errdefer _ = coff.import_table.entries.pop();
6490 try coff.import_table.ni.resizeLeaf(
6491 gpa,
6492 &coff.mf,
6493 @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
6494 );
6495 const import_hint_name_table_len =
6496 import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
6497 const idata_section_ni = coff.import_table.ni.parent(&coff.mf).unwrap().?;
6498 const import_lookup_table_ni = try idata_section_ni.addFloatingChild(gpa, &coff.mf, .{
6499 .size = addr_info.size * 2,
6500 .alignment = addr_info.alignment,
6501 .moved = true,
6502 });
6503 const import_address_table_ni = try idata_section_ni.addFloatingChild(gpa, &coff.mf, .{
6504 .size = addr_info.size * 2,
6505 .alignment = addr_info.alignment,
6506 .moved = true,
6507 });
6508 const import_address_table_si = coff.addSymbolAssumeCapacity();
6509 {
6510 const import_address_table_sym = import_address_table_si.get(coff);
6511 import_address_table_sym.ni = .wrap(import_address_table_ni);
6512 assert(import_address_table_sym.loc_relocs == .none);
6513 import_address_table_sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6514 import_address_table_sym.section_number =
6515 coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
6516 }
6517 const import_hint_name_table_ni = try idata_section_ni.addFloatingChild(gpa, &coff.mf, .{
6518 .size = import_hint_name_table_len,
6519 .alignment = import_hint_name_align,
6520 .moved = true,
6521 });
6522 gop.value_ptr.* = .{
6523 .import_lookup_table_ni = import_lookup_table_ni,
6524 .import_address_table_si = import_address_table_si,
6525 .import_hint_name_table_ni = import_hint_name_table_ni,
6526 .import_address_table_symbols = .empty,
6527 .len = 0,
6528 .hint_name_len = @intCast(import_hint_name_table_len),
6529 };
6530 const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf);
6531 @memcpy(import_hint_name_slice[0..lib_name.len], lib_name);
6532 @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll");
6533 @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0);
6534 coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @fromBackingInt(@intCast(gop.index)) });
6535 coff.nodes.appendAssumeCapacity(.{ .import_address_table = @fromBackingInt(@intCast(gop.index)) });
6536 coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @fromBackingInt(@intCast(gop.index)) });
6537
6538 const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2];
6539 import_directory_entries.* = .{ .{
6540 .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni),
6541 .time_date_stamp = 0,
6542 .forwarder_chain = 0,
6543 .name_rva = coff.computeNodeRva(import_hint_name_table_ni),
6544 .import_address_table_rva = coff.computeNodeRva(import_address_table_ni),
6545 }, .{
6546 .import_lookup_table_rva = 0,
6547 .time_date_stamp = 0,
6548 .forwarder_chain = 0,
6549 .name_rva = 0,
6550 .import_address_table_rva = 0,
6551 } };
6552 if (target_endian != native_endian)
6553 std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries);
6554 }
6555
6556 log.debug(
6557 "flushGlobalImport({s}, {?s}, {d}, {s})",
6558 .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name },
6559 );
6560
6561 const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{
6562 .iti = @fromBackingInt(@intCast(gop.index)),
6563 .name = import.name,
6564 .ordinal_hint = import.ordinal_hint,
6565 });
6566 if (!iat_symbol_gop.found_existing) {
6567 const import_symbol_index = gop.value_ptr.len;
6568 iat_symbol_gop.value_ptr.* = import_symbol_index;
6569
6570 gop.value_ptr.len = import_symbol_index + 1;
6571 const new_symbol_table_size = addr_info.size * (import_symbol_index + 2);
6572
6573 try gop.value_ptr.import_lookup_table_ni.resizeLeaf(gpa, &coff.mf, new_symbol_table_size);
6574 const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
6575 try import_address_table_ni.resizeLeaf(gpa, &coff.mf, new_symbol_table_size);
6576
6577 const opt_imp_name = import.name.toSlice(coff);
6578 const opt_import_hint_name_index = if (opt_imp_name) |imp_name| blk: {
6579 const import_hint_name_index = gop.value_ptr.hint_name_len;
6580 gop.value_ptr.hint_name_len = @intCast(
6581 import_hint_name_align.forward(import_hint_name_index + 2 + imp_name.len + 1),
6582 );
6583 try gop.value_ptr.import_hint_name_table_ni.resizeLeaf(gpa, &coff.mf, gop.value_ptr.hint_name_len);
6584 break :blk import_hint_name_index;
6585 } else null;
6586
6587 const import_hint_name_rva = if (opt_import_hint_name_index) |import_hint_name_index| blk: {
6588 const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
6589 const ordinal_hint: *u16 = @ptrCast(@alignCast(import_hint_name_slice[import_hint_name_index..][0..2]));
6590 ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian);
6591 @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_imp_name.?.len], opt_imp_name.?);
6592 @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_imp_name.?.len ..], 0);
6593 break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index;
6594 } else 0;
6595
6596 const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf);
6597 const import_address_slice = import_address_table_ni.slice(&coff.mf);
6598 switch (addr_info.magic) {
6599 _ => unreachable,
6600 inline .PE32, .@"PE32+" => |ct_magic| {
6601 const Entry = std.coff.ImportLookupTableEntry(ct_magic);
6602 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
6603 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
6604 var import_hint_name_rvas: [2]Entry = .{
6605 .{
6606 .payload = if (import.name == .none)
6607 .{ .ordinal = .{ .ordinal = import.ordinal_hint } }
6608 else
6609 .{ .hint_name_rva = @intCast(import_hint_name_rva) },
6610 .is_ordinal = import.name == .none,
6611 },
6612 @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)),
6613 };
6614 if (native_endian != target_endian)
6615 for (&import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v);
6616
6617 import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
6618 import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
6619 },
6620 }
6621 }
6622
6623 const sym = si.get(coff);
6624 assert(sym.loc_relocs == .none);
6625 const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*);
6626 switch (import.kind) {
6627 .iat_ptr => {
6628 const iat_sym = gop.value_ptr.import_address_table_si.get(coff);
6629 sym.section_number = iat_sym.section_number;
6630 sym.ni = iat_sym.ni;
6631 sym.setValue(.{ .node_offset = iat_offset });
6632 (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si;
6633 },
6634 .thunk => {
6635 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6636
6637 const target = &comp.root_mod.resolved_target.result;
6638 const alignment: Alignment = switch (comp.root_mod.optimize_mode) {
6639 .debug,
6640 .safe,
6641 .fast,
6642 => .fromIp(target_util.defaultFunctionAlignment(target)),
6643 .small => .fromIp(target_util.minFunctionAlignment(target)),
6644 };
6645 const parent_si = (try coff.pseudoSectionMapIndex(
6646 .@".thunks",
6647 alignment,
6648 .{ .execute = true, .read = true },
6649 )).symbol(coff);
6650
6651 const parent_sym = parent_si.get(coff);
6652 sym.section_number = parent_sym.section_number;
6653
6654 switch (coff.targetLoad(&coff.headerPtr().machine)) {
6655 else => |tag| @panic(@tagName(tag)),
6656 .AMD64 => {
6657 const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
6658 const ni = try parent_sym.ni.unwrap().?.addFloatingChild(gpa, &coff.mf, .{
6659 .alignment = alignment,
6660 .size = alignment.forward(init.len),
6661 });
6662 @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
6663 sym.ni = .wrap(ni);
6664 sym.extra.size = init.len;
6665 try coff.addReloc(
6666 si,
6667 init.len - 4,
6668 gop.value_ptr.import_address_table_si,
6669 .{ .known = iat_offset },
6670 .{ .AMD64 = .REL32 },
6671 );
6672 },
6673 }
6674 coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi });
6675 },
6676 }
6677
6678 try si.flushMoved(coff);
6679 return true;
6680}
6681
6682fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol {
6683 const comp = coff.base.comp;
6684
6685 if (!coff.isImage()) return .none;
6686 const gpa = comp.gpa;
6687 const machine = coff.targetLoad(&coff.headerPtr().machine);
6688 const target = &comp.root_mod.resolved_target.result;
6689
6690 return next: switch (pending) {
6691 .entry => {
6692 // TODO: Use explicitly specified entry if set, add err if not found
6693 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
6694 if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
6695 .WINDOWS_CUI => &.{
6696 .{ "main", "mainCRTStartup" },
6697 .{ "wmain", "wmainCRTStartup" },
6698 },
6699 .WINDOWS_GUI => &.{
6700 .{ "WinMain", "WinMainCRTStartup" },
6701 .{ "wWinMain", "wWinMainCRTStartup" },
6702 },
6703 else => unreachable,
6704 } else &.{
6705 .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
6706 }
6707 else
6708 &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }};
6709
6710 const entry_si = for (entries) |entry| {
6711 if (entry[0]) |required_name|
6712 if (coff.getDefinedGlobal(required_name) == .null) continue;
6713
6714 break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
6715 } else .null;
6716
6717 if (entry_si != .null) {
6718 log.debug(
6719 "entry({s}, {d})",
6720 .{ entry_si.get(coff).gmi.name(coff).toSlice(coff), entry_si },
6721 );
6722
6723 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6724 const optional_hdr_si = coff.addSymbolAssumeCapacity();
6725 const optional_hdr_sym = optional_hdr_si.get(coff);
6726 optional_hdr_sym.ni = .wrap(Node.known.optional_header);
6727 assert(optional_hdr_sym.loc_relocs == .none);
6728 optional_hdr_sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6729
6730 const optional_hdr = coff.optionalHeaderStandardPtr();
6731 optional_hdr.address_of_entry_point = std.mem.nativeTo(
6732 u32,
6733 entry_si.get(coff).rva,
6734 coff.targetEndian(),
6735 );
6736
6737 try coff.addReloc(
6738 optional_hdr_si,
6739 @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
6740 entry_si,
6741 .{ .known = 0 },
6742 switch (machine) {
6743 else => |tag| @panic(@tagName(tag)),
6744 .AMD64 => .{ .AMD64 = .ADDR32NB },
6745 .I386 => .{ .I386 = .DIR32NB },
6746 },
6747 );
6748 }
6749
6750 // Referencing the startup functions may trigger loading the object containing them,
6751 // we need to wait until that is done before looking for further symbols.
6752 break :next .tls;
6753 },
6754 .tls => {
6755 if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
6756 log.debug("tlsDir({d})", .{tls_used_si});
6757
6758 const tls_directory = coff.dataDirectoryPtr(.TLS);
6759 tls_directory.* = .{
6760 .virtual_address = tls_used_si.get(coff).rva,
6761 .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
6762 _ => unreachable,
6763 .PE32 => 24,
6764 .@"PE32+" => 40,
6765 },
6766 };
6767 if (coff.targetEndian() != native_endian)
6768 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6769
6770 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6771 const data_dir_si = coff.addSymbolAssumeCapacity();
6772 const data_dir_sym = data_dir_si.get(coff);
6773 data_dir_sym.ni = .wrap(Node.known.data_directories);
6774 assert(data_dir_sym.loc_relocs == .none);
6775 data_dir_sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6776
6777 try coff.addReloc(
6778 data_dir_si,
6779 @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
6780 tls_used_si,
6781 .{ .known = 0 },
6782 switch (machine) {
6783 else => |tag| @panic(@tagName(tag)),
6784 .AMD64 => .{ .AMD64 = .ADDR32NB },
6785 .I386 => .{ .I386 = .DIR32NB },
6786 },
6787 );
6788 }
6789
6790 break :next .none;
6791 },
6792 .none => unreachable,
6793 };
6794}
6795
6796fn genLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
6797 const lazy = lmr.lazySymbol(coff);
6798 if (lazy.ty == .anyerror_type) return;
6799 const kind = switch (lmr.kind) {
6800 .code => "code",
6801 .const_data => "data",
6802 };
6803 var name: [std.Progress.Node.max_name_len]u8 = undefined;
6804 const sub_prog_node = coff.synth_prog_node.start(
6805 std.mem.print(&name, "lazy {s} for {f}", .{
6806 kind,
6807 Type.fromInterned(lazy.ty).fmt(pt),
6808 }) catch &name,
6809 0,
6810 );
6811 defer sub_prog_node.end();
6812 coff.genLazyInner(pt, lmr) catch |err| switch (err) {
6813 else => |e| return e,
6814 error.MappedFileIo => return coff.base.comp.link_diags.fail(
6815 "linker failed to lower lazy {s}: {t}",
6816 .{ kind, coff.mf.io_err.? },
6817 ),
6818 };
6819}
6820fn genLazyInner(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
6821 const zcu = pt.zcu;
6822 const gpa = zcu.gpa;
6823
6824 const lazy = lmr.lazySymbol(coff);
6825 const si = lmr.symbol(coff);
6826 const ni = ni: {
6827 const sym = si.get(coff);
6828 switch (sym.ni) {
6829 .none => {
6830 try coff.nodes.ensureUnusedCapacity(gpa, 1);
6831 const sec_si: Symbol.Index = switch (lazy.kind) {
6832 .code => .text,
6833 .const_data => .rdata,
6834 };
6835 const ni = try sec_si.node(coff).addFloatingChild(gpa, &coff.mf, .{ .moved = true });
6836 coff.nodes.appendAssumeCapacity(switch (lazy.kind) {
6837 .code => .{ .lazy_code = @fromBackingInt(@intCast(lmr.index)) },
6838 .const_data => .{ .lazy_const_data = @fromBackingInt(@intCast(lmr.index)) },
6839 });
6840 sym.ni = .wrap(ni);
6841 sym.section_number = sec_si.get(coff).section_number;
6842 },
6843 else => si.deleteLocationRelocs(coff),
6844 }
6845 assert(sym.loc_relocs == .none);
6846 sym.loc_relocs = @fromBackingInt(@intCast(coff.relocs.items.len));
6847 if (!isImage(coff) and sym.target_relocs != .none)
6848 try coff.pendingSymbolTableEntry(si);
6849
6850 break :ni sym.ni.unwrap().?;
6851 };
6852
6853 var required_alignment: InternPool.Alignment = .none;
6854 var nw: MappedFile.Node.Writer = undefined;
6855 ni.writer(gpa, &coff.mf, &nw);
6856 defer nw.deinit();
6857 codegen.generateLazySymbol(
6858 &coff.base,
6859 pt,
6860 lazy,
6861 &required_alignment,
6862 &nw.interface,
6863 .none,
6864 .{ .atom_index = @fromBackingInt(@intCast(@backingInt(si))) },
6865 ) catch |err| switch (err) {
6866 error.WriteFailed => return nw.err.?,
6867 else => |e| return e,
6868 };
6869
6870 si.get(coff).setSize(coff, @intCast(nw.interface.end));
6871 try si.applyLocationRelocs(coff);
6872}
6873
6874fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
6875 log.debug("flushMoved({s}, n{d})", .{ @tagName(coff.getNode(ni)), ni });
6876 switch (coff.getNode(ni)) {
6877 .file,
6878 .header,
6879 .signature,
6880 => unreachable,
6881 .coff_header,
6882 .optional_header,
6883 .data_directories,
6884 .section_table,
6885 .placeholder,
6886 => assert(!coff.isImage()),
6887 .symbol_table,
6888 .string_table,
6889 => |_, tag| {
6890 if (tag == .symbol_table)
6891 coff.targetStore(
6892 &coff.headerPtr().pointer_to_symbol_table,
6893 @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
6894 );
6895
6896 if (!coff.symbol_table.pending_shrink) {
6897 const symbol_table_loc, const symbol_table_size = coff.symbol_table.ni.location(&coff.mf).resolve(&coff.mf);
6898 const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
6899 coff.symbol_table.pending_shrink = string_table_offset - (symbol_table_loc + symbol_table_size) > 0;
6900 }
6901 },
6902 .relocation_table => |sn| {
6903 coff.targetStore(
6904 &sn.header(coff).pointer_to_relocations,
6905 @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
6906 );
6907 },
6908 .relocation_table_entry => {},
6909 .archive_member_header => |mi| {
6910 const member = mi.get(coff);
6911 switch (member.kind) {
6912 .first_linker, .second_linker, .longnames => {},
6913 else => coff.targetStore(
6914 &coff.secondLinkerMemberOffsetsSlice()[@backingInt(mi) - Member.Index.known_count],
6915 @intCast(ni.fileLocation(&coff.mf, false).offset),
6916 ),
6917 }
6918
6919 if (member.kind == .coff)
6920 try coff.pending_members.put(coff.base.comp.gpa, mi, {});
6921 },
6922 .archive_member,
6923 => {},
6924 .image_section => |si| {
6925 const sym = si.get(coff);
6926 const flags = coff.targetLoad(&sym.section_number.header(coff).flags);
6927 if (!flags.CNT_UNINITIALIZED_DATA) {
6928 const file_offset = if (isArchive(coff))
6929 sym.ni.unwrap().?.location(&coff.mf).resolve(&coff.mf)[0]
6930 else
6931 ni.fileLocation(&coff.mf, false).offset;
6932
6933 return coff.targetStore(
6934 &sym.section_number.header(coff).pointer_to_raw_data,
6935 @intCast(file_offset),
6936 );
6937 }
6938 },
6939 .input_section => |isi| {
6940 try isi.symbol(coff).flushMoved(coff);
6941 for (coff.input_symbols.items[@backingInt(isi.firstSymbol(coff))..]) |input_symbol| {
6942 if (input_symbol.si.get(coff).ni != ni.toOptional()) break;
6943 try input_symbol.si.flushMoved(coff);
6944 }
6945 },
6946 .import_directory_table => {
6947 _, const size = ni.location(&coff.mf).resolve(&coff.mf);
6948 if (size > 0)
6949 coff.targetStore(
6950 &coff.dataDirectoryPtr(.IMPORT).virtual_address,
6951 coff.computeNodeRva(ni),
6952 );
6953 },
6954 .import_lookup_table => |import_index| coff.targetStore(
6955 &coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva,
6956 coff.computeNodeRva(ni),
6957 ),
6958 .import_address_table => |import_index| {
6959 const entry = import_index.get(coff);
6960 const import_address_table_si = entry.import_address_table_si;
6961 try import_address_table_si.flushMoved(coff);
6962 coff.targetStore(
6963 &coff.importDirectoryEntryPtr(import_index).import_address_table_rva,
6964 import_address_table_si.get(coff).rva,
6965 );
6966
6967 for (entry.import_address_table_symbols.items) |iat_ptr_si|
6968 try iat_ptr_si.flushMoved(coff);
6969 },
6970 .import_hint_name_table => |import_index| {
6971 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
6972 const import_hint_name_rva = coff.computeNodeRva(ni);
6973 coff.targetStore(
6974 &coff.importDirectoryEntryPtr(import_index).name_rva,
6975 import_hint_name_rva,
6976 );
6977 const import_entry = import_index.get(coff);
6978 const import_lookup_slice = import_entry.import_lookup_table_ni.slice(&coff.mf);
6979 const import_address_slice =
6980 import_entry.import_address_table_si.node(coff).slice(&coff.mf);
6981 const import_hint_name_slice = ni.slice(&coff.mf);
6982 const import_hint_name_align = ni.alignment(&coff.mf);
6983
6984 var import_hint_name_index: u32 = 0;
6985 for (0..import_entry.len) |import_symbol_index| {
6986 switch (magic) {
6987 _ => unreachable,
6988 inline .PE32, .@"PE32+" => |ct_magic| {
6989 const Entry = std.coff.ImportLookupTableEntry(ct_magic);
6990 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
6991 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
6992
6993 var entry = coff.targetLoad(&import_lookup_table[import_symbol_index]);
6994 if (entry.is_ordinal)
6995 continue;
6996
6997 import_hint_name_index = @intCast(import_hint_name_align.forward(
6998 std.mem.findScalarPos(
6999 u8,
7000 import_hint_name_slice,
7001 import_hint_name_index,
7002 0,
7003 ).? + 1,
7004 ));
7005
7006 entry.payload.hint_name_rva = @intCast(import_hint_name_rva + import_hint_name_index);
7007 import_hint_name_index += 2;
7008
7009 coff.targetStore(&import_lookup_table[import_symbol_index], entry);
7010 coff.targetStore(&import_address_table[import_symbol_index], entry);
7011 },
7012 }
7013 }
7014 },
7015 .export_directory_table => {
7016 const rva = coff.computeNodeRva(ni);
7017 coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva);
7018 coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable));
7019 },
7020 .export_address_table => {
7021 try coff.export_table.export_address_table_si.flushMoved(coff);
7022
7023 // These relocs are applied directly here instead of via the above flushMoved call as
7024 // they are non-contiguous, and not tracked under export_address_table_si.
7025 for (coff.export_table.entries.values()) |entry|
7026 try entry.export_address_table_ri.get(coff).apply(coff);
7027
7028 coff.targetStore(
7029 &coff.exportDirectoryTable().export_address_table_rva,
7030 coff.computeNodeRva(ni),
7031 );
7032 },
7033 .export_name_pointer_table => coff.targetStore(
7034 &coff.exportDirectoryTable().name_pointer_table_rva,
7035 coff.computeNodeRva(ni),
7036 ),
7037 .export_ordinal_table => coff.targetStore(
7038 &coff.exportDirectoryTable().ordinal_table_rva,
7039 coff.computeNodeRva(ni),
7040 ),
7041 .export_name_table => {
7042 const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni);
7043 for (
7044 coff.exportNamePointerTableSlice(),
7045 coff.exportOrdinalTableSlice(),
7046 ) |*np, target_ord| {
7047 const ord: ExportTable.Ordinal = @fromBackingInt(@intCast(coff.targetLoad(&target_ord.unbiased_ordinal)));
7048 const entry = ord.get(coff);
7049 coff.targetStore(
7050 &np.name_rva,
7051 @intCast(name_table_rva + entry.name_index),
7052 );
7053 }
7054 },
7055 inline .pseudo_section,
7056 .object_section,
7057 .import_thunk,
7058 .nav,
7059 .uav,
7060 .lazy_code,
7061 .lazy_const_data,
7062 => |mi| try mi.symbol(coff).flushMoved(coff),
7063 .builtin => |si| try si.flushMoved(coff),
7064 }
7065 try ni.childrenMoved(coff.base.comp.gpa, &coff.mf);
7066}
7067
7068fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
7069 const offset, const size = ni.location(&coff.mf).resolve(&coff.mf);
7070 log.debug("flushResized({s}, n{d}, 0x{x})", .{ @tagName(coff.getNode(ni)), ni, size });
7071
7072 switch (coff.getNode(ni)) {
7073 .file => {
7074 if (coff.isArchive() and coff.members.items.len > 0) {
7075 const last_member = coff.members.items[coff.members.items.len - 1];
7076 // See .archive_member branch for reasoning
7077 assert(Node.known.file.last(&coff.mf).unwrap().? == last_member.content_ni);
7078 try coff.flushResized(last_member.content_ni);
7079 }
7080 },
7081 .header => {
7082 if (coff.isImage()) {
7083 switch (coff.optionalHeaderPtr()) {
7084 inline else => |optional_header| coff.targetStore(
7085 &optional_header.size_of_headers,
7086 @intCast(size),
7087 ),
7088 }
7089
7090 if (size > coff.section_table.values()[0].si.get(coff).rva) try coff.virtualSlide(
7091 0,
7092 std.mem.alignForward(
7093 u32,
7094 @intCast(size * 4),
7095 coff.optionalHeaderField(.section_alignment),
7096 ),
7097 );
7098 }
7099 },
7100 .signature,
7101 .archive_member_header,
7102 => unreachable,
7103 .archive_member => |mi| {
7104 const content_ni = mi.get(coff).content_ni;
7105 const content_offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf);
7106 const next_offset = if (content_ni.next(&coff.mf).unwrap()) |next_ni| offset: {
7107 assert(coff.getNode(next_ni) == .archive_member_header);
7108 break :offset next_ni.location(&coff.mf).resolve(&coff.mf)[0];
7109 } else offset: {
7110 assert(content_ni.parent(&coff.mf) == Node.known.file.toOptional());
7111 // This must take into account the final file size. If there are trailing
7112 // bytes, they will be expected to contain another valid member header
7113 break :offset coff.mf.memory_map.memory.len;
7114 };
7115
7116 // Not inserting IMAGE_ARCHIVE_PAD `\n` byte here, because we are expanding to full size
7117 Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - content_offset);
7118 },
7119 .coff_header,
7120 .optional_header,
7121 .data_directories,
7122 => unreachable,
7123 .section_table => {},
7124 .symbol_table => {
7125 assert(!coff.isImage());
7126 if (!coff.symbol_table.pending_shrink) {
7127 const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
7128 coff.symbol_table.pending_shrink =
7129 size > coff.targetLoad(&coff.headerPtr().number_of_symbols) * std.coff.Symbol.sizeOf() or
7130 string_table_offset - (offset + size) > 0;
7131 }
7132 },
7133 .string_table => {
7134 assert(!coff.isImage());
7135 coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size));
7136 },
7137 .relocation_table,
7138 .relocation_table_entry,
7139 => assert(!coff.isImage()),
7140 .image_section => |si| {
7141 const sym = si.get(coff);
7142 const section_index = sym.section_number.toIndex();
7143 const section = &coff.sectionTableSlice()[section_index];
7144 coff.targetStore(&section.size_of_raw_data, @intCast(size));
7145 if (coff.isImage() and size > coff.targetLoad(&section.virtual_size)) {
7146 const virtual_size = std.mem.alignForward(
7147 u32,
7148 @intCast(size * 4),
7149 coff.optionalHeaderField(.section_alignment),
7150 );
7151 coff.targetStore(&section.virtual_size, virtual_size);
7152 try coff.virtualSlide(section_index + 1, sym.rva + virtual_size);
7153 }
7154
7155 if (!coff.isImage()) {
7156 if (coff.symbolTableSectionAuxEntryPtr(si.sti(coff))) |aux_ptr|
7157 coff.targetStore(&aux_ptr.length, @intCast(size));
7158 }
7159 },
7160 .input_section => {},
7161 .import_directory_table => {
7162 const prev_size = coff.targetLoad(&coff.dataDirectoryPtr(.IMPORT).size);
7163 coff.targetStore(
7164 &coff.dataDirectoryPtr(.IMPORT).size,
7165 @intCast(size),
7166 );
7167 if (prev_size == 0) try coff.flushMoved(ni);
7168 },
7169 .import_lookup_table,
7170 .import_address_table,
7171 .import_hint_name_table,
7172 => {},
7173 .export_directory_table => unreachable,
7174 .export_address_table,
7175 .export_name_pointer_table,
7176 .export_ordinal_table,
7177 .export_name_table,
7178 => {},
7179 inline .pseudo_section,
7180 .object_section,
7181 => |smi, tag| {
7182 if (tag == .pseudo_section and smi.name(coff) == .@".edata") {
7183 coff.targetStore(
7184 &coff.dataDirectoryPtr(.EXPORT).size,
7185 @intCast(size),
7186 );
7187 }
7188
7189 var sym = smi.symbol(coff).get(coff);
7190 while (sym.flags.extra_tag == .next_alias_si)
7191 sym = sym.extra.next_alias_si.get(coff);
7192
7193 sym.extra.size = @intCast(size);
7194 },
7195 .import_thunk,
7196 .nav,
7197 .uav,
7198 .lazy_code,
7199 .lazy_const_data,
7200 .builtin,
7201 => {},
7202 .placeholder,
7203 => unreachable,
7204 }
7205}
7206
7207fn flushMember(coff: *Coff, mi: Member.Index) !void {
7208 const member = mi.get(coff);
7209 switch (member.kind) {
7210 .first_linker,
7211 .longnames,
7212 .import,
7213 => unreachable,
7214 .second_linker => {
7215 const Context = struct {
7216 coff: *Coff,
7217 indices: []u16,
7218 strings: []String,
7219
7220 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
7221 return std.mem.lessThan(
7222 u8,
7223 ctx.strings[lhs].toSlice(ctx.coff),
7224 ctx.strings[rhs].toSlice(ctx.coff),
7225 );
7226 }
7227
7228 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
7229 std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
7230 std.mem.swap(String, &ctx.strings[lhs], &ctx.strings[rhs]);
7231 }
7232 };
7233
7234 // TODO: Does this sort need to also sort by linker input order (if names equal)?
7235 std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{
7236 .coff = coff,
7237 .indices = coff.secondLinkerMemberIndicesSlice(),
7238 .strings = coff.lib_string_table.items,
7239 });
7240
7241 var offset: usize = 0;
7242 var string_table = coff.secondLinkerMemberStringsSlice();
7243 for (coff.lib_string_table.items) |string| {
7244 const str = string.toSlice(coff);
7245 @memcpy(string_table[offset..][0..str.len], str);
7246 string_table[offset + str.len] = 0;
7247 offset += str.len + 1;
7248 }
7249 },
7250 .coff => {
7251 const file_offset: u32 = @intCast(member.header_ni.fileLocation(&coff.mf, false).offset);
7252 const first_linker_offsets = coff.firstLinkerMemberOffsetsSlice();
7253 for (member.first_linker_indices.values()) |mfli|
7254 first_linker_offsets[@backingInt(mfli)] = std.mem.nativeTo(u32, file_offset, .big);
7255 },
7256 }
7257}
7258
7259fn flushExportsSort(coff: *Coff) void {
7260 const Context = struct {
7261 coff: *Coff,
7262 np: []std.coff.ExportNamePointerTableEntry,
7263 ord: []std.coff.ExportOrdinalTableEntry,
7264 entries: []ExportTable.Entry,
7265 nt: []const u8,
7266
7267 pub fn lessThan(ctx: *const @This(), lhs: usize, rhs: usize) bool {
7268 const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)];
7269 const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)];
7270 return std.mem.lessThan(
7271 u8,
7272 ctx.nt[lhs_entry.name_index..][0..lhs_entry.name_len],
7273 ctx.nt[rhs_entry.name_index..][0..rhs_entry.name_len],
7274 );
7275 }
7276
7277 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
7278 std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]);
7279 std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]);
7280 }
7281 };
7282
7283 std.sort.pdqContext(0, coff.export_table.entries.count(), &Context{
7284 .coff = coff,
7285 .np = coff.exportNamePointerTableSlice(),
7286 .ord = coff.exportOrdinalTableSlice(),
7287 .entries = coff.export_table.entries.values(),
7288 .nt = coff.export_table.name_table_ni.slice(&coff.mf),
7289 });
7290}
7291
7292fn flushSectionMerges(coff: *Coff) !void {
7293 while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1)
7294 try coff.flushSectionMerge(coff.section_merge_pending_index);
7295}
7296
7297fn flushSectionMerge(coff: *Coff, index: u32) !void {
7298 assert(coff.isImage());
7299 const from = coff.section_merges.keys()[index];
7300 const to = coff.section_merges.values()[index];
7301 assert(from != to);
7302
7303 log.debug("flushSectionMerge({s}->{s})", .{ from.toSlice(coff), to.toSlice(coff) });
7304
7305 const opt_to_sec = coff.section_table.getPtr(to);
7306 if (coff.section_table.getPtr(from)) |from_sec| {
7307 const from_sym = from_sec.si.get(coff);
7308 if (opt_to_sec) |to_sec| {
7309 const to_sym = to_sec.si.get(coff);
7310
7311 // TODO: Create a pseudo-section named `from` in `to`, copy `from_sec` ni into that pseudo section
7312 // TODO: Update .section_number for all contained syms
7313 // TODO: Remove `from_sec` from section table (set size = 0 and can do it in flushResized?).
7314 // This is non-trivial as we can't leave holes in the section table.
7315 // TODO: Merge section flags
7316 _ = to_sym;
7317 return coff.base.comp.link_diags.fail("TODO implement section to section merge", .{});
7318 } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
7319 const to_sym = to_ps_si.get(coff);
7320 if (from_sym.section_number == to_sym.section_number)
7321 return;
7322
7323 // TODO: Same as above, except place `from` into a node in `to_psmi`'s parent
7324 return coff.base.comp.link_diags.fail("TODO implement section to pseudosection merge", .{});
7325 }
7326
7327 // If `to` doesn't exist, /MERGE is defined as renaming `from` to `to`.
7328 // No other path will create image-level sections, so we can safely rename this now
7329 const from_name = &from_sec.si.get(coff).section_number.header(coff).name;
7330 const to_slice = to.toSlice(coff);
7331 @memcpy(from_name[0..to_slice.len], to_slice);
7332 @memset(from_name[to_slice.len..], 0);
7333 } else if (coff.pseudo_section_table.getIndex(from)) |from_index| {
7334 const from_psmi: Node.PseudoSectionMapIndex = @fromBackingInt(@intCast(from_index));
7335 const from_sym = from_psmi.symbol(coff).get(coff);
7336 if (opt_to_sec) |to_sec| {
7337 const to_sym = to_sec.si.get(coff);
7338 if (from_sym.section_number == to_sym.section_number)
7339 return;
7340
7341 // TODO: Move from_psmi's node into to_sec
7342 // TODO: Update .section_number for all contained syms
7343 // TODO: Merge section flags
7344 return coff.base.comp.link_diags.fail("TODO implement pseudosection to section merge", .{});
7345 } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
7346 const to_sym = to_ps_si.get(coff);
7347 if (from_sym.section_number == to_sym.section_number)
7348 return;
7349
7350 // TODO: Same as above, but move from_psmi's node after to_psmi's node in its parent
7351 return coff.base.comp.link_diags.fail("TODO implement pseudosection to pseudosection merge", .{});
7352 }
7353
7354 // Renaming pseudo-sections have no effect on the output, so this is a no-op.
7355 }
7356}
7357
7358fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void {
7359 var rva = start_rva;
7360 for (
7361 coff.section_table.values()[start_section_index..],
7362 coff.sectionTableSlice()[start_section_index..],
7363 ) |*section, *header| {
7364 const section_sym = section.si.get(coff);
7365 section_sym.rva = rva;
7366 coff.targetStore(&header.virtual_address, rva);
7367 try section_sym.ni.unwrap().?.childrenMoved(coff.base.comp.gpa, &coff.mf);
7368 rva += coff.targetLoad(&header.virtual_size);
7369 }
7370 switch (coff.optionalHeaderPtr()) {
7371 inline else => |optional_header| coff.targetStore(
7372 &optional_header.size_of_image,
7373 @intCast(rva),
7374 ),
7375 }
7376}
7377
7378pub fn updateExports(
7379 coff: *Coff,
7380 pt: Zcu.PerThread,
7381 export_indices: []const Zcu.Export.Index,
7382) link.Error!void {
7383 // TODO: delete old exports from first/second linker member table
7384 // TODO: delete old exports from symbol table inside section
7385 const diags = &coff.base.comp.link_diags;
7386 var alias_syms: std.array_hash_map.Auto(Symbol.Index, Symbol.Index) = .empty;
7387 defer alias_syms.deinit(coff.base.comp.gpa);
7388 for (export_indices) |export_index| {
7389 coff.updateExportInner(pt, export_index, &alias_syms) catch |err| switch (err) {
7390 error.MappedFileIo => return diags.fail(
7391 "failed to write output file: {t}",
7392 .{coff.mf.io_err.?},
7393 ),
7394 else => |e| return e,
7395 };
7396 }
7397 coff.exports_complete = true;
7398}
7399fn updateExportInner(
7400 coff: *Coff,
7401 pt: Zcu.PerThread,
7402 export_index: Zcu.Export.Index,
7403 alias_syms: *std.array_hash_map.Auto(Symbol.Index, Symbol.Index),
7404) Error!void {
7405 const zcu = pt.zcu;
7406 const gpa = zcu.gpa;
7407 const ip = &zcu.intern_pool;
7408
7409 const exp = export_index.ptr(zcu);
7410
7411 try coff.symbols.ensureUnusedCapacity(gpa, 1);
7412 const exported_si: Symbol.Index = switch (exp.exported) {
7413 .nav => |nav| try coff.navSymbol(zcu, nav),
7414 .uav => |uav| @fromBackingInt(@intCast(@backingInt(try coff.lowerUav(
7415 pt,
7416 uav,
7417 Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu),
7418 )))),
7419 };
7420 switch (exp.exported) {
7421 .nav => |nav| log.debug("updateExports({f}) = {d}", .{ ip.getNav(nav).fqn.fmt(ip), exported_si }),
7422 .uav => |uav| log.debug("updateExports(@as({f}, {f})) = {d}", .{
7423 Type.fromInterned(ip.typeOf(uav)).fmt(pt),
7424 Value.fromInterned(uav).fmtValue(pt),
7425 exported_si,
7426 }),
7427 }
7428
7429 try coff.genPending(pt);
7430 while (try coff.resolve(pt.tid)) {}
7431 while (try coff.idle(pt.tid)) {}
7432
7433 const machine = coff.targetLoad(&coff.headerPtr().machine);
7434 const exported_ni = exported_si.node(coff);
7435 const exported_sym = exported_si.get(coff);
7436
7437 const @"export" = export_index.ptr(zcu);
7438 const name = @"export".opts.name.toSlice(ip);
7439
7440 // TODO: add an errMsg if this conflicts with an existing symbol
7441 const export_si = try coff.globalSymbol(.{ .name = name });
7442 const export_sym = export_si.get(coff);
7443 export_sym.ni = .wrap(exported_ni);
7444 export_sym.rva = exported_sym.rva;
7445 export_sym.section_number = exported_sym.section_number;
7446 if (@"export".opts.linkage == .weak and !coff.isImage()) {
7447 // exported_si needs to be ahead of export_si in the symbol table,
7448 // so that its sti is known when creating the weak external aux entry
7449 try coff.pendingSymbolTableEntry(exported_si);
7450 export_sym.flags.weak_external_strat = .alias;
7451 export_sym.setValue(.{ .weak_alias_si = exported_si });
7452 }
7453 defer export_si.applyTargetRelocs(coff, .none) catch unreachable;
7454
7455 const prev_alias_si: Symbol.Index = si: {
7456 const gop = try alias_syms.getOrPut(gpa, exported_si);
7457 const prev_alias_si = if (gop.found_existing) gop.value_ptr.* else exported_si;
7458 gop.value_ptr.* = export_si;
7459 break :si prev_alias_si;
7460 };
7461
7462 // The last symbol in the alias list holds the size
7463 const prev_alias_sym = prev_alias_si.get(coff);
7464 switch (prev_alias_sym.flags.extra_tag) {
7465 .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }),
7466 // This export should have been deleted
7467 .next_alias_si => assert(prev_alias_sym.extra.next_alias_si == export_si),
7468 else => unreachable,
7469 }
7470
7471 prev_alias_sym.setExtra(.{ .next_alias_si = export_si });
7472
7473 if (!coff.isImage()) return;
7474
7475 const entries_ctx = ExportTable.Adapter{ .coff = coff };
7476 const gop = try coff.export_table.entries.getOrPutAdapted(
7477 gpa,
7478 name,
7479 entries_ctx,
7480 );
7481
7482 if (!gop.found_existing) {
7483 errdefer _ = coff.export_table.entries.pop();
7484
7485 const export_count = coff.export_table.entries.count();
7486 if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries")))
7487 return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{});
7488
7489 const name_index: u32 = @intCast(coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]);
7490 const new_name_table_size = name_index + name.len + 1;
7491 if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index")))
7492 return coff.base.comp.link_diags.fail("exports name table limit reached", .{});
7493
7494 try coff.export_table.name_table_ni.resizeLeaf(gpa, &coff.mf, new_name_table_size);
7495
7496 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
7497 @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
7498
7499 // If the new name sorts after the current tail of the sorted list, we don't need to re-sort
7500 {
7501 const ordinal_table_slice = coff.exportOrdinalTableSlice();
7502 if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) {
7503 const tail_index: ExportTable.Ordinal =
7504 @fromBackingInt(@intCast(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal));
7505 const tail_entry = tail_index.get(coff);
7506 const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len];
7507 coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name);
7508 }
7509 }
7510
7511 const edt = coff.exportDirectoryTable();
7512 coff.targetStore(&edt.number_of_names, @intCast(export_count));
7513 edt.number_of_entries = edt.number_of_names;
7514
7515 // TODO: These should all be resized ahead of time to fit all exports
7516 // after https://github.com/ziglang/zig/issues/23616
7517 try coff.export_table.export_address_table_si.node(coff).resizeLeaf(
7518 gpa,
7519 &coff.mf,
7520 export_count * @sizeOf(std.coff.ExportAddressTableEntry),
7521 );
7522
7523 try coff.export_table.name_pointer_table_ni.resizeLeaf(
7524 gpa,
7525 &coff.mf,
7526 export_count * @sizeOf(std.coff.ExportNamePointerTableEntry),
7527 );
7528
7529 try coff.export_table.ordinal_table_ni.resizeLeaf(
7530 gpa,
7531 &coff.mf,
7532 export_count * @sizeOf(std.coff.ExportOrdinalTableEntry),
7533 );
7534
7535 coff.targetStore(
7536 &coff.exportNamePointerTableSlice()[gop.index].name_rva,
7537 @intCast(coff.computeNodeRva(coff.export_table.name_table_ni) + name_index),
7538 );
7539 coff.targetStore(
7540 &coff.exportOrdinalTableSlice()[gop.index].unbiased_ordinal,
7541 @intCast(gop.index),
7542 );
7543
7544 gop.value_ptr.* = .{
7545 .si = export_si,
7546 .name_index = @intCast(name_index),
7547 .name_len = @intCast(name.len),
7548 .export_address_table_ri = @fromBackingInt(@intCast(coff.relocs.items.len)),
7549 };
7550
7551 try coff.addReloc(
7552 coff.export_table.export_address_table_si,
7553 @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index),
7554 export_si,
7555 .{ .known = 0 },
7556 switch (machine) {
7557 else => |tag| @panic(@tagName(tag)),
7558 .AMD64 => .{ .AMD64 = .ADDR32NB },
7559 .I386 => .{ .I386 = .DIR32NB },
7560 },
7561 );
7562 } else {
7563 gop.value_ptr.si = export_si;
7564 const reloc = gop.value_ptr.*.export_address_table_ri.get(coff);
7565 reloc.target = export_si;
7566 }
7567}
7568
7569fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) Io.File.Writer.Error!void {
7570 const comp = coff.base.comp;
7571 const io = comp.io;
7572 var buffer: [512]u8 = undefined;
7573 const stderr = try io.lockStderr(&buffer, null);
7574 defer io.unlockStderr();
7575 const w = &stderr.file_writer.interface;
7576 _ = coff.dump(w, tid) catch |err| switch (err) {
7577 error.WriteFailed => return stderr.file_writer.err.?,
7578 };
7579}
7580
7581pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) Io.Writer.Error!link.File.DumpResult {
7582 if (coff.options.enable_link_snapshots) {
7583 try coff.printNode(tid, w, .root, 0);
7584 try w.writeAll("Section table:\n");
7585 for (coff.section_table.keys(), coff.section_table.values()) |name, sec|
7586 try coff.printSection(w, name, sec.si);
7587 try w.writeAll("Symbol table:\n");
7588 for (1..coff.symbols.items.len) |si|
7589 try coff.printSymbol(w, tid, @fromBackingInt(@intCast(si)));
7590
7591 return .enabled;
7592 }
7593 return .disabled;
7594}
7595
7596fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) Io.Writer.Error!void {
7597 const sym = si.get(coff);
7598 try w.print("{d:0>6}@{d:0>2} {x:08} n{d:0>8} | {s}\n", .{
7599 si,
7600 sym.section_number,
7601 if (sym.flags.extra_tag == .size) sym.extra.size else 0,
7602 sym.ni,
7603 name.toSlice(coff),
7604 });
7605}
7606
7607fn printSymbol(
7608 coff: *Coff,
7609 w: *Io.Writer,
7610 tid: Zcu.PerThread.Id,
7611 si: Symbol.Index,
7612) Io.Writer.Error!void {
7613 const sym = si.get(coff);
7614 try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} {s} n{d:0>8}+{x:08}:{s: <26} | {x:08} ", .{
7615 si,
7616 sym.section_number,
7617 if (sym.flags.extra_tag == .size)
7618 @as(u64, sym.extra.size)
7619 else if (sym.ni.unwrap()) |ni|
7620 ni.location(&coff.mf).resolve(&coff.mf)[1]
7621 else
7622 0,
7623 switch (sym.flags.value_tag) {
7624 .none => "xx",
7625 .weak_alias_name => "an",
7626 .weak_alias_si => "as",
7627 .node_offset => "no",
7628 },
7629 switch (sym.flags.extra_tag) {
7630 .size => "sz",
7631 .isli => "li",
7632 .next_alias_si => "na",
7633 },
7634 switch (sym.flags.type) {
7635 .unknown => "u",
7636 .code => "c",
7637 .data => "d",
7638 },
7639 sym.ni,
7640 if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0,
7641 if (sym.ni.unwrap()) |ni| @tagName(coff.getNode(ni)) else "",
7642 sym.rva,
7643 });
7644
7645 if (sym.gmi != .none) {
7646 try w.print("G {f}\n", .{fmtGlobalName(coff, sym.gmi)});
7647 } else {
7648 try w.writeAll("| ");
7649 try coff.printNodeName(w, tid, coff.getNode(sym.ni.unwrap().?));
7650 if (sym.flags.extra_tag == .isli)
7651 try w.print(" | {s}", .{sym.extra.isli.name(coff).toSlice(coff)});
7652 try w.writeByte('\n');
7653 }
7654}
7655
7656const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex };
7657
7658fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalName, globalNameEscape) {
7659 return .{ .data = .{ .coff = coff, .gmi = gmi } };
7660}
7661
7662fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void {
7663 if (data.gmi == .none) return;
7664 try w.writeAll(data.gmi.name(data.coff).toSlice(data.coff));
7665 if (data.gmi.libName(data.coff).unwrap()) |lib_name|
7666 try w.print("({s})", .{lib_name.toSlice(data.coff)});
7667}
7668
7669fn printNodeName(
7670 coff: *Coff,
7671 w: *std.Io.Writer,
7672 tid: Zcu.PerThread.Id,
7673 node: Node,
7674) Io.Writer.Error!void {
7675 switch (node) {
7676 else => {},
7677 .image_section => |si| try w.print("({s})", .{
7678 std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
7679 }),
7680 .input_section => |isi| {
7681 const ioi = isi.input(coff);
7682 const is = isi.inputSection(coff);
7683 try w.print("({f}{f}, {s}", .{
7684 ioi.path(coff).fmtEscapeString(),
7685 fmtMemberNameString(ioi.memberName(coff)),
7686 coff.getNode(is.si.node(coff).parent(&coff.mf).unwrap().?).object_section.name(coff).toSlice(coff),
7687 });
7688 if (is.comdat_si != .null) {
7689 const comdat_sym = is.comdat_si.get(coff);
7690 const comdat_name = if (comdat_sym.gmi != .none)
7691 comdat_sym.gmi.name(coff).toSlice(coff)
7692 else
7693 coff.input_symbols.items[@backingInt(comdat_sym.extra.isli)].name.toSlice(coff);
7694
7695 try w.print("={s}", .{comdat_name});
7696 }
7697 try w.writeAll(")");
7698 },
7699 .import_lookup_table,
7700 .import_address_table,
7701 .import_hint_name_table,
7702 => |import_index| try w.print("({s})", .{
7703 std.mem.sliceTo(import_index.get(coff).import_hint_name_table_ni.sliceConst(&coff.mf), 0),
7704 }),
7705 inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{
7706 smi.name(coff).toSlice(coff),
7707 }),
7708 .import_thunk,
7709 => |gmi| {
7710 try w.writeByte('(');
7711 if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7712 try w.print("{s})", .{gmi.name(coff).toSlice(coff)});
7713 },
7714 .nav => |nmi| {
7715 const zcu = coff.base.comp.zcu.?;
7716 const ip = &zcu.intern_pool;
7717 const nav = ip.getNav(nmi.navIndex(coff));
7718 try w.print("({f}, {f})", .{
7719 Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }),
7720 nav.fqn.fmt(ip),
7721 });
7722 },
7723 .uav => |umi| {
7724 const zcu = coff.base.comp.zcu.?;
7725 const val: Value = .fromInterned(umi.uavValue(coff));
7726 try w.print("({f}, {f})", .{
7727 val.typeOf(zcu).fmt(.{ .zcu = zcu, .tid = tid }),
7728 val.fmtValue(.{ .zcu = zcu, .tid = tid }),
7729 });
7730 },
7731 inline .lazy_code, .lazy_const_data => |lmi| try w.print("({f})", .{
7732 Type.fromInterned(lmi.lazySymbol(coff).ty).fmt(.{
7733 .zcu = coff.base.comp.zcu.?,
7734 .tid = tid,
7735 }),
7736 }),
7737 .builtin => |si| {
7738 const sym = si.get(coff);
7739 if (sym.gmi != .none) {
7740 try w.writeByte('(');
7741 if (sym.gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7742 try w.print("{s})", .{sym.gmi.name(coff).toSlice(coff)});
7743 }
7744 },
7745 }
7746}
7747
7748pub fn printNode(
7749 coff: *Coff,
7750 tid: Zcu.PerThread.Id,
7751 w: *Io.Writer,
7752 ni: MappedFile.Node.Index,
7753 indent: usize,
7754) Io.Writer.Error!void {
7755 const node = coff.getNode(ni);
7756 try w.splatByteAll(' ', indent);
7757 try w.writeAll(@tagName(node));
7758 try coff.printNodeName(w, tid, node);
7759 {
7760 const mf_node = &coff.mf.nodes.items[@backingInt(ni)];
7761 const off, const size = mf_node.location().resolve(&coff.mf);
7762 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}{s}\n", .{
7763 @backingInt(ni),
7764 off,
7765 size,
7766 mf_node.flags.alignment.toByteUnits(),
7767 mf_node.flags.position,
7768 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",
7769 if (mf_node.flags.moved) " moved" else "",
7770 if (mf_node.flags.resized) " resized" else "",
7771 if (mf_node.flags.has_content) " has_content" else "",
7772 });
7773 }
7774 if (ni.first(&coff.mf).unwrap()) |first_ni| {
7775 // non-leaf, just print children
7776 var child_ni = first_ni;
7777 while (true) {
7778 try coff.printNode(tid, w, child_ni, indent + 1);
7779 child_ni = child_ni.next(&coff.mf).unwrap() orelse break;
7780 }
7781 return;
7782 }
7783 const start_address: usize, const end_address: usize = file_loc: {
7784 const file_loc = ni.fileLocation(&coff.mf, false);
7785 break :file_loc .{ @intCast(file_loc.offset), @intCast(file_loc.offset + file_loc.size) };
7786 };
7787 var address = start_address;
7788 const line_len = 0x10;
7789 while (true) : (address = @min(std.mem.alignForward(usize, address + 1, line_len), end_address)) {
7790 try w.splatByteAll(' ', indent + 1);
7791 try w.print("{x:0>8}", .{address});
7792 if (address == end_address) break try w.writeByte('\n');
7793 try w.splatByteAll(' ', 2);
7794 const start_byte_address = std.mem.alignBackward(usize, address, line_len);
7795 const end_byte_address = start_byte_address + line_len;
7796 for (start_byte_address..end_byte_address) |byte_address|
7797 if (byte_address < start_address or byte_address >= end_address)
7798 try w.splatByteAll(' ', 3)
7799 else
7800 try w.print("{x:0>2} ", .{coff.mf.memory_map.memory[byte_address]});
7801 try w.writeByte(' ');
7802 for (start_byte_address..@min(end_address, end_byte_address)) |byte_address|
7803 try w.writeByte(if (byte_address < start_address or byte_address >= end_address) ' ' else char: {
7804 const byte = coff.mf.memory_map.memory[byte_address];
7805 break :char if (std.ascii.isPrint(byte)) byte else '.';
7806 });
7807 try w.writeByte('\n');
7808 }
7809}