authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-01 15:21:49-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-02 17:44:52-04:00
log1fa11e0954fe84e1c571384d9bcd8a7ffadc6425
tree57f2b0bde01ba7e420bbf26fc503276445747d56
parente1f3fc6ce289502cde1e52fa946476ff8e3bcaac

Coff: delete


9 files changed, 85 insertions(+), 3294 deletions(-)

CMakeLists.txt-1
......@@ -561,7 +561,6 @@ set(ZIG_STAGE2_SOURCES
561561 src/libs/libunwind.zig
562562 src/link.zig
563563 src/link/C.zig
564 src/link/Coff.zig
565564 src/link/Dwarf.zig
566565 src/link/Elf.zig
567566 src/link/Elf/Archive.zig
src/Compilation.zig+1-1
......@@ -3226,7 +3226,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
32263226 .root_dir = comp.dirs.local_cache,
32273227 .sub_path = try fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }),
32283228 };
3229 const result: link.File.OpenError!void = switch (need_writable_dance) {
3229 const result: (link.File.OpenError || error{HotSwapUnavailableOnHostOperatingSystem})!void = switch (need_writable_dance) {
32303230 .no => {},
32313231 .lf_only => lf.makeWritable(),
32323232 .lf_and_debug => res: {
src/Compilation/Config.zig-2
......@@ -438,8 +438,6 @@ pub fn resolve(options: Options) ResolveError!Config {
438438
439439 if (options.use_new_linker) |x| break :b x;
440440
441 if (target.ofmt == .coff) break :b true;
442
443441 break :b options.incremental;
444442 };
445443
src/codegen.zig-15
......@@ -978,21 +978,6 @@ pub fn genNavRef(
978978 },
979979 .link_once => unreachable,
980980 }
981 } else if (lf.cast(.coff)) |coff_file| {
982 // TODO audit this
983 switch (linkage) {
984 .internal => {
985 const atom_index = try coff_file.getOrCreateAtomForNav(nav_index);
986 const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
987 return .{ .sym_index = sym_index };
988 },
989 .strong, .weak => {
990 const global_index = try coff_file.getGlobalSymbol(nav.name.toSlice(ip), lib_name.toSlice(ip));
991 try coff_file.need_got_table.put(zcu.gpa, global_index, {}); // needs GOT
992 return .{ .sym_index = global_index };
993 },
994 .link_once => unreachable,
995 }
996981 } else if (lf.cast(.coff2)) |coff| {
997982 return .{ .sym_index = @intFromEnum(try coff.navSymbol(zcu, nav_index)) };
998983 } else {
src/codegen/aarch64/Mir.zig-7
......@@ -135,11 +135,6 @@ pub fn emit(
135135 else if (lf.cast(.macho)) |mf|
136136 mf.getZigObject().?.getOrCreateMetadataForLazySymbol(mf, pt, lazy_reloc.symbol) catch |err|
137137 return zcu.codegenFail(func.owner_nav, "{s} creating lazy symbol", .{@errorName(err)})
138 else if (lf.cast(.coff)) |cf|
139 if (cf.getOrCreateAtomForLazySymbol(pt, lazy_reloc.symbol)) |atom|
140 cf.getAtom(atom).getSymbolIndex().?
141 else |err|
142 return zcu.codegenFail(func.owner_nav, "{s} creating lazy symbol", .{@errorName(err)})
143138 else
144139 return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}),
145140 mir.body[lazy_reloc.reloc.label],
......@@ -154,8 +149,6 @@ pub fn emit(
154149 try ef.getGlobalSymbol(std.mem.span(global_reloc.name), null)
155150 else if (lf.cast(.macho)) |mf|
156151 try mf.getGlobalSymbol(std.mem.span(global_reloc.name), null)
157 else if (lf.cast(.coff)) |cf|
158 try cf.getGlobalSymbol(std.mem.span(global_reloc.name), "compiler_rt")
159152 else
160153 return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}),
161154 mir.body[global_reloc.reloc.label],
src/codegen/x86_64/Emit.zig+2-72
......@@ -170,11 +170,6 @@ pub fn emitMir(emit: *Emit) Error!void {
170170 else if (emit.bin_file.cast(.macho)) |macho_file|
171171 macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, emit.pt, lazy_sym) catch |err|
172172 return emit.fail("{s} creating lazy symbol", .{@errorName(err)})
173 else if (emit.bin_file.cast(.coff)) |coff_file|
174 if (coff_file.getOrCreateAtomForLazySymbol(emit.pt, lazy_sym)) |atom|
175 coff_file.getAtom(atom).getSymbolIndex().?
176 else |err|
177 return emit.fail("{s} creating lazy symbol", .{@errorName(err)})
178173 else if (emit.bin_file.cast(.coff2)) |elf|
179174 @intFromEnum(try elf.lazySymbol(lazy_sym))
180175 else
......@@ -190,8 +185,6 @@ pub fn emitMir(emit: *Emit) Error!void {
190185 .type = .FUNC,
191186 })) else if (emit.bin_file.cast(.macho)) |macho_file|
192187 try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)
193 else if (emit.bin_file.cast(.coff)) |coff_file|
194 try coff_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, "compiler_rt")
195188 else if (emit.bin_file.cast(.coff2)) |coff| @intFromEnum(try coff.globalSymbol(
196189 extern_func.toSlice(&emit.lower.mir).?,
197190 switch (comp.compiler_rt_strat) {
......@@ -211,9 +204,7 @@ pub fn emitMir(emit: *Emit) Error!void {
211204 switch (lowered_inst.encoding.mnemonic) {
212205 .call => {
213206 reloc.target.type = .branch;
214 if (emit.bin_file.cast(.coff)) |_| try emit.encodeInst(try .new(.none, .call, &.{
215 .{ .mem = .initRip(.ptr, 0) },
216 }, emit.lower.target), reloc_info) else try emit.encodeInst(lowered_inst, reloc_info);
207 try emit.encodeInst(lowered_inst, reloc_info);
217208 continue :lowered_inst;
218209 },
219210 else => {},
......@@ -290,37 +281,6 @@ pub fn emitMir(emit: *Emit) Error!void {
290281 }, emit.lower.target), reloc_info),
291282 else => unreachable,
292283 }
293 } else if (emit.bin_file.cast(.coff)) |_| {
294 if (reloc.target.is_extern) switch (lowered_inst.encoding.mnemonic) {
295 .lea => try emit.encodeInst(try .new(.none, .mov, &.{
296 lowered_inst.ops[0],
297 .{ .mem = .initRip(.ptr, 0) },
298 }, emit.lower.target), reloc_info),
299 .mov => {
300 const dst_reg = lowered_inst.ops[0].reg.to64();
301 try emit.encodeInst(try .new(.none, .mov, &.{
302 .{ .reg = dst_reg },
303 .{ .mem = .initRip(.ptr, 0) },
304 }, emit.lower.target), reloc_info);
305 try emit.encodeInst(try .new(.none, .mov, &.{
306 lowered_inst.ops[0],
307 .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{
308 .reg = dst_reg,
309 } }) },
310 }, emit.lower.target), &.{});
311 },
312 else => unreachable,
313 } else switch (lowered_inst.encoding.mnemonic) {
314 .lea => try emit.encodeInst(try .new(.none, .lea, &.{
315 lowered_inst.ops[0],
316 .{ .mem = .initRip(.none, 0) },
317 }, emit.lower.target), reloc_info),
318 .mov => try emit.encodeInst(try .new(.none, .mov, &.{
319 lowered_inst.ops[0],
320 .{ .mem = .initRip(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, 0) },
321 }, emit.lower.target), reloc_info),
322 else => unreachable,
323 }
324284 } else if (emit.bin_file.cast(.coff2)) |_| {
325285 switch (lowered_inst.encoding.mnemonic) {
326286 .lea => try emit.encodeInst(try .new(.none, .lea, &.{
......@@ -820,22 +780,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
820780 @enumFromInt(reloc.target.index),
821781 reloc.off,
822782 .{ .X86_64 = .@"32" },
823 ) else if (emit.bin_file.cast(.coff)) |coff_file| {
824 const atom_index = coff_file.getAtomIndexForSymbol(
825 .{ .sym_index = emit.atom_index, .file = null },
826 ).?;
827 try coff_file.addRelocation(atom_index, .{
828 .type = if (reloc.target.is_extern) .got else .direct,
829 .target = if (reloc.target.is_extern)
830 coff_file.getGlobalByIndex(reloc.target.index)
831 else
832 .{ .sym_index = reloc.target.index, .file = null },
833 .offset = end_offset - 4,
834 .addend = @intCast(reloc.off),
835 .pcrel = true,
836 .length = 2,
837 });
838 } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
783 ) else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
839784 @enumFromInt(emit.atom_index),
840785 end_offset - 4,
841786 @enumFromInt(reloc.target.index),
......@@ -873,21 +818,6 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
873818 .symbolnum = @intCast(reloc.target.index),
874819 },
875820 });
876 } else if (emit.bin_file.cast(.coff)) |coff_file| {
877 const atom_index = coff_file.getAtomIndexForSymbol(
878 .{ .sym_index = emit.atom_index, .file = null },
879 ).?;
880 try coff_file.addRelocation(atom_index, .{
881 .type = if (reloc.target.is_extern) .import else .got,
882 .target = if (reloc.target.is_extern)
883 coff_file.getGlobalByIndex(reloc.target.index)
884 else
885 .{ .sym_index = reloc.target.index, .file = null },
886 .offset = end_offset - 4,
887 .addend = @intCast(reloc.off),
888 .pcrel = true,
889 .length = 2,
890 });
891821 } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
892822 @enumFromInt(emit.atom_index),
893823 end_offset - 4,
src/link.zig+4-14
......@@ -574,16 +574,13 @@ pub const File = struct {
574574 const gpa = comp.gpa;
575575 switch (base.tag) {
576576 .lld => assert(base.file == null),
577 .coff, .elf, .macho, .wasm, .goff, .xcoff => {
577 .elf, .macho, .wasm, .goff, .xcoff => {
578578 if (base.file != null) return;
579579 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
580580 const emit = base.emit;
581581 if (base.child_pid) |pid| {
582582 if (builtin.os.tag == .windows) {
583 const coff_file = base.cast(.coff).?;
584 coff_file.ptraceAttach(pid) catch |err| {
585 log.warn("attaching failed with error: {s}", .{@errorName(err)});
586 };
583 return error.HotSwapUnavailableOnHostOperatingSystem;
587584 } else {
588585 // If we try to open the output file in write mode while it is running,
589586 // it will return ETXTBSY. So instead, we copy the file, atomically rename it
......@@ -671,7 +668,7 @@ pub const File = struct {
671668 }
672669 }
673670 },
674 .coff, .macho, .wasm, .goff, .xcoff => if (base.file) |f| {
671 .macho, .wasm, .goff, .xcoff => if (base.file) |f| {
675672 dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
676673 f.close();
677674 base.file = null;
......@@ -684,10 +681,6 @@ pub const File = struct {
684681 log.warn("detaching failed with error: {s}", .{@errorName(err)});
685682 };
686683 },
687 .windows => {
688 const coff_file = base.cast(.coff).?;
689 coff_file.ptraceDetach(pid);
690 },
691684 else => return error.HotSwapUnavailableOnHostOperatingSystem,
692685 }
693686 }
......@@ -1157,7 +1150,6 @@ pub const File = struct {
11571150 }
11581151
11591152 pub const Tag = enum {
1160 coff,
11611153 coff2,
11621154 elf,
11631155 elf2,
......@@ -1172,7 +1164,6 @@ pub const File = struct {
11721164
11731165 pub fn Type(comptime tag: Tag) type {
11741166 return switch (tag) {
1175 .coff => Coff,
11761167 .coff2 => Coff2,
11771168 .elf => Elf,
11781169 .elf2 => Elf2,
......@@ -1189,7 +1180,7 @@ pub const File = struct {
11891180
11901181 fn fromObjectFormat(ofmt: std.Target.ObjectFormat, use_new_linker: bool) Tag {
11911182 return switch (ofmt) {
1192 .coff => if (use_new_linker) .coff2 else .coff,
1183 .coff => .coff2,
11931184 .elf => if (use_new_linker) .elf2 else .elf,
11941185 .macho => .macho,
11951186 .wasm => .wasm,
......@@ -1274,7 +1265,6 @@ pub const File = struct {
12741265
12751266 pub const Lld = @import("link/Lld.zig");
12761267 pub const C = @import("link/C.zig");
1277 pub const Coff = @import("link/Coff.zig");
12781268 pub const Coff2 = @import("link/Coff2.zig");
12791269 pub const Elf = @import("link/Elf.zig");
12801270 pub const Elf2 = @import("link/Elf2.zig");
src/link/Coff.zig deleted-3169
......@@ -1,3169 +0,0 @@
1//! The main driver of the self-hosted COFF linker.
2const Coff = @This();
3
4const std = @import("std");
5const build_options = @import("build_options");
6const builtin = @import("builtin");
7const assert = std.debug.assert;
8const coff_util = std.coff;
9const fmt = std.fmt;
10const fs = std.fs;
11const log = std.log.scoped(.link);
12const math = std.math;
13const mem = std.mem;
14
15const Allocator = std.mem.Allocator;
16const Path = std.Build.Cache.Path;
17const Directory = std.Build.Cache.Directory;
18const Cache = std.Build.Cache;
19
20const aarch64_util = link.aarch64;
21const allocPrint = std.fmt.allocPrint;
22const codegen = @import("../codegen.zig");
23const link = @import("../link.zig");
24const target_util = @import("../target.zig");
25const trace = @import("../tracy.zig").trace;
26
27const Compilation = @import("../Compilation.zig");
28const Zcu = @import("../Zcu.zig");
29const InternPool = @import("../InternPool.zig");
30const TableSection = @import("table_section.zig").TableSection;
31const StringTable = @import("StringTable.zig");
32const Type = @import("../Type.zig");
33const Value = @import("../Value.zig");
34const AnalUnit = InternPool.AnalUnit;
35const dev = @import("../dev.zig");
36
37base: link.File,
38image_base: u64,
39/// TODO this and minor_subsystem_version should be combined into one property and left as
40/// default or populated together. They should not be separate fields.
41major_subsystem_version: u16,
42minor_subsystem_version: u16,
43entry: link.File.OpenOptions.Entry,
44entry_addr: ?u32,
45module_definition_file: ?[]const u8,
46repro: bool,
47
48ptr_width: PtrWidth,
49page_size: u32,
50
51sections: std.MultiArrayList(Section) = .{},
52data_directories: [coff_util.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]coff_util.ImageDataDirectory,
53
54text_section_index: ?u16 = null,
55got_section_index: ?u16 = null,
56rdata_section_index: ?u16 = null,
57data_section_index: ?u16 = null,
58reloc_section_index: ?u16 = null,
59idata_section_index: ?u16 = null,
60
61locals: std.ArrayListUnmanaged(coff_util.Symbol) = .empty,
62globals: std.ArrayListUnmanaged(SymbolWithLoc) = .empty,
63resolver: std.StringHashMapUnmanaged(u32) = .empty,
64unresolved: std.AutoArrayHashMapUnmanaged(u32, bool) = .empty,
65need_got_table: std.AutoHashMapUnmanaged(u32, void) = .empty,
66
67locals_free_list: std.ArrayListUnmanaged(u32) = .empty,
68globals_free_list: std.ArrayListUnmanaged(u32) = .empty,
69
70strtab: StringTable = .{},
71strtab_offset: ?u32 = null,
72
73temp_strtab: StringTable = .{},
74
75got_table: TableSection(SymbolWithLoc) = .{},
76
77/// A table of ImportTables partitioned by the library name.
78/// Key is an offset into the interning string table `temp_strtab`.
79import_tables: std.AutoArrayHashMapUnmanaged(u32, ImportTable) = .empty,
80
81got_table_count_dirty: bool = true,
82got_table_contents_dirty: bool = true,
83imports_count_dirty: bool = true,
84
85/// Table of tracked LazySymbols.
86lazy_syms: LazySymbolTable = .{},
87
88/// Table of tracked `Nav`s.
89navs: NavTable = .{},
90
91/// List of atoms that are either synthetic or map directly to the Zig source program.
92atoms: std.ArrayListUnmanaged(Atom) = .empty,
93
94/// Table of atoms indexed by the symbol index.
95atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .empty,
96
97uavs: UavTable = .{},
98
99/// A table of relocations indexed by the owning them `Atom`.
100/// Note that once we refactor `Atom`'s lifetime and ownership rules,
101/// this will be a table indexed by index into the list of Atoms.
102relocs: RelocTable = .{},
103
104/// A table of base relocations indexed by the owning them `Atom`.
105/// Note that once we refactor `Atom`'s lifetime and ownership rules,
106/// this will be a table indexed by index into the list of Atoms.
107base_relocs: BaseRelocationTable = .{},
108
109/// Hot-code swapping state.
110hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
111
112const is_hot_update_compatible = switch (builtin.target.os.tag) {
113 .windows => true,
114 else => false,
115};
116
117const HotUpdateState = struct {
118 /// Base address at which the process (image) got loaded.
119 /// We need this info to correctly slide pointers when relocating.
120 loaded_base_address: ?std.os.windows.HMODULE = null,
121};
122
123const NavTable = std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, AvMetadata);
124const UavTable = std.AutoHashMapUnmanaged(InternPool.Index, AvMetadata);
125const RelocTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation));
126const BaseRelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32));
127
128pub const default_file_alignment: u16 = 0x200;
129pub const default_size_of_stack_reserve: u32 = 0x1000000;
130pub const default_size_of_stack_commit: u32 = 0x1000;
131pub const default_size_of_heap_reserve: u32 = 0x100000;
132pub const default_size_of_heap_commit: u32 = 0x1000;
133
134const Section = struct {
135 header: coff_util.SectionHeader,
136
137 last_atom_index: ?Atom.Index = null,
138
139 /// A list of atoms that have surplus capacity. This list can have false
140 /// positives, as functions grow and shrink over time, only sometimes being added
141 /// or removed from the freelist.
142 ///
143 /// An atom has surplus capacity when its overcapacity value is greater than
144 /// padToIdeal(minimum_atom_size). That is, when it has so
145 /// much extra capacity, that we could fit a small new symbol in it, itself with
146 /// ideal_capacity or more.
147 ///
148 /// Ideal capacity is defined by size + (size / ideal_factor).
149 ///
150 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
151 /// overcapacity can be negative. A simple way to have negative overcapacity is to
152 /// allocate a fresh atom, which will have ideal capacity, and then grow it
153 /// by 1 byte. It will then have -1 overcapacity.
154 free_list: std.ArrayListUnmanaged(Atom.Index) = .empty,
155};
156
157const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.Index, LazySymbolMetadata);
158
159const LazySymbolMetadata = struct {
160 const State = enum { unused, pending_flush, flushed };
161 text_atom: Atom.Index = undefined,
162 rdata_atom: Atom.Index = undefined,
163 text_state: State = .unused,
164 rdata_state: State = .unused,
165};
166
167const AvMetadata = struct {
168 atom: Atom.Index,
169 section: u16,
170 /// A list of all exports aliases of this Decl.
171 exports: std.ArrayListUnmanaged(u32) = .empty,
172
173 fn deinit(m: *AvMetadata, allocator: Allocator) void {
174 m.exports.deinit(allocator);
175 }
176
177 fn getExport(m: AvMetadata, coff: *const Coff, name: []const u8) ?u32 {
178 for (m.exports.items) |exp| {
179 if (mem.eql(u8, name, coff.getSymbolName(.{
180 .sym_index = exp,
181 .file = null,
182 }))) return exp;
183 }
184 return null;
185 }
186
187 fn getExportPtr(m: *AvMetadata, coff: *Coff, name: []const u8) ?*u32 {
188 for (m.exports.items) |*exp| {
189 if (mem.eql(u8, name, coff.getSymbolName(.{
190 .sym_index = exp.*,
191 .file = null,
192 }))) return exp;
193 }
194 return null;
195 }
196};
197
198pub const PtrWidth = enum {
199 p32,
200 p64,
201
202 /// Size in bytes.
203 pub fn size(pw: PtrWidth) u4 {
204 return switch (pw) {
205 .p32 => 4,
206 .p64 => 8,
207 };
208 }
209};
210
211pub const SymbolWithLoc = struct {
212 // Index into the respective symbol table.
213 sym_index: u32,
214
215 // null means it's a synthetic global or Zig source.
216 file: ?u32 = null,
217
218 pub fn eql(this: SymbolWithLoc, other: SymbolWithLoc) bool {
219 if (this.file == null and other.file == null) {
220 return this.sym_index == other.sym_index;
221 }
222 if (this.file != null and other.file != null) {
223 return this.sym_index == other.sym_index and this.file.? == other.file.?;
224 }
225 return false;
226 }
227};
228
229/// When allocating, the ideal_capacity is calculated by
230/// actual_capacity + (actual_capacity / ideal_factor)
231const ideal_factor = 3;
232
233/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
234/// it as a possible place to put new symbols, it must have enough room for this many bytes
235/// (plus extra for reserved capacity).
236const minimum_text_block_size = 64;
237pub const min_text_capacity = padToIdeal(minimum_text_block_size);
238
239pub fn createEmpty(
240 arena: Allocator,
241 comp: *Compilation,
242 emit: Path,
243 options: link.File.OpenOptions,
244) !*Coff {
245 const target = &comp.root_mod.resolved_target.result;
246 assert(target.ofmt == .coff);
247 const optimize_mode = comp.root_mod.optimize_mode;
248 const output_mode = comp.config.output_mode;
249 const link_mode = comp.config.link_mode;
250 const use_llvm = comp.config.use_llvm;
251
252 const ptr_width: PtrWidth = switch (target.ptrBitWidth()) {
253 0...32 => .p32,
254 33...64 => .p64,
255 else => return error.UnsupportedCOFFArchitecture,
256 };
257 const page_size: u32 = switch (target.cpu.arch) {
258 else => 0x1000,
259 };
260
261 const coff = try arena.create(Coff);
262 coff.* = .{
263 .base = .{
264 .tag = .coff,
265 .comp = comp,
266 .emit = emit,
267 .zcu_object_basename = if (use_llvm)
268 try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)})
269 else
270 null,
271 .stack_size = options.stack_size orelse 16777216,
272 .gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
273 .print_gc_sections = options.print_gc_sections,
274 .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
275 .file = null,
276 .build_id = options.build_id,
277 },
278 .ptr_width = ptr_width,
279 .page_size = page_size,
280
281 .data_directories = [1]coff_util.ImageDataDirectory{.{
282 .virtual_address = 0,
283 .size = 0,
284 }} ** coff_util.IMAGE_NUMBEROF_DIRECTORY_ENTRIES,
285
286 .image_base = options.image_base orelse switch (output_mode) {
287 .Exe => switch (target.cpu.arch) {
288 .aarch64, .x86_64 => 0x140000000,
289 .thumb, .x86 => 0x400000,
290 else => unreachable,
291 },
292 .Lib => switch (target.cpu.arch) {
293 .aarch64, .x86_64 => 0x180000000,
294 .thumb, .x86 => 0x10000000,
295 else => unreachable,
296 },
297 .Obj => 0,
298 },
299
300 .entry = options.entry,
301
302 .major_subsystem_version = options.major_subsystem_version orelse 6,
303 .minor_subsystem_version = options.minor_subsystem_version orelse 0,
304 .entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse
305 return error.EntryAddressTooBig,
306 .module_definition_file = options.module_definition_file,
307 .repro = options.repro,
308 };
309 errdefer coff.base.destroy();
310
311 coff.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
312 .truncate = true,
313 .read = true,
314 .mode = link.File.determineMode(output_mode, link_mode),
315 });
316
317 const gpa = comp.gpa;
318
319 try coff.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32));
320 coff.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32));
321
322 try coff.temp_strtab.buffer.append(gpa, 0);
323
324 // Index 0 is always a null symbol.
325 try coff.locals.append(gpa, .{
326 .name = [_]u8{0} ** 8,
327 .value = 0,
328 .section_number = .UNDEFINED,
329 .type = .{ .base_type = .NULL, .complex_type = .NULL },
330 .storage_class = .NULL,
331 .number_of_aux_symbols = 0,
332 });
333
334 if (coff.text_section_index == null) {
335 const file_size: u32 = @intCast(options.program_code_size_hint);
336 coff.text_section_index = try coff.allocateSection(".text", file_size, .{
337 .CNT_CODE = true,
338 .MEM_EXECUTE = true,
339 .MEM_READ = true,
340 });
341 }
342
343 if (coff.got_section_index == null) {
344 const file_size = @as(u32, @intCast(options.symbol_count_hint)) * coff.ptr_width.size();
345 coff.got_section_index = try coff.allocateSection(".got", file_size, .{
346 .CNT_INITIALIZED_DATA = true,
347 .MEM_READ = true,
348 });
349 }
350
351 if (coff.rdata_section_index == null) {
352 const file_size: u32 = coff.page_size;
353 coff.rdata_section_index = try coff.allocateSection(".rdata", file_size, .{
354 .CNT_INITIALIZED_DATA = true,
355 .MEM_READ = true,
356 });
357 }
358
359 if (coff.data_section_index == null) {
360 const file_size: u32 = coff.page_size;
361 coff.data_section_index = try coff.allocateSection(".data", file_size, .{
362 .CNT_INITIALIZED_DATA = true,
363 .MEM_READ = true,
364 .MEM_WRITE = true,
365 });
366 }
367
368 if (coff.idata_section_index == null) {
369 const file_size = @as(u32, @intCast(options.symbol_count_hint)) * coff.ptr_width.size();
370 coff.idata_section_index = try coff.allocateSection(".idata", file_size, .{
371 .CNT_INITIALIZED_DATA = true,
372 .MEM_READ = true,
373 });
374 }
375
376 if (coff.reloc_section_index == null) {
377 const file_size = @as(u32, @intCast(options.symbol_count_hint)) * @sizeOf(coff_util.BaseRelocation);
378 coff.reloc_section_index = try coff.allocateSection(".reloc", file_size, .{
379 .CNT_INITIALIZED_DATA = true,
380 .MEM_DISCARDABLE = true,
381 .MEM_READ = true,
382 });
383 }
384
385 if (coff.strtab_offset == null) {
386 const file_size = @as(u32, @intCast(coff.strtab.buffer.items.len));
387 coff.strtab_offset = coff.findFreeSpace(file_size, @alignOf(u32)); // 4bytes aligned seems like a good idea here
388 log.debug("found strtab free space 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + file_size });
389 }
390
391 {
392 // We need to find out what the max file offset is according to section headers.
393 // Otherwise, we may end up with an COFF binary with file size not matching the final section's
394 // offset + it's filesize.
395 // TODO I don't like this here one bit
396 var max_file_offset: u64 = 0;
397 for (coff.sections.items(.header)) |header| {
398 if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) {
399 max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data;
400 }
401 }
402 try coff.pwriteAll(&[_]u8{0}, max_file_offset);
403 }
404
405 return coff;
406}
407
408pub fn open(
409 arena: Allocator,
410 comp: *Compilation,
411 emit: Path,
412 options: link.File.OpenOptions,
413) !*Coff {
414 // TODO: restore saved linker state, don't truncate the file, and
415 // participate in incremental compilation.
416 return createEmpty(arena, comp, emit, options);
417}
418
419pub fn deinit(coff: *Coff) void {
420 const gpa = coff.base.comp.gpa;
421
422 for (coff.sections.items(.free_list)) |*free_list| {
423 free_list.deinit(gpa);
424 }
425 coff.sections.deinit(gpa);
426
427 coff.atoms.deinit(gpa);
428 coff.locals.deinit(gpa);
429 coff.globals.deinit(gpa);
430
431 {
432 var it = coff.resolver.keyIterator();
433 while (it.next()) |key_ptr| {
434 gpa.free(key_ptr.*);
435 }
436 coff.resolver.deinit(gpa);
437 }
438
439 coff.unresolved.deinit(gpa);
440 coff.need_got_table.deinit(gpa);
441 coff.locals_free_list.deinit(gpa);
442 coff.globals_free_list.deinit(gpa);
443 coff.strtab.deinit(gpa);
444 coff.temp_strtab.deinit(gpa);
445 coff.got_table.deinit(gpa);
446
447 for (coff.import_tables.values()) |*itab| {
448 itab.deinit(gpa);
449 }
450 coff.import_tables.deinit(gpa);
451
452 coff.lazy_syms.deinit(gpa);
453
454 for (coff.navs.values()) |*metadata| {
455 metadata.deinit(gpa);
456 }
457 coff.navs.deinit(gpa);
458
459 coff.atom_by_index_table.deinit(gpa);
460
461 {
462 var it = coff.uavs.iterator();
463 while (it.next()) |entry| {
464 entry.value_ptr.exports.deinit(gpa);
465 }
466 coff.uavs.deinit(gpa);
467 }
468
469 for (coff.relocs.values()) |*relocs| {
470 relocs.deinit(gpa);
471 }
472 coff.relocs.deinit(gpa);
473
474 for (coff.base_relocs.values()) |*relocs| {
475 relocs.deinit(gpa);
476 }
477 coff.base_relocs.deinit(gpa);
478}
479
480fn allocateSection(coff: *Coff, name: []const u8, size: u32, flags: coff_util.SectionHeader.Flags) !u16 {
481 const index = @as(u16, @intCast(coff.sections.slice().len));
482 const off = coff.findFreeSpace(size, default_file_alignment);
483 // Memory is always allocated in sequence
484 // TODO: investigate if we can allocate .text last; this way it would never need to grow in memory!
485 const vaddr = blk: {
486 if (index == 0) break :blk coff.page_size;
487 const prev_header = coff.sections.items(.header)[index - 1];
488 break :blk mem.alignForward(u32, prev_header.virtual_address + prev_header.virtual_size, coff.page_size);
489 };
490 // We commit more memory than needed upfront so that we don't have to reallocate too soon.
491 const memsz = mem.alignForward(u32, size, coff.page_size) * 100;
492 log.debug("found {s} free space 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
493 name,
494 off,
495 off + size,
496 vaddr,
497 vaddr + size,
498 });
499 var header = coff_util.SectionHeader{
500 .name = undefined,
501 .virtual_size = memsz,
502 .virtual_address = vaddr,
503 .size_of_raw_data = size,
504 .pointer_to_raw_data = off,
505 .pointer_to_relocations = 0,
506 .pointer_to_linenumbers = 0,
507 .number_of_relocations = 0,
508 .number_of_linenumbers = 0,
509 .flags = flags,
510 };
511 const gpa = coff.base.comp.gpa;
512 try coff.setSectionName(&header, name);
513 try coff.sections.append(gpa, .{ .header = header });
514 return index;
515}
516
517fn growSection(coff: *Coff, sect_id: u32, needed_size: u32) !void {
518 const header = &coff.sections.items(.header)[sect_id];
519 const maybe_last_atom_index = coff.sections.items(.last_atom_index)[sect_id];
520 const sect_capacity = coff.allocatedSize(header.pointer_to_raw_data);
521
522 if (needed_size > sect_capacity) {
523 const new_offset = coff.findFreeSpace(needed_size, default_file_alignment);
524 const current_size = if (maybe_last_atom_index) |last_atom_index| blk: {
525 const last_atom = coff.getAtom(last_atom_index);
526 const sym = last_atom.getSymbol(coff);
527 break :blk (sym.value + last_atom.size) - header.virtual_address;
528 } else 0;
529 log.debug("moving {s} from 0x{x} to 0x{x}", .{
530 coff.getSectionName(header),
531 header.pointer_to_raw_data,
532 new_offset,
533 });
534 const amt = try coff.base.file.?.copyRangeAll(
535 header.pointer_to_raw_data,
536 coff.base.file.?,
537 new_offset,
538 current_size,
539 );
540 if (amt != current_size) return error.InputOutput;
541 header.pointer_to_raw_data = new_offset;
542 }
543
544 const sect_vm_capacity = coff.allocatedVirtualSize(header.virtual_address);
545 if (needed_size > sect_vm_capacity) {
546 coff.markRelocsDirtyByAddress(header.virtual_address + header.virtual_size);
547 try coff.growSectionVirtualMemory(sect_id, needed_size);
548 }
549
550 header.virtual_size = @max(header.virtual_size, needed_size);
551 header.size_of_raw_data = needed_size;
552}
553
554fn growSectionVirtualMemory(coff: *Coff, sect_id: u32, needed_size: u32) !void {
555 const header = &coff.sections.items(.header)[sect_id];
556 const increased_size = padToIdeal(needed_size);
557 const old_aligned_end = header.virtual_address + mem.alignForward(u32, header.virtual_size, coff.page_size);
558 const new_aligned_end = header.virtual_address + mem.alignForward(u32, increased_size, coff.page_size);
559 const diff = new_aligned_end - old_aligned_end;
560 log.debug("growing {s} in virtual memory by {x}", .{ coff.getSectionName(header), diff });
561
562 // TODO: enforce order by increasing VM addresses in coff.sections container.
563 // This is required by the loader anyhow as far as I can tell.
564 for (coff.sections.items(.header)[sect_id + 1 ..], 0..) |*next_header, next_sect_id| {
565 const maybe_last_atom_index = coff.sections.items(.last_atom_index)[sect_id + 1 + next_sect_id];
566 next_header.virtual_address += diff;
567
568 if (maybe_last_atom_index) |last_atom_index| {
569 var atom_index = last_atom_index;
570 while (true) {
571 const atom = coff.getAtom(atom_index);
572 const sym = atom.getSymbolPtr(coff);
573 sym.value += diff;
574
575 if (atom.prev_index) |prev_index| {
576 atom_index = prev_index;
577 } else break;
578 }
579 }
580 }
581
582 header.virtual_size = increased_size;
583}
584
585fn allocateAtom(coff: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignment: u32) !u32 {
586 const tracy = trace(@src());
587 defer tracy.end();
588
589 const atom = coff.getAtom(atom_index);
590 const sect_id = @intFromEnum(atom.getSymbol(coff).section_number) - 1;
591 const header = &coff.sections.items(.header)[sect_id];
592 const free_list = &coff.sections.items(.free_list)[sect_id];
593 const maybe_last_atom_index = &coff.sections.items(.last_atom_index)[sect_id];
594 const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size;
595
596 // We use these to indicate our intention to update metadata, placing the new atom,
597 // and possibly removing a free list node.
598 // It would be simpler to do it inside the for loop below, but that would cause a
599 // problem if an error was returned later in the function. So this action
600 // is actually carried out at the end of the function, when errors are no longer possible.
601 var atom_placement: ?Atom.Index = null;
602 var free_list_removal: ?usize = null;
603
604 // First we look for an appropriately sized free list node.
605 // The list is unordered. We'll just take the first thing that works.
606 const vaddr = blk: {
607 var i: usize = 0;
608 while (i < free_list.items.len) {
609 const big_atom_index = free_list.items[i];
610 const big_atom = coff.getAtom(big_atom_index);
611 // We now have a pointer to a live atom that has too much capacity.
612 // Is it enough that we could fit this new atom?
613 const sym = big_atom.getSymbol(coff);
614 const capacity = big_atom.capacity(coff);
615 const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity;
616 const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity;
617 const capacity_end_vaddr = sym.value + capacity;
618 const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
619 const new_start_vaddr = mem.alignBackward(u32, new_start_vaddr_unaligned, alignment);
620 if (new_start_vaddr < ideal_capacity_end_vaddr) {
621 // Additional bookkeeping here to notice if this free list node
622 // should be deleted because the atom that it points to has grown to take up
623 // more of the extra capacity.
624 if (!big_atom.freeListEligible(coff)) {
625 _ = free_list.swapRemove(i);
626 } else {
627 i += 1;
628 }
629 continue;
630 }
631 // At this point we know that we will place the new atom here. But the
632 // remaining question is whether there is still yet enough capacity left
633 // over for there to still be a free list node.
634 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
635 const keep_free_list_node = remaining_capacity >= min_text_capacity;
636
637 // Set up the metadata to be updated, after errors are no longer possible.
638 atom_placement = big_atom_index;
639 if (!keep_free_list_node) {
640 free_list_removal = i;
641 }
642 break :blk new_start_vaddr;
643 } else if (maybe_last_atom_index.*) |last_index| {
644 const last = coff.getAtom(last_index);
645 const last_symbol = last.getSymbol(coff);
646 const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size;
647 const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity;
648 const new_start_vaddr = mem.alignForward(u32, ideal_capacity_end_vaddr, alignment);
649 atom_placement = last_index;
650 break :blk new_start_vaddr;
651 } else {
652 break :blk mem.alignForward(u32, header.virtual_address, alignment);
653 }
654 };
655
656 const expand_section = if (atom_placement) |placement_index|
657 coff.getAtom(placement_index).next_index == null
658 else
659 true;
660 if (expand_section) {
661 const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address;
662 try coff.growSection(sect_id, needed_size);
663 maybe_last_atom_index.* = atom_index;
664 }
665 coff.getAtomPtr(atom_index).size = new_atom_size;
666
667 if (atom.prev_index) |prev_index| {
668 const prev = coff.getAtomPtr(prev_index);
669 prev.next_index = atom.next_index;
670 }
671 if (atom.next_index) |next_index| {
672 const next = coff.getAtomPtr(next_index);
673 next.prev_index = atom.prev_index;
674 }
675
676 if (atom_placement) |big_atom_index| {
677 const big_atom = coff.getAtomPtr(big_atom_index);
678 const atom_ptr = coff.getAtomPtr(atom_index);
679 atom_ptr.prev_index = big_atom_index;
680 atom_ptr.next_index = big_atom.next_index;
681 big_atom.next_index = atom_index;
682 } else {
683 const atom_ptr = coff.getAtomPtr(atom_index);
684 atom_ptr.prev_index = null;
685 atom_ptr.next_index = null;
686 }
687 if (free_list_removal) |i| {
688 _ = free_list.swapRemove(i);
689 }
690
691 return vaddr;
692}
693
694pub fn allocateSymbol(coff: *Coff) !u32 {
695 const gpa = coff.base.comp.gpa;
696 try coff.locals.ensureUnusedCapacity(gpa, 1);
697
698 const index = blk: {
699 if (coff.locals_free_list.pop()) |index| {
700 log.debug(" (reusing symbol index {d})", .{index});
701 break :blk index;
702 } else {
703 log.debug(" (allocating symbol index {d})", .{coff.locals.items.len});
704 const index = @as(u32, @intCast(coff.locals.items.len));
705 _ = coff.locals.addOneAssumeCapacity();
706 break :blk index;
707 }
708 };
709
710 coff.locals.items[index] = .{
711 .name = [_]u8{0} ** 8,
712 .value = 0,
713 .section_number = .UNDEFINED,
714 .type = .{ .base_type = .NULL, .complex_type = .NULL },
715 .storage_class = .NULL,
716 .number_of_aux_symbols = 0,
717 };
718
719 return index;
720}
721
722fn allocateGlobal(coff: *Coff) !u32 {
723 const gpa = coff.base.comp.gpa;
724 try coff.globals.ensureUnusedCapacity(gpa, 1);
725
726 const index = blk: {
727 if (coff.globals_free_list.pop()) |index| {
728 log.debug(" (reusing global index {d})", .{index});
729 break :blk index;
730 } else {
731 log.debug(" (allocating global index {d})", .{coff.globals.items.len});
732 const index = @as(u32, @intCast(coff.globals.items.len));
733 _ = coff.globals.addOneAssumeCapacity();
734 break :blk index;
735 }
736 };
737
738 coff.globals.items[index] = .{
739 .sym_index = 0,
740 .file = null,
741 };
742
743 return index;
744}
745
746fn addGotEntry(coff: *Coff, target: SymbolWithLoc) !void {
747 const gpa = coff.base.comp.gpa;
748 if (coff.got_table.lookup.contains(target)) return;
749 const got_index = try coff.got_table.allocateEntry(gpa, target);
750 try coff.writeOffsetTableEntry(got_index);
751 coff.got_table_count_dirty = true;
752 coff.markRelocsDirtyByTarget(target);
753}
754
755pub fn createAtom(coff: *Coff) !Atom.Index {
756 const gpa = coff.base.comp.gpa;
757 const atom_index = @as(Atom.Index, @intCast(coff.atoms.items.len));
758 const atom = try coff.atoms.addOne(gpa);
759 const sym_index = try coff.allocateSymbol();
760 try coff.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
761 atom.* = .{
762 .sym_index = sym_index,
763 .file = null,
764 .size = 0,
765 .prev_index = null,
766 .next_index = null,
767 };
768 log.debug("creating ATOM(%{d}) at index {d}", .{ sym_index, atom_index });
769 return atom_index;
770}
771
772fn growAtom(coff: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignment: u32) !u32 {
773 const atom = coff.getAtom(atom_index);
774 const sym = atom.getSymbol(coff);
775 const align_ok = mem.alignBackward(u32, sym.value, alignment) == sym.value;
776 const need_realloc = !align_ok or new_atom_size > atom.capacity(coff);
777 if (!need_realloc) return sym.value;
778 return coff.allocateAtom(atom_index, new_atom_size, alignment);
779}
780
781fn shrinkAtom(coff: *Coff, atom_index: Atom.Index, new_block_size: u32) void {
782 _ = coff;
783 _ = atom_index;
784 _ = new_block_size;
785 // TODO check the new capacity, and if it crosses the size threshold into a big enough
786 // capacity, insert a free list node for it.
787}
788
789fn writeAtom(coff: *Coff, atom_index: Atom.Index, code: []u8, resolve_relocs: bool) !void {
790 const atom = coff.getAtom(atom_index);
791 const sym = atom.getSymbol(coff);
792 const section = coff.sections.get(@intFromEnum(sym.section_number) - 1);
793 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
794
795 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{
796 atom.getName(coff),
797 file_offset,
798 file_offset + code.len,
799 });
800
801 const gpa = coff.base.comp.gpa;
802
803 // Gather relocs which can be resolved.
804 // We need to do this as we will be applying different slide values depending
805 // if we are running in hot-code swapping mode or not.
806 // TODO: how crazy would it be to try and apply the actual image base of the loaded
807 // process for the in-file values rather than the Windows defaults?
808 var relocs = std.array_list.Managed(*Relocation).init(gpa);
809 defer relocs.deinit();
810
811 if (resolve_relocs) {
812 if (coff.relocs.getPtr(atom_index)) |rels| {
813 try relocs.ensureTotalCapacityPrecise(rels.items.len);
814 for (rels.items) |*reloc| {
815 if (reloc.isResolvable(coff) and reloc.dirty) {
816 relocs.appendAssumeCapacity(reloc);
817 }
818 }
819 }
820 }
821
822 if (is_hot_update_compatible) {
823 if (coff.base.child_pid) |handle| {
824 const slide = @intFromPtr(coff.hot_state.loaded_base_address.?);
825
826 const mem_code = try gpa.dupe(u8, code);
827 defer gpa.free(mem_code);
828 coff.resolveRelocs(atom_index, relocs.items, mem_code, slide);
829
830 const vaddr = sym.value + slide;
831 const pvaddr = @as(*anyopaque, @ptrFromInt(vaddr));
832
833 log.debug("writing to memory at address {x}", .{vaddr});
834
835 if (build_options.enable_logging) {
836 try debugMem(gpa, handle, pvaddr, mem_code);
837 }
838
839 if (!section.header.flags.MEM_WRITE) {
840 writeMemProtected(handle, pvaddr, mem_code) catch |err| {
841 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
842 };
843 } else {
844 writeMem(handle, pvaddr, mem_code) catch |err| {
845 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
846 };
847 }
848 }
849 }
850
851 if (resolve_relocs) {
852 coff.resolveRelocs(atom_index, relocs.items, code, coff.image_base);
853 }
854 try coff.pwriteAll(code, file_offset);
855 if (resolve_relocs) {
856 // Now we can mark the relocs as resolved.
857 while (relocs.pop()) |reloc| {
858 reloc.dirty = false;
859 }
860 }
861}
862
863fn debugMem(allocator: Allocator, handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
864 const buffer = try allocator.alloc(u8, code.len);
865 defer allocator.free(buffer);
866 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);
867 log.debug("to write: {x}", .{code});
868 log.debug("in memory: {x}", .{memread});
869}
870
871fn writeMemProtected(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
872 const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);
873 try writeMem(handle, pvaddr, code);
874 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
875 // For that though, we want to track which page has already been modified.
876 _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot);
877}
878
879fn writeMem(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
880 const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code);
881 if (amt != code.len) return error.InputOutput;
882}
883
884fn writeOffsetTableEntry(coff: *Coff, index: usize) !void {
885 const sect_id = coff.got_section_index.?;
886
887 if (coff.got_table_count_dirty) {
888 const needed_size: u32 = @intCast(coff.got_table.entries.items.len * coff.ptr_width.size());
889 try coff.growSection(sect_id, needed_size);
890 coff.got_table_count_dirty = false;
891 }
892
893 const header = &coff.sections.items(.header)[sect_id];
894 const entry = coff.got_table.entries.items[index];
895 const entry_value = coff.getSymbol(entry).value;
896 const entry_offset = index * coff.ptr_width.size();
897 const file_offset = header.pointer_to_raw_data + entry_offset;
898 const vmaddr = header.virtual_address + entry_offset;
899
900 log.debug("writing GOT entry {d}: @{x} => {x}", .{ index, vmaddr, entry_value + coff.image_base });
901
902 switch (coff.ptr_width) {
903 .p32 => {
904 var buf: [4]u8 = undefined;
905 mem.writeInt(u32, &buf, @intCast(entry_value + coff.image_base), .little);
906 try coff.base.file.?.pwriteAll(&buf, file_offset);
907 },
908 .p64 => {
909 var buf: [8]u8 = undefined;
910 mem.writeInt(u64, &buf, entry_value + coff.image_base, .little);
911 try coff.base.file.?.pwriteAll(&buf, file_offset);
912 },
913 }
914
915 if (is_hot_update_compatible) {
916 if (coff.base.child_pid) |handle| {
917 const gpa = coff.base.comp.gpa;
918 const slide = @intFromPtr(coff.hot_state.loaded_base_address.?);
919 const actual_vmaddr = vmaddr + slide;
920 const pvaddr = @as(*anyopaque, @ptrFromInt(actual_vmaddr));
921 log.debug("writing GOT entry to memory at address {x}", .{actual_vmaddr});
922 if (build_options.enable_logging) {
923 switch (coff.ptr_width) {
924 .p32 => {
925 var buf: [4]u8 = undefined;
926 try debugMem(gpa, handle, pvaddr, &buf);
927 },
928 .p64 => {
929 var buf: [8]u8 = undefined;
930 try debugMem(gpa, handle, pvaddr, &buf);
931 },
932 }
933 }
934
935 switch (coff.ptr_width) {
936 .p32 => {
937 var buf: [4]u8 = undefined;
938 mem.writeInt(u32, &buf, @as(u32, @intCast(entry_value + slide)), .little);
939 writeMem(handle, pvaddr, &buf) catch |err| {
940 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
941 };
942 },
943 .p64 => {
944 var buf: [8]u8 = undefined;
945 mem.writeInt(u64, &buf, entry_value + slide, .little);
946 writeMem(handle, pvaddr, &buf) catch |err| {
947 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
948 };
949 },
950 }
951 }
952 }
953}
954
955fn markRelocsDirtyByTarget(coff: *Coff, target: SymbolWithLoc) void {
956 if (!coff.base.comp.config.incremental) return;
957 // TODO: reverse-lookup might come in handy here
958 for (coff.relocs.values()) |*relocs| {
959 for (relocs.items) |*reloc| {
960 if (!reloc.target.eql(target)) continue;
961 reloc.dirty = true;
962 }
963 }
964}
965
966fn markRelocsDirtyByAddress(coff: *Coff, addr: u32) void {
967 if (!coff.base.comp.config.incremental) return;
968 const got_moved = blk: {
969 const sect_id = coff.got_section_index orelse break :blk false;
970 break :blk coff.sections.items(.header)[sect_id].virtual_address >= addr;
971 };
972
973 // TODO: dirty relocations targeting import table if that got moved in memory
974
975 for (coff.relocs.values()) |*relocs| {
976 for (relocs.items) |*reloc| {
977 if (reloc.isGotIndirection()) {
978 reloc.dirty = reloc.dirty or got_moved;
979 } else {
980 const target_vaddr = reloc.getTargetAddress(coff) orelse continue;
981 if (target_vaddr >= addr) reloc.dirty = true;
982 }
983 }
984 }
985
986 // TODO: dirty only really affected GOT cells
987 for (coff.got_table.entries.items) |entry| {
988 const target_addr = coff.getSymbol(entry).value;
989 if (target_addr >= addr) {
990 coff.got_table_contents_dirty = true;
991 break;
992 }
993 }
994}
995
996fn resolveRelocs(coff: *Coff, atom_index: Atom.Index, relocs: []const *const Relocation, code: []u8, image_base: u64) void {
997 log.debug("relocating '{s}'", .{coff.getAtom(atom_index).getName(coff)});
998 for (relocs) |reloc| {
999 reloc.resolve(atom_index, code, image_base, coff);
1000 }
1001}
1002
1003pub fn ptraceAttach(coff: *Coff, handle: std.process.Child.Id) !void {
1004 if (!is_hot_update_compatible) return;
1005
1006 log.debug("attaching to process with handle {*}", .{handle});
1007 coff.hot_state.loaded_base_address = std.os.windows.ProcessBaseAddress(handle) catch |err| {
1008 log.warn("failed to get base address for the process with error: {s}", .{@errorName(err)});
1009 return;
1010 };
1011}
1012
1013pub fn ptraceDetach(coff: *Coff, handle: std.process.Child.Id) void {
1014 if (!is_hot_update_compatible) return;
1015
1016 log.debug("detaching from process with handle {*}", .{handle});
1017 coff.hot_state.loaded_base_address = null;
1018}
1019
1020fn freeAtom(coff: *Coff, atom_index: Atom.Index) void {
1021 log.debug("freeAtom {d}", .{atom_index});
1022
1023 const gpa = coff.base.comp.gpa;
1024
1025 // Remove any relocs and base relocs associated with this Atom
1026 coff.freeRelocations(atom_index);
1027
1028 const atom = coff.getAtom(atom_index);
1029 const sym = atom.getSymbol(coff);
1030 const sect_id = @intFromEnum(sym.section_number) - 1;
1031 const free_list = &coff.sections.items(.free_list)[sect_id];
1032 var already_have_free_list_node = false;
1033 {
1034 var i: usize = 0;
1035 // TODO turn free_list into a hash map
1036 while (i < free_list.items.len) {
1037 if (free_list.items[i] == atom_index) {
1038 _ = free_list.swapRemove(i);
1039 continue;
1040 }
1041 if (free_list.items[i] == atom.prev_index) {
1042 already_have_free_list_node = true;
1043 }
1044 i += 1;
1045 }
1046 }
1047
1048 const maybe_last_atom_index = &coff.sections.items(.last_atom_index)[sect_id];
1049 if (maybe_last_atom_index.*) |last_atom_index| {
1050 if (last_atom_index == atom_index) {
1051 if (atom.prev_index) |prev_index| {
1052 // TODO shrink the section size here
1053 maybe_last_atom_index.* = prev_index;
1054 } else {
1055 maybe_last_atom_index.* = null;
1056 }
1057 }
1058 }
1059
1060 if (atom.prev_index) |prev_index| {
1061 const prev = coff.getAtomPtr(prev_index);
1062 prev.next_index = atom.next_index;
1063
1064 if (!already_have_free_list_node and prev.*.freeListEligible(coff)) {
1065 // The free list is heuristics, it doesn't have to be perfect, so we can
1066 // ignore the OOM here.
1067 free_list.append(gpa, prev_index) catch {};
1068 }
1069 } else {
1070 coff.getAtomPtr(atom_index).prev_index = null;
1071 }
1072
1073 if (atom.next_index) |next_index| {
1074 coff.getAtomPtr(next_index).prev_index = atom.prev_index;
1075 } else {
1076 coff.getAtomPtr(atom_index).next_index = null;
1077 }
1078
1079 // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
1080 const sym_index = atom.getSymbolIndex().?;
1081 coff.locals_free_list.append(gpa, sym_index) catch {};
1082
1083 // Try freeing GOT atom if this decl had one
1084 coff.got_table.freeEntry(gpa, .{ .sym_index = sym_index });
1085
1086 coff.locals.items[sym_index].section_number = .UNDEFINED;
1087 _ = coff.atom_by_index_table.remove(sym_index);
1088 log.debug(" adding local symbol index {d} to free list", .{sym_index});
1089 coff.getAtomPtr(atom_index).sym_index = 0;
1090}
1091
1092pub fn updateFunc(
1093 coff: *Coff,
1094 pt: Zcu.PerThread,
1095 func_index: InternPool.Index,
1096 mir: *const codegen.AnyMir,
1097) link.File.UpdateNavError!void {
1098 if (build_options.skip_non_native and builtin.object_format != .coff) {
1099 @panic("Attempted to compile for object format that was disabled by build configuration");
1100 }
1101 const tracy = trace(@src());
1102 defer tracy.end();
1103
1104 const zcu = pt.zcu;
1105 const gpa = zcu.gpa;
1106 const func = zcu.funcInfo(func_index);
1107 const nav_index = func.owner_nav;
1108
1109 const atom_index = try coff.getOrCreateAtomForNav(nav_index);
1110 coff.freeRelocations(atom_index);
1111
1112 coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?;
1113
1114 var aw: std.Io.Writer.Allocating = .init(gpa);
1115 defer aw.deinit();
1116
1117 codegen.emitFunction(
1118 &coff.base,
1119 pt,
1120 zcu.navSrcLoc(nav_index),
1121 func_index,
1122 coff.getAtom(atom_index).getSymbolIndex().?,
1123 mir,
1124 &aw.writer,
1125 .none,
1126 ) catch |err| switch (err) {
1127 error.WriteFailed => return error.OutOfMemory,
1128 else => |e| return e,
1129 };
1130
1131 try coff.updateNavCode(pt, nav_index, aw.written(), .FUNCTION);
1132
1133 // Exports will be updated by `Zcu.processExports` after the update.
1134}
1135
1136const LowerConstResult = union(enum) {
1137 ok: Atom.Index,
1138 fail: *Zcu.ErrorMsg,
1139};
1140
1141fn lowerConst(
1142 coff: *Coff,
1143 pt: Zcu.PerThread,
1144 name: []const u8,
1145 val: Value,
1146 required_alignment: InternPool.Alignment,
1147 sect_id: u16,
1148 src_loc: Zcu.LazySrcLoc,
1149) !LowerConstResult {
1150 const gpa = coff.base.comp.gpa;
1151
1152 var aw: std.Io.Writer.Allocating = .init(gpa);
1153 defer aw.deinit();
1154
1155 const atom_index = try coff.createAtom();
1156 const sym = coff.getAtom(atom_index).getSymbolPtr(coff);
1157 try coff.setSymbolName(sym, name);
1158 sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_id + 1));
1159
1160 try codegen.generateSymbol(&coff.base, pt, src_loc, val, &aw.writer, .{
1161 .atom_index = coff.getAtom(atom_index).getSymbolIndex().?,
1162 });
1163 const code = aw.written();
1164
1165 const atom = coff.getAtomPtr(atom_index);
1166 atom.size = @intCast(code.len);
1167 atom.getSymbolPtr(coff).value = try coff.allocateAtom(
1168 atom_index,
1169 atom.size,
1170 @intCast(required_alignment.toByteUnits().?),
1171 );
1172 errdefer coff.freeAtom(atom_index);
1173
1174 log.debug("allocated atom for {s} at 0x{x}", .{ name, atom.getSymbol(coff).value });
1175 log.debug(" (required alignment 0x{x})", .{required_alignment});
1176
1177 try coff.writeAtom(atom_index, code, coff.base.comp.config.incremental);
1178
1179 return .{ .ok = atom_index };
1180}
1181
1182pub fn updateNav(
1183 coff: *Coff,
1184 pt: Zcu.PerThread,
1185 nav_index: InternPool.Nav.Index,
1186) link.File.UpdateNavError!void {
1187 if (build_options.skip_non_native and builtin.object_format != .coff) {
1188 @panic("Attempted to compile for object format that was disabled by build configuration");
1189 }
1190 const tracy = trace(@src());
1191 defer tracy.end();
1192
1193 const zcu = pt.zcu;
1194 const gpa = zcu.gpa;
1195 const ip = &zcu.intern_pool;
1196 const nav = ip.getNav(nav_index);
1197
1198 const nav_val = zcu.navValue(nav_index);
1199 const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
1200 .func => return,
1201 .variable => |variable| Value.fromInterned(variable.init),
1202 .@"extern" => |@"extern"| {
1203 if (ip.isFunctionType(@"extern".ty)) return;
1204 // TODO make this part of getGlobalSymbol
1205 const name = nav.name.toSlice(ip);
1206 const lib_name = @"extern".lib_name.toSlice(ip);
1207 const global_index = try coff.getGlobalSymbol(name, lib_name);
1208 try coff.need_got_table.put(gpa, global_index, {});
1209 return;
1210 },
1211 else => nav_val,
1212 };
1213
1214 if (nav_init.typeOf(zcu).hasRuntimeBits(zcu)) {
1215 const atom_index = try coff.getOrCreateAtomForNav(nav_index);
1216 coff.freeRelocations(atom_index);
1217 const atom = coff.getAtom(atom_index);
1218
1219 coff.navs.getPtr(nav_index).?.section = coff.getNavOutputSection(nav_index);
1220
1221 var aw: std.Io.Writer.Allocating = .init(gpa);
1222 defer aw.deinit();
1223
1224 codegen.generateSymbol(
1225 &coff.base,
1226 pt,
1227 zcu.navSrcLoc(nav_index),
1228 nav_init,
1229 &aw.writer,
1230 .{ .atom_index = atom.getSymbolIndex().? },
1231 ) catch |err| switch (err) {
1232 error.WriteFailed => return error.OutOfMemory,
1233 else => |e| return e,
1234 };
1235
1236 try coff.updateNavCode(pt, nav_index, aw.written(), .NULL);
1237 }
1238
1239 // Exports will be updated by `Zcu.processExports` after the update.
1240}
1241
1242fn updateLazySymbolAtom(
1243 coff: *Coff,
1244 pt: Zcu.PerThread,
1245 sym: link.File.LazySymbol,
1246 atom_index: Atom.Index,
1247 section_index: u16,
1248) !void {
1249 const zcu = pt.zcu;
1250 const comp = coff.base.comp;
1251 const gpa = comp.gpa;
1252
1253 var required_alignment: InternPool.Alignment = .none;
1254 var aw: std.Io.Writer.Allocating = .init(gpa);
1255 defer aw.deinit();
1256
1257 const name = try allocPrint(gpa, "__lazy_{s}_{f}", .{
1258 @tagName(sym.kind),
1259 Type.fromInterned(sym.ty).fmt(pt),
1260 });
1261 defer gpa.free(name);
1262
1263 const local_sym_index = coff.getAtomPtr(atom_index).getSymbolIndex().?;
1264
1265 const src = Type.fromInterned(sym.ty).srcLocOrNull(zcu) orelse Zcu.LazySrcLoc.unneeded;
1266 try codegen.generateLazySymbol(
1267 &coff.base,
1268 pt,
1269 src,
1270 sym,
1271 &required_alignment,
1272 &aw.writer,
1273 .none,
1274 .{ .atom_index = local_sym_index },
1275 );
1276 const code = aw.written();
1277
1278 const atom = coff.getAtomPtr(atom_index);
1279 const symbol = atom.getSymbolPtr(coff);
1280 try coff.setSymbolName(symbol, name);
1281 symbol.section_number = @enumFromInt(section_index + 1);
1282 symbol.type = .{ .complex_type = .NULL, .base_type = .NULL };
1283
1284 const code_len: u32 = @intCast(code.len);
1285 const vaddr = try coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0));
1286 errdefer coff.freeAtom(atom_index);
1287
1288 log.debug("allocated atom for {s} at 0x{x}", .{ name, vaddr });
1289 log.debug(" (required alignment 0x{x})", .{required_alignment});
1290
1291 atom.size = code_len;
1292 symbol.value = vaddr;
1293
1294 try coff.addGotEntry(.{ .sym_index = local_sym_index });
1295 try coff.writeAtom(atom_index, code, coff.base.comp.config.incremental);
1296}
1297
1298pub fn getOrCreateAtomForLazySymbol(
1299 coff: *Coff,
1300 pt: Zcu.PerThread,
1301 lazy_sym: link.File.LazySymbol,
1302) !Atom.Index {
1303 const gop = try coff.lazy_syms.getOrPut(pt.zcu.gpa, lazy_sym.ty);
1304 errdefer _ = if (!gop.found_existing) coff.lazy_syms.pop();
1305 if (!gop.found_existing) gop.value_ptr.* = .{};
1306 const atom_ptr, const state_ptr = switch (lazy_sym.kind) {
1307 .code => .{ &gop.value_ptr.text_atom, &gop.value_ptr.text_state },
1308 .const_data => .{ &gop.value_ptr.rdata_atom, &gop.value_ptr.rdata_state },
1309 };
1310 switch (state_ptr.*) {
1311 .unused => atom_ptr.* = try coff.createAtom(),
1312 .pending_flush => return atom_ptr.*,
1313 .flushed => {},
1314 }
1315 state_ptr.* = .pending_flush;
1316 const atom = atom_ptr.*;
1317 // anyerror needs to be deferred until flush
1318 if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) {
1319 .code => coff.text_section_index.?,
1320 .const_data => coff.rdata_section_index.?,
1321 });
1322 return atom;
1323}
1324
1325pub fn getOrCreateAtomForNav(coff: *Coff, nav_index: InternPool.Nav.Index) !Atom.Index {
1326 const gpa = coff.base.comp.gpa;
1327 const gop = try coff.navs.getOrPut(gpa, nav_index);
1328 if (!gop.found_existing) {
1329 gop.value_ptr.* = .{
1330 .atom = try coff.createAtom(),
1331 // If necessary, this will be modified by `updateNav` or `updateFunc`.
1332 .section = coff.rdata_section_index.?,
1333 .exports = .{},
1334 };
1335 }
1336 return gop.value_ptr.atom;
1337}
1338
1339fn getNavOutputSection(coff: *Coff, nav_index: InternPool.Nav.Index) u16 {
1340 const zcu = coff.base.comp.zcu.?;
1341 const ip = &zcu.intern_pool;
1342 const nav = ip.getNav(nav_index);
1343 const ty = Type.fromInterned(nav.typeOf(ip));
1344 const zig_ty = ty.zigTypeTag(zcu);
1345 const val = Value.fromInterned(nav.status.fully_resolved.val);
1346 const index: u16 = blk: {
1347 if (val.isUndef(zcu)) {
1348 // TODO in release-fast and release-small, we should put undef in .bss
1349 break :blk coff.data_section_index.?;
1350 }
1351
1352 switch (zig_ty) {
1353 // TODO: what if this is a function pointer?
1354 .@"fn" => break :blk coff.text_section_index.?,
1355 else => {
1356 if (val.getVariable(zcu)) |_| {
1357 break :blk coff.data_section_index.?;
1358 }
1359 break :blk coff.rdata_section_index.?;
1360 },
1361 }
1362 };
1363 return index;
1364}
1365
1366fn updateNavCode(
1367 coff: *Coff,
1368 pt: Zcu.PerThread,
1369 nav_index: InternPool.Nav.Index,
1370 code: []u8,
1371 complex_type: coff_util.ComplexType,
1372) link.File.UpdateNavError!void {
1373 const zcu = pt.zcu;
1374 const ip = &zcu.intern_pool;
1375 const nav = ip.getNav(nav_index);
1376
1377 log.debug("updateNavCode {f} 0x{x}", .{ nav.fqn.fmt(ip), nav_index });
1378
1379 const mod = zcu.navFileScope(nav_index).mod.?;
1380 const target = &mod.resolved_target.result;
1381 const required_alignment = switch (nav.status.fully_resolved.alignment) {
1382 .none => switch (mod.optimize_mode) {
1383 .Debug, .ReleaseSafe, .ReleaseFast => target_util.defaultFunctionAlignment(target),
1384 .ReleaseSmall => target_util.minFunctionAlignment(target),
1385 },
1386 else => |a| a.maxStrict(target_util.minFunctionAlignment(target)),
1387 };
1388
1389 const nav_metadata = coff.navs.get(nav_index).?;
1390 const atom_index = nav_metadata.atom;
1391 const atom = coff.getAtom(atom_index);
1392 const sym_index = atom.getSymbolIndex().?;
1393 const sect_index = nav_metadata.section;
1394 const code_len: u32 = @intCast(code.len);
1395
1396 if (atom.size != 0) {
1397 const sym = atom.getSymbolPtr(coff);
1398 try coff.setSymbolName(sym, nav.fqn.toSlice(ip));
1399 sym.section_number = @enumFromInt(sect_index + 1);
1400 sym.type = .{ .complex_type = complex_type, .base_type = .NULL };
1401
1402 const capacity = atom.capacity(coff);
1403 const need_realloc = code.len > capacity or !required_alignment.check(sym.value);
1404 if (need_realloc) {
1405 const vaddr = coff.growAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)) catch |err| switch (err) {
1406 error.OutOfMemory => return error.OutOfMemory,
1407 else => |e| return coff.base.cgFail(nav_index, "failed to grow atom: {s}", .{@errorName(e)}),
1408 };
1409 log.debug("growing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), sym.value, vaddr });
1410 log.debug(" (required alignment 0x{x}", .{required_alignment});
1411
1412 if (vaddr != sym.value) {
1413 sym.value = vaddr;
1414 log.debug(" (updating GOT entry)", .{});
1415 const got_entry_index = coff.got_table.lookup.get(.{ .sym_index = sym_index }).?;
1416 coff.writeOffsetTableEntry(got_entry_index) catch |err| switch (err) {
1417 error.OutOfMemory => return error.OutOfMemory,
1418 else => |e| return coff.base.cgFail(nav_index, "failed to write offset table entry: {s}", .{@errorName(e)}),
1419 };
1420 coff.markRelocsDirtyByTarget(.{ .sym_index = sym_index });
1421 }
1422 } else if (code_len < atom.size) {
1423 coff.shrinkAtom(atom_index, code_len);
1424 }
1425 coff.getAtomPtr(atom_index).size = code_len;
1426 } else {
1427 const sym = atom.getSymbolPtr(coff);
1428 try coff.setSymbolName(sym, nav.fqn.toSlice(ip));
1429 sym.section_number = @enumFromInt(sect_index + 1);
1430 sym.type = .{ .complex_type = complex_type, .base_type = .NULL };
1431
1432 const vaddr = coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)) catch |err| switch (err) {
1433 error.OutOfMemory => return error.OutOfMemory,
1434 else => |e| return coff.base.cgFail(nav_index, "failed to allocate atom: {s}", .{@errorName(e)}),
1435 };
1436 errdefer coff.freeAtom(atom_index);
1437 log.debug("allocated atom for {f} at 0x{x}", .{ nav.fqn.fmt(ip), vaddr });
1438 coff.getAtomPtr(atom_index).size = code_len;
1439 sym.value = vaddr;
1440
1441 coff.addGotEntry(.{ .sym_index = sym_index }) catch |err| switch (err) {
1442 error.OutOfMemory => return error.OutOfMemory,
1443 else => |e| return coff.base.cgFail(nav_index, "failed to add GOT entry: {s}", .{@errorName(e)}),
1444 };
1445 }
1446
1447 coff.writeAtom(atom_index, code, coff.base.comp.config.incremental) catch |err| switch (err) {
1448 error.OutOfMemory => return error.OutOfMemory,
1449 else => |e| return coff.base.cgFail(nav_index, "failed to write atom: {s}", .{@errorName(e)}),
1450 };
1451}
1452
1453pub fn freeNav(coff: *Coff, nav_index: InternPool.NavIndex) void {
1454 const gpa = coff.base.comp.gpa;
1455
1456 if (coff.decls.fetchOrderedRemove(nav_index)) |const_kv| {
1457 var kv = const_kv;
1458 coff.freeAtom(kv.value.atom);
1459 kv.value.exports.deinit(gpa);
1460 }
1461}
1462
1463pub fn updateExports(
1464 coff: *Coff,
1465 pt: Zcu.PerThread,
1466 exported: Zcu.Exported,
1467 export_indices: []const Zcu.Export.Index,
1468) link.File.UpdateExportsError!void {
1469 if (build_options.skip_non_native and builtin.object_format != .coff) {
1470 @panic("Attempted to compile for object format that was disabled by build configuration");
1471 }
1472
1473 const zcu = pt.zcu;
1474 const gpa = zcu.gpa;
1475
1476 const metadata = switch (exported) {
1477 .nav => |nav| blk: {
1478 _ = try coff.getOrCreateAtomForNav(nav);
1479 break :blk coff.navs.getPtr(nav).?;
1480 },
1481 .uav => |uav| coff.uavs.getPtr(uav) orelse blk: {
1482 const first_exp = export_indices[0].ptr(zcu);
1483 const res = try coff.lowerUav(pt, uav, .none, first_exp.src);
1484 switch (res) {
1485 .sym_index => {},
1486 .fail => |em| {
1487 // TODO maybe it's enough to return an error here and let Module.processExportsInner
1488 // handle the error?
1489 try zcu.failed_exports.ensureUnusedCapacity(zcu.gpa, 1);
1490 zcu.failed_exports.putAssumeCapacityNoClobber(export_indices[0], em);
1491 return;
1492 },
1493 }
1494 break :blk coff.uavs.getPtr(uav).?;
1495 },
1496 };
1497 const atom_index = metadata.atom;
1498 const atom = coff.getAtom(atom_index);
1499
1500 for (export_indices) |export_idx| {
1501 const exp = export_idx.ptr(zcu);
1502 log.debug("adding new export '{f}'", .{exp.opts.name.fmt(&zcu.intern_pool)});
1503
1504 if (exp.opts.section.toSlice(&zcu.intern_pool)) |section_name| {
1505 if (!mem.eql(u8, section_name, ".text")) {
1506 try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create(
1507 gpa,
1508 exp.src,
1509 "Unimplemented: ExportOptions.section",
1510 .{},
1511 ));
1512 continue;
1513 }
1514 }
1515
1516 if (exp.opts.linkage == .link_once) {
1517 try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create(
1518 gpa,
1519 exp.src,
1520 "Unimplemented: GlobalLinkage.link_once",
1521 .{},
1522 ));
1523 continue;
1524 }
1525
1526 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
1527 const sym_index = metadata.getExport(coff, exp_name) orelse blk: {
1528 const sym_index = if (coff.getGlobalIndex(exp_name)) |global_index| ind: {
1529 const global = coff.globals.items[global_index];
1530 // TODO this is just plain wrong as it all should happen in a single `resolveSymbols`
1531 // pass. This will go away once we abstact away Zig's incremental compilation into
1532 // its own module.
1533 if (global.file == null and coff.getSymbol(global).section_number == .UNDEFINED) {
1534 _ = coff.unresolved.swapRemove(global_index);
1535 break :ind global.sym_index;
1536 }
1537 break :ind try coff.allocateSymbol();
1538 } else try coff.allocateSymbol();
1539 try metadata.exports.append(gpa, sym_index);
1540 break :blk sym_index;
1541 };
1542 const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
1543 const sym = coff.getSymbolPtr(sym_loc);
1544 try coff.setSymbolName(sym, exp_name);
1545 sym.value = atom.getSymbol(coff).value;
1546 sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(metadata.section + 1));
1547 sym.type = atom.getSymbol(coff).type;
1548
1549 sym.storage_class = switch (exp.opts.linkage) {
1550 .internal => .EXTERNAL,
1551 .strong => .EXTERNAL,
1552 .weak => @panic("TODO WeakExternal"),
1553 else => unreachable,
1554 };
1555
1556 try coff.resolveGlobalSymbol(sym_loc);
1557 }
1558}
1559
1560pub fn deleteExport(
1561 coff: *Coff,
1562 exported: Zcu.Exported,
1563 name: InternPool.NullTerminatedString,
1564) void {
1565 const metadata = switch (exported) {
1566 .nav => |nav| coff.navs.getPtr(nav),
1567 .uav => |uav| coff.uavs.getPtr(uav),
1568 } orelse return;
1569 const zcu = coff.base.comp.zcu.?;
1570 const name_slice = name.toSlice(&zcu.intern_pool);
1571 const sym_index = metadata.getExportPtr(coff, name_slice) orelse return;
1572
1573 const gpa = coff.base.comp.gpa;
1574 const sym_loc = SymbolWithLoc{ .sym_index = sym_index.*, .file = null };
1575 const sym = coff.getSymbolPtr(sym_loc);
1576 log.debug("deleting export '{f}'", .{name.fmt(&zcu.intern_pool)});
1577 assert(sym.storage_class == .EXTERNAL and sym.section_number != .UNDEFINED);
1578 sym.* = .{
1579 .name = [_]u8{0} ** 8,
1580 .value = 0,
1581 .section_number = .UNDEFINED,
1582 .type = .{ .base_type = .NULL, .complex_type = .NULL },
1583 .storage_class = .NULL,
1584 .number_of_aux_symbols = 0,
1585 };
1586 coff.locals_free_list.append(gpa, sym_index.*) catch {};
1587
1588 if (coff.resolver.fetchRemove(name_slice)) |entry| {
1589 defer gpa.free(entry.key);
1590 coff.globals_free_list.append(gpa, entry.value) catch {};
1591 coff.globals.items[entry.value] = .{
1592 .sym_index = 0,
1593 .file = null,
1594 };
1595 }
1596
1597 sym_index.* = 0;
1598}
1599
1600fn resolveGlobalSymbol(coff: *Coff, current: SymbolWithLoc) !void {
1601 const gpa = coff.base.comp.gpa;
1602 const sym = coff.getSymbol(current);
1603 const sym_name = coff.getSymbolName(current);
1604
1605 const gop = try coff.getOrPutGlobalPtr(sym_name);
1606 if (!gop.found_existing) {
1607 gop.value_ptr.* = current;
1608 if (sym.section_number == .UNDEFINED) {
1609 try coff.unresolved.putNoClobber(gpa, coff.getGlobalIndex(sym_name).?, false);
1610 }
1611 return;
1612 }
1613
1614 log.debug("TODO finish resolveGlobalSymbols implementation", .{});
1615
1616 if (sym.section_number == .UNDEFINED) return;
1617
1618 _ = coff.unresolved.swapRemove(coff.getGlobalIndex(sym_name).?);
1619
1620 gop.value_ptr.* = current;
1621}
1622
1623pub fn flush(
1624 coff: *Coff,
1625 arena: Allocator,
1626 tid: Zcu.PerThread.Id,
1627 prog_node: std.Progress.Node,
1628) link.File.FlushError!void {
1629 const tracy = trace(@src());
1630 defer tracy.end();
1631
1632 const comp = coff.base.comp;
1633 const diags = &comp.link_diags;
1634
1635 switch (coff.base.comp.config.output_mode) {
1636 .Exe, .Obj => {},
1637 .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}),
1638 }
1639
1640 const sub_prog_node = prog_node.start("COFF Flush", 0);
1641 defer sub_prog_node.end();
1642
1643 return flushInner(coff, arena, tid) catch |err| switch (err) {
1644 error.OutOfMemory => return error.OutOfMemory,
1645 error.LinkFailure => return error.LinkFailure,
1646 else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}),
1647 };
1648}
1649
1650fn flushInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void {
1651 _ = arena;
1652
1653 const comp = coff.base.comp;
1654 const gpa = comp.gpa;
1655 const diags = &comp.link_diags;
1656
1657 const pt: Zcu.PerThread = .activate(
1658 comp.zcu orelse return diags.fail("linking without zig source is not yet implemented", .{}),
1659 tid,
1660 );
1661 defer pt.deactivate();
1662
1663 if (coff.lazy_syms.getPtr(.anyerror_type)) |metadata| {
1664 // Most lazy symbols can be updated on first use, but
1665 // anyerror needs to wait for everything to be flushed.
1666 if (metadata.text_state != .unused) try coff.updateLazySymbolAtom(
1667 pt,
1668 .{ .kind = .code, .ty = .anyerror_type },
1669 metadata.text_atom,
1670 coff.text_section_index.?,
1671 );
1672 if (metadata.rdata_state != .unused) try coff.updateLazySymbolAtom(
1673 pt,
1674 .{ .kind = .const_data, .ty = .anyerror_type },
1675 metadata.rdata_atom,
1676 coff.rdata_section_index.?,
1677 );
1678 }
1679 for (coff.lazy_syms.values()) |*metadata| {
1680 if (metadata.text_state != .unused) metadata.text_state = .flushed;
1681 if (metadata.rdata_state != .unused) metadata.rdata_state = .flushed;
1682 }
1683
1684 {
1685 var it = coff.need_got_table.iterator();
1686 while (it.next()) |entry| {
1687 const global = coff.globals.items[entry.key_ptr.*];
1688 try coff.addGotEntry(global);
1689 }
1690 }
1691
1692 while (coff.unresolved.pop()) |entry| {
1693 assert(entry.value);
1694 const global = coff.globals.items[entry.key];
1695 const sym = coff.getSymbol(global);
1696 const res = try coff.import_tables.getOrPut(gpa, sym.value);
1697 const itable = res.value_ptr;
1698 if (!res.found_existing) {
1699 itable.* = .{};
1700 }
1701 if (itable.lookup.contains(global)) continue;
1702 // TODO: we could technically write the pointer placeholder for to-be-bound import here,
1703 // but since this happens in flush, there is currently no point.
1704 _ = try itable.addImport(gpa, global);
1705 coff.imports_count_dirty = true;
1706 }
1707
1708 try coff.writeImportTables();
1709
1710 for (coff.relocs.keys(), coff.relocs.values()) |atom_index, relocs| {
1711 const needs_update = for (relocs.items) |reloc| {
1712 if (reloc.dirty) break true;
1713 } else false;
1714
1715 if (!needs_update) continue;
1716
1717 const atom = coff.getAtom(atom_index);
1718 const sym = atom.getSymbol(coff);
1719 const section = coff.sections.get(@intFromEnum(sym.section_number) - 1).header;
1720 const file_offset = section.pointer_to_raw_data + sym.value - section.virtual_address;
1721
1722 var code = std.array_list.Managed(u8).init(gpa);
1723 defer code.deinit();
1724 try code.resize(math.cast(usize, atom.size) orelse return error.Overflow);
1725 assert(atom.size > 0);
1726
1727 const amt = try coff.base.file.?.preadAll(code.items, file_offset);
1728 if (amt != code.items.len) return error.InputOutput;
1729
1730 try coff.writeAtom(atom_index, code.items, true);
1731 }
1732
1733 // Update GOT if it got moved in memory.
1734 if (coff.got_table_contents_dirty) {
1735 for (coff.got_table.entries.items, 0..) |entry, i| {
1736 if (!coff.got_table.lookup.contains(entry)) continue;
1737 // TODO: write all in one go rather than incrementally.
1738 try coff.writeOffsetTableEntry(i);
1739 }
1740 coff.got_table_contents_dirty = false;
1741 }
1742
1743 try coff.writeBaseRelocations();
1744
1745 if (coff.getEntryPoint()) |entry_sym_loc| {
1746 coff.entry_addr = coff.getSymbol(entry_sym_loc).value;
1747 }
1748
1749 if (build_options.enable_logging) {
1750 coff.logSymtab();
1751 coff.logImportTables();
1752 }
1753
1754 try coff.writeStrtab();
1755 try coff.writeDataDirectoriesHeaders();
1756 try coff.writeSectionHeaders();
1757
1758 if (coff.entry_addr == null and comp.config.output_mode == .Exe) {
1759 log.debug("flushing. no_entry_point_found = true\n", .{});
1760 diags.flags.no_entry_point_found = true;
1761 } else {
1762 log.debug("flushing. no_entry_point_found = false\n", .{});
1763 diags.flags.no_entry_point_found = false;
1764 try coff.writeHeader();
1765 }
1766
1767 assert(!coff.imports_count_dirty);
1768
1769 // hack for stage2_x86_64 + coff
1770 if (comp.compiler_rt_dyn_lib) |crt_file| {
1771 const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{
1772 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
1773 std.fs.path.basename(crt_file.full_object_path.sub_path),
1774 });
1775 defer gpa.free(compiler_rt_sub_path);
1776 try crt_file.full_object_path.root_dir.handle.copyFile(
1777 crt_file.full_object_path.sub_path,
1778 coff.base.emit.root_dir.handle,
1779 compiler_rt_sub_path,
1780 .{},
1781 );
1782 }
1783}
1784
1785pub fn getNavVAddr(
1786 coff: *Coff,
1787 pt: Zcu.PerThread,
1788 nav_index: InternPool.Nav.Index,
1789 reloc_info: link.File.RelocInfo,
1790) !u64 {
1791 const zcu = pt.zcu;
1792 const ip = &zcu.intern_pool;
1793 const nav = ip.getNav(nav_index);
1794 log.debug("getNavVAddr {f}({d})", .{ nav.fqn.fmt(ip), nav_index });
1795 const sym_index = if (nav.getExtern(ip)) |e|
1796 try coff.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip))
1797 else
1798 coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?;
1799 const atom_index = coff.getAtomIndexForSymbol(.{
1800 .sym_index = reloc_info.parent.atom_index,
1801 .file = null,
1802 }).?;
1803 const target = SymbolWithLoc{ .sym_index = sym_index, .file = null };
1804 try coff.addRelocation(atom_index, .{
1805 .type = .direct,
1806 .target = target,
1807 .offset = @as(u32, @intCast(reloc_info.offset)),
1808 .addend = reloc_info.addend,
1809 .pcrel = false,
1810 .length = 3,
1811 });
1812 try coff.addBaseRelocation(atom_index, @as(u32, @intCast(reloc_info.offset)));
1813
1814 return 0;
1815}
1816
1817pub fn lowerUav(
1818 coff: *Coff,
1819 pt: Zcu.PerThread,
1820 uav: InternPool.Index,
1821 explicit_alignment: InternPool.Alignment,
1822 src_loc: Zcu.LazySrcLoc,
1823) !codegen.SymbolResult {
1824 const zcu = pt.zcu;
1825 const gpa = zcu.gpa;
1826 const val = Value.fromInterned(uav);
1827 const uav_alignment = switch (explicit_alignment) {
1828 .none => val.typeOf(zcu).abiAlignment(zcu),
1829 else => explicit_alignment,
1830 };
1831 if (coff.uavs.get(uav)) |metadata| {
1832 const atom = coff.getAtom(metadata.atom);
1833 const existing_addr = atom.getSymbol(coff).value;
1834 if (uav_alignment.check(existing_addr))
1835 return .{ .sym_index = atom.getSymbolIndex().? };
1836 }
1837
1838 var name_buf: [32]u8 = undefined;
1839 const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
1840 @intFromEnum(uav),
1841 }) catch unreachable;
1842 const res = coff.lowerConst(
1843 pt,
1844 name,
1845 val,
1846 uav_alignment,
1847 coff.rdata_section_index.?,
1848 src_loc,
1849 ) catch |err| switch (err) {
1850 error.OutOfMemory => return error.OutOfMemory,
1851 else => |e| return .{ .fail = try Zcu.ErrorMsg.create(
1852 gpa,
1853 src_loc,
1854 "lowerAnonDecl failed with error: {s}",
1855 .{@errorName(e)},
1856 ) },
1857 };
1858 const atom_index = switch (res) {
1859 .ok => |atom_index| atom_index,
1860 .fail => |em| return .{ .fail = em },
1861 };
1862 try coff.uavs.put(gpa, uav, .{
1863 .atom = atom_index,
1864 .section = coff.rdata_section_index.?,
1865 });
1866 return .{ .sym_index = coff.getAtom(atom_index).getSymbolIndex().? };
1867}
1868
1869pub fn getUavVAddr(
1870 coff: *Coff,
1871 uav: InternPool.Index,
1872 reloc_info: link.File.RelocInfo,
1873) !u64 {
1874 const this_atom_index = coff.uavs.get(uav).?.atom;
1875 const sym_index = coff.getAtom(this_atom_index).getSymbolIndex().?;
1876 const atom_index = coff.getAtomIndexForSymbol(.{
1877 .sym_index = reloc_info.parent.atom_index,
1878 .file = null,
1879 }).?;
1880 const target = SymbolWithLoc{ .sym_index = sym_index, .file = null };
1881 try coff.addRelocation(atom_index, .{
1882 .type = .direct,
1883 .target = target,
1884 .offset = @as(u32, @intCast(reloc_info.offset)),
1885 .addend = reloc_info.addend,
1886 .pcrel = false,
1887 .length = 3,
1888 });
1889 try coff.addBaseRelocation(atom_index, @as(u32, @intCast(reloc_info.offset)));
1890
1891 return 0;
1892}
1893
1894pub fn getGlobalSymbol(coff: *Coff, name: []const u8, lib_name_name: ?[]const u8) !u32 {
1895 const gop = try coff.getOrPutGlobalPtr(name);
1896 const global_index = coff.getGlobalIndex(name).?;
1897
1898 if (gop.found_existing) {
1899 return global_index;
1900 }
1901
1902 const sym_index = try coff.allocateSymbol();
1903 const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
1904 gop.value_ptr.* = sym_loc;
1905
1906 const gpa = coff.base.comp.gpa;
1907 const sym = coff.getSymbolPtr(sym_loc);
1908 try coff.setSymbolName(sym, name);
1909 sym.storage_class = .EXTERNAL;
1910
1911 if (lib_name_name) |lib_name| {
1912 // We repurpose the 'value' of the Symbol struct to store an offset into
1913 // temporary string table where we will store the library name hint.
1914 sym.value = try coff.temp_strtab.insert(gpa, lib_name);
1915 }
1916
1917 try coff.unresolved.putNoClobber(gpa, global_index, true);
1918
1919 return global_index;
1920}
1921
1922pub fn updateLineNumber(coff: *Coff, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
1923 _ = coff;
1924 _ = pt;
1925 _ = ti_id;
1926 log.debug("TODO implement updateLineNumber", .{});
1927}
1928
1929/// TODO: note if we need to rewrite base relocations by dirtying any of the entries in the global table
1930/// TODO: note that .ABSOLUTE is used as padding within each block; we could use this fact to do
1931/// incremental updates and writes into the table instead of doing it all at once
1932fn writeBaseRelocations(coff: *Coff) !void {
1933 const gpa = coff.base.comp.gpa;
1934
1935 var page_table = std.AutoHashMap(u32, std.array_list.Managed(coff_util.BaseRelocation)).init(gpa);
1936 defer {
1937 var it = page_table.valueIterator();
1938 while (it.next()) |inner| {
1939 inner.deinit();
1940 }
1941 page_table.deinit();
1942 }
1943
1944 {
1945 var it = coff.base_relocs.iterator();
1946 while (it.next()) |entry| {
1947 const atom_index = entry.key_ptr.*;
1948 const atom = coff.getAtom(atom_index);
1949 const sym = atom.getSymbol(coff);
1950 const offsets = entry.value_ptr.*;
1951
1952 for (offsets.items) |offset| {
1953 const rva = sym.value + offset;
1954 const page = mem.alignBackward(u32, rva, coff.page_size);
1955 const gop = try page_table.getOrPut(page);
1956 if (!gop.found_existing) {
1957 gop.value_ptr.* = std.array_list.Managed(coff_util.BaseRelocation).init(gpa);
1958 }
1959 try gop.value_ptr.append(.{
1960 .offset = @as(u12, @intCast(rva - page)),
1961 .type = .DIR64,
1962 });
1963 }
1964 }
1965
1966 {
1967 const header = &coff.sections.items(.header)[coff.got_section_index.?];
1968 for (coff.got_table.entries.items, 0..) |entry, index| {
1969 if (!coff.got_table.lookup.contains(entry)) continue;
1970
1971 const sym = coff.getSymbol(entry);
1972 if (sym.section_number == .UNDEFINED) continue;
1973
1974 const rva = @as(u32, @intCast(header.virtual_address + index * coff.ptr_width.size()));
1975 const page = mem.alignBackward(u32, rva, coff.page_size);
1976 const gop = try page_table.getOrPut(page);
1977 if (!gop.found_existing) {
1978 gop.value_ptr.* = std.array_list.Managed(coff_util.BaseRelocation).init(gpa);
1979 }
1980 try gop.value_ptr.append(.{
1981 .offset = @as(u12, @intCast(rva - page)),
1982 .type = .DIR64,
1983 });
1984 }
1985 }
1986 }
1987
1988 // Sort pages by address.
1989 var pages = try std.array_list.Managed(u32).initCapacity(gpa, page_table.count());
1990 defer pages.deinit();
1991 {
1992 var it = page_table.keyIterator();
1993 while (it.next()) |page| {
1994 pages.appendAssumeCapacity(page.*);
1995 }
1996 }
1997 mem.sort(u32, pages.items, {}, std.sort.asc(u32));
1998
1999 var buffer = std.array_list.Managed(u8).init(gpa);
2000 defer buffer.deinit();
2001
2002 for (pages.items) |page| {
2003 const entries = page_table.getPtr(page).?;
2004 // Pad to required 4byte alignment
2005 if (!mem.isAlignedGeneric(
2006 usize,
2007 entries.items.len * @sizeOf(coff_util.BaseRelocation),
2008 @sizeOf(u32),
2009 )) {
2010 try entries.append(.{
2011 .offset = 0,
2012 .type = .ABSOLUTE,
2013 });
2014 }
2015
2016 const block_size = @as(
2017 u32,
2018 @intCast(entries.items.len * @sizeOf(coff_util.BaseRelocation) + @sizeOf(coff_util.BaseRelocationDirectoryEntry)),
2019 );
2020 try buffer.ensureUnusedCapacity(block_size);
2021 buffer.appendSliceAssumeCapacity(mem.asBytes(&coff_util.BaseRelocationDirectoryEntry{
2022 .page_rva = page,
2023 .block_size = block_size,
2024 }));
2025 buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(entries.items));
2026 }
2027
2028 const header = &coff.sections.items(.header)[coff.reloc_section_index.?];
2029 const needed_size = @as(u32, @intCast(buffer.items.len));
2030 try coff.growSection(coff.reloc_section_index.?, needed_size);
2031
2032 try coff.pwriteAll(buffer.items, header.pointer_to_raw_data);
2033
2034 coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.BASERELOC)] = .{
2035 .virtual_address = header.virtual_address,
2036 .size = needed_size,
2037 };
2038}
2039
2040fn writeImportTables(coff: *Coff) !void {
2041 if (coff.idata_section_index == null) return;
2042 if (!coff.imports_count_dirty) return;
2043
2044 const gpa = coff.base.comp.gpa;
2045
2046 const ext = ".dll";
2047 const header = &coff.sections.items(.header)[coff.idata_section_index.?];
2048
2049 // Calculate needed size
2050 var iat_size: u32 = 0;
2051 var dir_table_size: u32 = @sizeOf(coff_util.ImportDirectoryEntry); // sentinel
2052 var lookup_table_size: u32 = 0;
2053 var names_table_size: u32 = 0;
2054 var dll_names_size: u32 = 0;
2055 for (coff.import_tables.keys(), 0..) |off, i| {
2056 const lib_name = coff.temp_strtab.getAssumeExists(off);
2057 const itable = coff.import_tables.values()[i];
2058 iat_size += itable.size() + 8;
2059 dir_table_size += @sizeOf(coff_util.ImportDirectoryEntry);
2060 lookup_table_size += @as(u32, @intCast(itable.entries.items.len + 1)) * @sizeOf(coff_util.ImportLookupEntry64.ByName);
2061 for (itable.entries.items) |entry| {
2062 const sym_name = coff.getSymbolName(entry);
2063 names_table_size += 2 + mem.alignForward(u32, @as(u32, @intCast(sym_name.len + 1)), 2);
2064 }
2065 dll_names_size += @as(u32, @intCast(lib_name.len + ext.len + 1));
2066 }
2067
2068 const needed_size = iat_size + dir_table_size + lookup_table_size + names_table_size + dll_names_size;
2069 try coff.growSection(coff.idata_section_index.?, needed_size);
2070
2071 // Do the actual writes
2072 var buffer = std.array_list.Managed(u8).init(gpa);
2073 defer buffer.deinit();
2074 try buffer.ensureTotalCapacityPrecise(needed_size);
2075 buffer.resize(needed_size) catch unreachable;
2076
2077 const dir_header_size = @sizeOf(coff_util.ImportDirectoryEntry);
2078 const lookup_entry_size = @sizeOf(coff_util.ImportLookupEntry64.ByName);
2079
2080 var iat_offset: u32 = 0;
2081 var dir_table_offset = iat_size;
2082 var lookup_table_offset = dir_table_offset + dir_table_size;
2083 var names_table_offset = lookup_table_offset + lookup_table_size;
2084 var dll_names_offset = names_table_offset + names_table_size;
2085 for (coff.import_tables.keys(), 0..) |off, i| {
2086 const lib_name = coff.temp_strtab.getAssumeExists(off);
2087 const itable = coff.import_tables.values()[i];
2088
2089 // Lookup table header
2090 const lookup_header = coff_util.ImportDirectoryEntry{
2091 .import_lookup_table_rva = header.virtual_address + lookup_table_offset,
2092 .time_date_stamp = 0,
2093 .forwarder_chain = 0,
2094 .name_rva = header.virtual_address + dll_names_offset,
2095 .import_address_table_rva = header.virtual_address + iat_offset,
2096 };
2097 @memcpy(buffer.items[dir_table_offset..][0..@sizeOf(coff_util.ImportDirectoryEntry)], mem.asBytes(&lookup_header));
2098 dir_table_offset += dir_header_size;
2099
2100 for (itable.entries.items) |entry| {
2101 const import_name = coff.getSymbolName(entry);
2102
2103 // IAT and lookup table entry
2104 const lookup = coff_util.ImportLookupEntry64.ByName{ .name_table_rva = @as(u31, @intCast(header.virtual_address + names_table_offset)) };
2105 @memcpy(
2106 buffer.items[iat_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)],
2107 mem.asBytes(&lookup),
2108 );
2109 iat_offset += lookup_entry_size;
2110 @memcpy(
2111 buffer.items[lookup_table_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)],
2112 mem.asBytes(&lookup),
2113 );
2114 lookup_table_offset += lookup_entry_size;
2115
2116 // Names table entry
2117 mem.writeInt(u16, buffer.items[names_table_offset..][0..2], 0, .little); // Hint set to 0 until we learn how to parse DLLs
2118 names_table_offset += 2;
2119 @memcpy(buffer.items[names_table_offset..][0..import_name.len], import_name);
2120 names_table_offset += @as(u32, @intCast(import_name.len));
2121 buffer.items[names_table_offset] = 0;
2122 names_table_offset += 1;
2123 if (!mem.isAlignedGeneric(usize, names_table_offset, @sizeOf(u16))) {
2124 buffer.items[names_table_offset] = 0;
2125 names_table_offset += 1;
2126 }
2127 }
2128
2129 // IAT sentinel
2130 mem.writeInt(u64, buffer.items[iat_offset..][0..lookup_entry_size], 0, .little);
2131 iat_offset += 8;
2132
2133 // Lookup table sentinel
2134 @memcpy(
2135 buffer.items[lookup_table_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)],
2136 mem.asBytes(&coff_util.ImportLookupEntry64.ByName{ .name_table_rva = 0 }),
2137 );
2138 lookup_table_offset += lookup_entry_size;
2139
2140 // DLL name
2141 @memcpy(buffer.items[dll_names_offset..][0..lib_name.len], lib_name);
2142 dll_names_offset += @as(u32, @intCast(lib_name.len));
2143 @memcpy(buffer.items[dll_names_offset..][0..ext.len], ext);
2144 dll_names_offset += @as(u32, @intCast(ext.len));
2145 buffer.items[dll_names_offset] = 0;
2146 dll_names_offset += 1;
2147 }
2148
2149 // Sentinel
2150 const lookup_header = coff_util.ImportDirectoryEntry{
2151 .import_lookup_table_rva = 0,
2152 .time_date_stamp = 0,
2153 .forwarder_chain = 0,
2154 .name_rva = 0,
2155 .import_address_table_rva = 0,
2156 };
2157 @memcpy(
2158 buffer.items[dir_table_offset..][0..@sizeOf(coff_util.ImportDirectoryEntry)],
2159 mem.asBytes(&lookup_header),
2160 );
2161 dir_table_offset += dir_header_size;
2162
2163 assert(dll_names_offset == needed_size);
2164
2165 try coff.pwriteAll(buffer.items, header.pointer_to_raw_data);
2166
2167 coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IMPORT)] = .{
2168 .virtual_address = header.virtual_address + iat_size,
2169 .size = dir_table_size,
2170 };
2171 coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IAT)] = .{
2172 .virtual_address = header.virtual_address,
2173 .size = iat_size,
2174 };
2175
2176 coff.imports_count_dirty = false;
2177}
2178
2179fn writeStrtab(coff: *Coff) !void {
2180 if (coff.strtab_offset == null) return;
2181
2182 const comp = coff.base.comp;
2183 const gpa = comp.gpa;
2184 const diags = &comp.link_diags;
2185 const allocated_size = coff.allocatedSize(coff.strtab_offset.?);
2186 const needed_size: u32 = @intCast(coff.strtab.buffer.items.len);
2187
2188 if (needed_size > allocated_size) {
2189 coff.strtab_offset = null;
2190 coff.strtab_offset = @intCast(coff.findFreeSpace(needed_size, @alignOf(u32)));
2191 }
2192
2193 log.debug("writing strtab from 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + needed_size });
2194
2195 var buffer = std.array_list.Managed(u8).init(gpa);
2196 defer buffer.deinit();
2197 try buffer.ensureTotalCapacityPrecise(needed_size);
2198 buffer.appendSliceAssumeCapacity(coff.strtab.buffer.items);
2199 // Here, we do a trick in that we do not commit the size of the strtab to strtab buffer, instead
2200 // we write the length of the strtab to a temporary buffer that goes to file.
2201 mem.writeInt(u32, buffer.items[0..4], @as(u32, @intCast(coff.strtab.buffer.items.len)), .little);
2202
2203 coff.pwriteAll(buffer.items, coff.strtab_offset.?) catch |err| {
2204 return diags.fail("failed to write: {s}", .{@errorName(err)});
2205 };
2206}
2207
2208fn writeSectionHeaders(coff: *Coff) !void {
2209 const offset = coff.getSectionHeadersOffset();
2210 try coff.pwriteAll(@ptrCast(coff.sections.items(.header)), offset);
2211}
2212
2213fn writeDataDirectoriesHeaders(coff: *Coff) !void {
2214 const offset = coff.getDataDirectoryHeadersOffset();
2215 try coff.pwriteAll(@ptrCast(&coff.data_directories), offset);
2216}
2217
2218fn writeHeader(coff: *Coff) !void {
2219 const target = &coff.base.comp.root_mod.resolved_target.result;
2220 const gpa = coff.base.comp.gpa;
2221 var buffer: std.Io.Writer.Allocating = .init(gpa);
2222 defer buffer.deinit();
2223 const writer = &buffer.writer;
2224
2225 try buffer.ensureTotalCapacity(coff.getSizeOfHeaders());
2226 writer.writeAll(&msdos_stub) catch unreachable;
2227 mem.writeInt(u32, buffer.writer.buffer[0x3c..][0..4], msdos_stub.len, .little);
2228
2229 writer.writeAll("PE\x00\x00") catch unreachable;
2230 var flags: coff_util.Header.Flags = .{
2231 .EXECUTABLE_IMAGE = true,
2232 .DEBUG_STRIPPED = true, // TODO
2233 };
2234 switch (coff.ptr_width) {
2235 .p32 => flags.@"32BIT_MACHINE" = true,
2236 .p64 => flags.LARGE_ADDRESS_AWARE = true,
2237 }
2238 if (coff.base.comp.config.output_mode == .Lib and coff.base.comp.config.link_mode == .dynamic) {
2239 flags.DLL = true;
2240 }
2241
2242 const timestamp = if (coff.repro) 0 else std.time.timestamp();
2243 const size_of_optional_header = @as(u16, @intCast(coff.getOptionalHeaderSize() + coff.getDataDirectoryHeadersSize()));
2244 var coff_header: coff_util.Header = .{
2245 .machine = target.toCoffMachine(),
2246 .number_of_sections = @as(u16, @intCast(coff.sections.slice().len)), // TODO what if we prune a section
2247 .time_date_stamp = @as(u32, @truncate(@as(u64, @bitCast(timestamp)))),
2248 .pointer_to_symbol_table = coff.strtab_offset orelse 0,
2249 .number_of_symbols = 0,
2250 .size_of_optional_header = size_of_optional_header,
2251 .flags = flags,
2252 };
2253
2254 writer.writeAll(mem.asBytes(&coff_header)) catch unreachable;
2255
2256 const dll_flags: coff_util.DllFlags = .{
2257 .HIGH_ENTROPY_VA = true, // TODO do we want to permit non-PIE builds at all?
2258 .DYNAMIC_BASE = true,
2259 .TERMINAL_SERVER_AWARE = true, // We are not a legacy app
2260 .NX_COMPAT = true, // We are compatible with Data Execution Prevention
2261 };
2262 const subsystem: coff_util.Subsystem = .WINDOWS_CUI;
2263 const size_of_image: u32 = coff.getSizeOfImage();
2264 const size_of_headers: u32 = mem.alignForward(u32, coff.getSizeOfHeaders(), default_file_alignment);
2265 const base_of_code = coff.sections.get(coff.text_section_index.?).header.virtual_address;
2266 const base_of_data = coff.sections.get(coff.data_section_index.?).header.virtual_address;
2267
2268 var size_of_code: u32 = 0;
2269 var size_of_initialized_data: u32 = 0;
2270 var size_of_uninitialized_data: u32 = 0;
2271 for (coff.sections.items(.header)) |header| {
2272 if (header.flags.CNT_CODE) {
2273 size_of_code += header.size_of_raw_data;
2274 }
2275 if (header.flags.CNT_INITIALIZED_DATA) {
2276 size_of_initialized_data += header.size_of_raw_data;
2277 }
2278 if (header.flags.CNT_UNINITIALIZED_DATA) {
2279 size_of_uninitialized_data += header.size_of_raw_data;
2280 }
2281 }
2282
2283 switch (coff.ptr_width) {
2284 .p32 => {
2285 var opt_header = coff_util.OptionalHeaderPE32{
2286 .magic = .PE32,
2287 .major_linker_version = 0,
2288 .minor_linker_version = 0,
2289 .size_of_code = size_of_code,
2290 .size_of_initialized_data = size_of_initialized_data,
2291 .size_of_uninitialized_data = size_of_uninitialized_data,
2292 .address_of_entry_point = coff.entry_addr orelse 0,
2293 .base_of_code = base_of_code,
2294 .base_of_data = base_of_data,
2295 .image_base = @intCast(coff.image_base),
2296 .section_alignment = coff.page_size,
2297 .file_alignment = default_file_alignment,
2298 .major_operating_system_version = 6,
2299 .minor_operating_system_version = 0,
2300 .major_image_version = 0,
2301 .minor_image_version = 0,
2302 .major_subsystem_version = @intCast(coff.major_subsystem_version),
2303 .minor_subsystem_version = @intCast(coff.minor_subsystem_version),
2304 .win32_version_value = 0,
2305 .size_of_image = size_of_image,
2306 .size_of_headers = size_of_headers,
2307 .checksum = 0,
2308 .subsystem = subsystem,
2309 .dll_flags = dll_flags,
2310 .size_of_stack_reserve = default_size_of_stack_reserve,
2311 .size_of_stack_commit = default_size_of_stack_commit,
2312 .size_of_heap_reserve = default_size_of_heap_reserve,
2313 .size_of_heap_commit = default_size_of_heap_commit,
2314 .loader_flags = 0,
2315 .number_of_rva_and_sizes = @intCast(coff.data_directories.len),
2316 };
2317 writer.writeAll(mem.asBytes(&opt_header)) catch unreachable;
2318 },
2319 .p64 => {
2320 var opt_header = coff_util.OptionalHeaderPE64{
2321 .magic = .@"PE32+",
2322 .major_linker_version = 0,
2323 .minor_linker_version = 0,
2324 .size_of_code = size_of_code,
2325 .size_of_initialized_data = size_of_initialized_data,
2326 .size_of_uninitialized_data = size_of_uninitialized_data,
2327 .address_of_entry_point = coff.entry_addr orelse 0,
2328 .base_of_code = base_of_code,
2329 .image_base = coff.image_base,
2330 .section_alignment = coff.page_size,
2331 .file_alignment = default_file_alignment,
2332 .major_operating_system_version = 6,
2333 .minor_operating_system_version = 0,
2334 .major_image_version = 0,
2335 .minor_image_version = 0,
2336 .major_subsystem_version = coff.major_subsystem_version,
2337 .minor_subsystem_version = coff.minor_subsystem_version,
2338 .win32_version_value = 0,
2339 .size_of_image = size_of_image,
2340 .size_of_headers = size_of_headers,
2341 .checksum = 0,
2342 .subsystem = subsystem,
2343 .dll_flags = dll_flags,
2344 .size_of_stack_reserve = default_size_of_stack_reserve,
2345 .size_of_stack_commit = default_size_of_stack_commit,
2346 .size_of_heap_reserve = default_size_of_heap_reserve,
2347 .size_of_heap_commit = default_size_of_heap_commit,
2348 .loader_flags = 0,
2349 .number_of_rva_and_sizes = @intCast(coff.data_directories.len),
2350 };
2351 writer.writeAll(mem.asBytes(&opt_header)) catch unreachable;
2352 },
2353 }
2354
2355 try coff.pwriteAll(buffer.written(), 0);
2356}
2357
2358pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
2359 return actual_size +| (actual_size / ideal_factor);
2360}
2361
2362fn detectAllocCollision(coff: *Coff, start: u32, size: u32) ?u32 {
2363 const headers_size = @max(coff.getSizeOfHeaders(), coff.page_size);
2364 if (start < headers_size)
2365 return headers_size;
2366
2367 const end = start + padToIdeal(size);
2368
2369 if (coff.strtab_offset) |off| {
2370 const tight_size = @as(u32, @intCast(coff.strtab.buffer.items.len));
2371 const increased_size = padToIdeal(tight_size);
2372 const test_end = off + increased_size;
2373 if (end > off and start < test_end) {
2374 return test_end;
2375 }
2376 }
2377
2378 for (coff.sections.items(.header)) |header| {
2379 const tight_size = header.size_of_raw_data;
2380 const increased_size = padToIdeal(tight_size);
2381 const test_end = header.pointer_to_raw_data + increased_size;
2382 if (end > header.pointer_to_raw_data and start < test_end) {
2383 return test_end;
2384 }
2385 }
2386
2387 return null;
2388}
2389
2390fn allocatedSize(coff: *Coff, start: u32) u32 {
2391 if (start == 0)
2392 return 0;
2393 var min_pos: u32 = std.math.maxInt(u32);
2394 if (coff.strtab_offset) |off| {
2395 if (off > start and off < min_pos) min_pos = off;
2396 }
2397 for (coff.sections.items(.header)) |header| {
2398 if (header.pointer_to_raw_data <= start) continue;
2399 if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data;
2400 }
2401 return min_pos - start;
2402}
2403
2404fn findFreeSpace(coff: *Coff, object_size: u32, min_alignment: u32) u32 {
2405 var start: u32 = 0;
2406 while (coff.detectAllocCollision(start, object_size)) |item_end| {
2407 start = mem.alignForward(u32, item_end, min_alignment);
2408 }
2409 return start;
2410}
2411
2412fn allocatedVirtualSize(coff: *Coff, start: u32) u32 {
2413 if (start == 0)
2414 return 0;
2415 var min_pos: u32 = std.math.maxInt(u32);
2416 for (coff.sections.items(.header)) |header| {
2417 if (header.virtual_address <= start) continue;
2418 if (header.virtual_address < min_pos) min_pos = header.virtual_address;
2419 }
2420 return min_pos - start;
2421}
2422
2423fn getSizeOfHeaders(coff: Coff) u32 {
2424 const msdos_hdr_size = msdos_stub.len + 4;
2425 return @as(u32, @intCast(msdos_hdr_size + @sizeOf(coff_util.Header) + coff.getOptionalHeaderSize() +
2426 coff.getDataDirectoryHeadersSize() + coff.getSectionHeadersSize()));
2427}
2428
2429fn getOptionalHeaderSize(coff: Coff) u32 {
2430 return switch (coff.ptr_width) {
2431 .p32 => @as(u32, @intCast(@sizeOf(coff_util.OptionalHeaderPE32))),
2432 .p64 => @as(u32, @intCast(@sizeOf(coff_util.OptionalHeaderPE64))),
2433 };
2434}
2435
2436fn getDataDirectoryHeadersSize(coff: Coff) u32 {
2437 return @as(u32, @intCast(coff.data_directories.len * @sizeOf(coff_util.ImageDataDirectory)));
2438}
2439
2440fn getSectionHeadersSize(coff: Coff) u32 {
2441 return @as(u32, @intCast(coff.sections.slice().len * @sizeOf(coff_util.SectionHeader)));
2442}
2443
2444fn getDataDirectoryHeadersOffset(coff: Coff) u32 {
2445 const msdos_hdr_size = msdos_stub.len + 4;
2446 return @as(u32, @intCast(msdos_hdr_size + @sizeOf(coff_util.Header) + coff.getOptionalHeaderSize()));
2447}
2448
2449fn getSectionHeadersOffset(coff: Coff) u32 {
2450 return coff.getDataDirectoryHeadersOffset() + coff.getDataDirectoryHeadersSize();
2451}
2452
2453fn getSizeOfImage(coff: Coff) u32 {
2454 var image_size: u32 = mem.alignForward(u32, coff.getSizeOfHeaders(), coff.page_size);
2455 for (coff.sections.items(.header)) |header| {
2456 image_size += mem.alignForward(u32, header.virtual_size, coff.page_size);
2457 }
2458 return image_size;
2459}
2460
2461/// Returns symbol location corresponding to the set entrypoint (if any).
2462pub fn getEntryPoint(coff: Coff) ?SymbolWithLoc {
2463 const comp = coff.base.comp;
2464
2465 // TODO This is incomplete.
2466 // The entry symbol name depends on the subsystem as well as the set of
2467 // public symbol names from linked objects.
2468 // See LinkerDriver::findDefaultEntry from the LLD project for the flow chart.
2469 const entry_name = switch (coff.entry) {
2470 .disabled => return null,
2471 .default => switch (comp.config.output_mode) {
2472 .Exe => "wWinMainCRTStartup",
2473 .Obj, .Lib => return null,
2474 },
2475 .enabled => "wWinMainCRTStartup",
2476 .named => |name| name,
2477 };
2478 const global_index = coff.resolver.get(entry_name) orelse return null;
2479 return coff.globals.items[global_index];
2480}
2481
2482/// Returns pointer-to-symbol described by `sym_loc` descriptor.
2483pub fn getSymbolPtr(coff: *Coff, sym_loc: SymbolWithLoc) *coff_util.Symbol {
2484 assert(sym_loc.file == null); // TODO linking object files
2485 return &coff.locals.items[sym_loc.sym_index];
2486}
2487
2488/// Returns symbol described by `sym_loc` descriptor.
2489pub fn getSymbol(coff: *const Coff, sym_loc: SymbolWithLoc) *const coff_util.Symbol {
2490 assert(sym_loc.file == null); // TODO linking object files
2491 return &coff.locals.items[sym_loc.sym_index];
2492}
2493
2494/// Returns name of the symbol described by `sym_loc` descriptor.
2495pub fn getSymbolName(coff: *const Coff, sym_loc: SymbolWithLoc) []const u8 {
2496 assert(sym_loc.file == null); // TODO linking object files
2497 const sym = coff.getSymbol(sym_loc);
2498 const offset = sym.getNameOffset() orelse return sym.getName().?;
2499 return coff.strtab.get(offset).?;
2500}
2501
2502/// Returns pointer to the global entry for `name` if one exists.
2503pub fn getGlobalPtr(coff: *Coff, name: []const u8) ?*SymbolWithLoc {
2504 const global_index = coff.resolver.get(name) orelse return null;
2505 return &coff.globals.items[global_index];
2506}
2507
2508/// Returns the global entry for `name` if one exists.
2509pub fn getGlobal(coff: *const Coff, name: []const u8) ?SymbolWithLoc {
2510 const global_index = coff.resolver.get(name) orelse return null;
2511 return coff.globals.items[global_index];
2512}
2513
2514/// Returns the index of the global entry for `name` if one exists.
2515pub fn getGlobalIndex(coff: *const Coff, name: []const u8) ?u32 {
2516 return coff.resolver.get(name);
2517}
2518
2519/// Returns global entry at `index`.
2520pub fn getGlobalByIndex(coff: *const Coff, index: u32) SymbolWithLoc {
2521 assert(index < coff.globals.items.len);
2522 return coff.globals.items[index];
2523}
2524
2525const GetOrPutGlobalPtrResult = struct {
2526 found_existing: bool,
2527 value_ptr: *SymbolWithLoc,
2528};
2529
2530/// Return pointer to the global entry for `name` if one exists.
2531/// Puts a new global entry for `name` if one doesn't exist, and
2532/// returns a pointer to it.
2533pub fn getOrPutGlobalPtr(coff: *Coff, name: []const u8) !GetOrPutGlobalPtrResult {
2534 if (coff.getGlobalPtr(name)) |ptr| {
2535 return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr };
2536 }
2537 const gpa = coff.base.comp.gpa;
2538 const global_index = try coff.allocateGlobal();
2539 const global_name = try gpa.dupe(u8, name);
2540 _ = try coff.resolver.put(gpa, global_name, global_index);
2541 const ptr = &coff.globals.items[global_index];
2542 return GetOrPutGlobalPtrResult{ .found_existing = false, .value_ptr = ptr };
2543}
2544
2545pub fn getAtom(coff: *const Coff, atom_index: Atom.Index) Atom {
2546 assert(atom_index < coff.atoms.items.len);
2547 return coff.atoms.items[atom_index];
2548}
2549
2550pub fn getAtomPtr(coff: *Coff, atom_index: Atom.Index) *Atom {
2551 assert(atom_index < coff.atoms.items.len);
2552 return &coff.atoms.items[atom_index];
2553}
2554
2555/// Returns atom if there is an atom referenced by the symbol described by `sym_loc` descriptor.
2556/// Returns null on failure.
2557pub fn getAtomIndexForSymbol(coff: *const Coff, sym_loc: SymbolWithLoc) ?Atom.Index {
2558 assert(sym_loc.file == null); // TODO linking with object files
2559 return coff.atom_by_index_table.get(sym_loc.sym_index);
2560}
2561
2562fn setSectionName(coff: *Coff, header: *coff_util.SectionHeader, name: []const u8) !void {
2563 if (name.len <= 8) {
2564 @memcpy(header.name[0..name.len], name);
2565 @memset(header.name[name.len..], 0);
2566 return;
2567 }
2568 const gpa = coff.base.comp.gpa;
2569 const offset = try coff.strtab.insert(gpa, name);
2570 const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable;
2571 @memset(header.name[name_offset.len..], 0);
2572}
2573
2574fn getSectionName(coff: *const Coff, header: *const coff_util.SectionHeader) []const u8 {
2575 if (header.getName()) |name| {
2576 return name;
2577 }
2578 const offset = header.getNameOffset().?;
2579 return coff.strtab.get(offset).?;
2580}
2581
2582fn setSymbolName(coff: *Coff, symbol: *coff_util.Symbol, name: []const u8) !void {
2583 if (name.len <= 8) {
2584 @memcpy(symbol.name[0..name.len], name);
2585 @memset(symbol.name[name.len..], 0);
2586 return;
2587 }
2588 const gpa = coff.base.comp.gpa;
2589 const offset = try coff.strtab.insert(gpa, name);
2590 @memset(symbol.name[0..4], 0);
2591 mem.writeInt(u32, symbol.name[4..8], offset, .little);
2592}
2593
2594fn logSymAttributes(sym: *const coff_util.Symbol, buf: *[4]u8) []const u8 {
2595 @memset(buf[0..4], '_');
2596 switch (sym.section_number) {
2597 .UNDEFINED => {
2598 buf[3] = 'u';
2599 switch (sym.storage_class) {
2600 .EXTERNAL => buf[1] = 'e',
2601 .WEAK_EXTERNAL => buf[1] = 'w',
2602 .NULL => {},
2603 else => unreachable,
2604 }
2605 },
2606 .ABSOLUTE => unreachable, // handle ABSOLUTE
2607 .DEBUG => unreachable,
2608 else => {
2609 buf[0] = 's';
2610 switch (sym.storage_class) {
2611 .EXTERNAL => buf[1] = 'e',
2612 .WEAK_EXTERNAL => buf[1] = 'w',
2613 .NULL => {},
2614 else => unreachable,
2615 }
2616 },
2617 }
2618 return buf[0..];
2619}
2620
2621fn logSymtab(coff: *Coff) void {
2622 var buf: [4]u8 = undefined;
2623
2624 log.debug("symtab:", .{});
2625 log.debug(" object(null)", .{});
2626 for (coff.locals.items, 0..) |*sym, sym_id| {
2627 const where = if (sym.section_number == .UNDEFINED) "ord" else "sect";
2628 const def_index: u16 = switch (sym.section_number) {
2629 .UNDEFINED => 0, // TODO
2630 .ABSOLUTE => unreachable, // TODO
2631 .DEBUG => unreachable, // TODO
2632 else => @intFromEnum(sym.section_number),
2633 };
2634 log.debug(" %{d}: {s} @{x} in {s}({d}), {s}", .{
2635 sym_id,
2636 coff.getSymbolName(.{ .sym_index = @as(u32, @intCast(sym_id)), .file = null }),
2637 sym.value,
2638 where,
2639 def_index,
2640 logSymAttributes(sym, &buf),
2641 });
2642 }
2643
2644 log.debug("globals table:", .{});
2645 for (coff.globals.items) |sym_loc| {
2646 const sym_name = coff.getSymbolName(sym_loc);
2647 log.debug(" {s} => %{d} in object({?d})", .{ sym_name, sym_loc.sym_index, sym_loc.file });
2648 }
2649
2650 log.debug("GOT entries:", .{});
2651 log.debug("{f}", .{coff.got_table});
2652}
2653
2654fn logSections(coff: *Coff) void {
2655 log.debug("sections:", .{});
2656 for (coff.sections.items(.header)) |*header| {
2657 log.debug(" {s}: VM({x}, {x}) FILE({x}, {x})", .{
2658 coff.getSectionName(header),
2659 header.virtual_address,
2660 header.virtual_address + header.virtual_size,
2661 header.pointer_to_raw_data,
2662 header.pointer_to_raw_data + header.size_of_raw_data,
2663 });
2664 }
2665}
2666
2667fn logImportTables(coff: *const Coff) void {
2668 log.debug("import tables:", .{});
2669 for (coff.import_tables.keys(), 0..) |off, i| {
2670 const itable = coff.import_tables.values()[i];
2671 log.debug("{f}", .{itable.fmtDebug(.{
2672 .coff = coff,
2673 .index = i,
2674 .name_off = off,
2675 })});
2676 }
2677}
2678
2679pub const Atom = struct {
2680 /// Each decl always gets a local symbol with the fully qualified name.
2681 /// The vaddr and size are found here directly.
2682 /// The file offset is found by computing the vaddr offset from the section vaddr
2683 /// the symbol references, and adding that to the file offset of the section.
2684 /// If this field is 0, it means the codegen size = 0 and there is no symbol or
2685 /// offset table entry.
2686 sym_index: u32,
2687
2688 /// null means symbol defined by Zig source.
2689 file: ?u32,
2690
2691 /// Size of the atom
2692 size: u32,
2693
2694 /// Points to the previous and next neighbors, based on the `text_offset`.
2695 /// This can be used to find, for example, the capacity of this `Atom`.
2696 prev_index: ?Index,
2697 next_index: ?Index,
2698
2699 const Index = u32;
2700
2701 pub fn getSymbolIndex(atom: Atom) ?u32 {
2702 if (atom.sym_index == 0) return null;
2703 return atom.sym_index;
2704 }
2705
2706 /// Returns symbol referencing this atom.
2707 fn getSymbol(atom: Atom, coff: *const Coff) *const coff_util.Symbol {
2708 const sym_index = atom.getSymbolIndex().?;
2709 return coff.getSymbol(.{
2710 .sym_index = sym_index,
2711 .file = atom.file,
2712 });
2713 }
2714
2715 /// Returns pointer-to-symbol referencing this atom.
2716 fn getSymbolPtr(atom: Atom, coff: *Coff) *coff_util.Symbol {
2717 const sym_index = atom.getSymbolIndex().?;
2718 return coff.getSymbolPtr(.{
2719 .sym_index = sym_index,
2720 .file = atom.file,
2721 });
2722 }
2723
2724 fn getSymbolWithLoc(atom: Atom) SymbolWithLoc {
2725 const sym_index = atom.getSymbolIndex().?;
2726 return .{ .sym_index = sym_index, .file = atom.file };
2727 }
2728
2729 /// Returns the name of this atom.
2730 fn getName(atom: Atom, coff: *const Coff) []const u8 {
2731 const sym_index = atom.getSymbolIndex().?;
2732 return coff.getSymbolName(.{
2733 .sym_index = sym_index,
2734 .file = atom.file,
2735 });
2736 }
2737
2738 /// Returns how much room there is to grow in virtual address space.
2739 fn capacity(atom: Atom, coff: *const Coff) u32 {
2740 const atom_sym = atom.getSymbol(coff);
2741 if (atom.next_index) |next_index| {
2742 const next = coff.getAtom(next_index);
2743 const next_sym = next.getSymbol(coff);
2744 return next_sym.value - atom_sym.value;
2745 } else {
2746 // We are the last atom.
2747 // The capacity is limited only by virtual address space.
2748 return std.math.maxInt(u32) - atom_sym.value;
2749 }
2750 }
2751
2752 fn freeListEligible(atom: Atom, coff: *const Coff) bool {
2753 // No need to keep a free list node for the last atom.
2754 const next_index = atom.next_index orelse return false;
2755 const next = coff.getAtom(next_index);
2756 const atom_sym = atom.getSymbol(coff);
2757 const next_sym = next.getSymbol(coff);
2758 const cap = next_sym.value - atom_sym.value;
2759 const ideal_cap = padToIdeal(atom.size);
2760 if (cap <= ideal_cap) return false;
2761 const surplus = cap - ideal_cap;
2762 return surplus >= min_text_capacity;
2763 }
2764};
2765
2766pub const Relocation = struct {
2767 type: enum {
2768 // x86, x86_64
2769 /// RIP-relative displacement to a GOT pointer
2770 got,
2771 /// RIP-relative displacement to an import pointer
2772 import,
2773
2774 // aarch64
2775 /// PC-relative distance to target page in GOT section
2776 got_page,
2777 /// Offset to a GOT pointer relative to the start of a page in GOT section
2778 got_pageoff,
2779 /// PC-relative distance to target page in a section (e.g., .rdata)
2780 page,
2781 /// Offset to a pointer relative to the start of a page in a section (e.g., .rdata)
2782 pageoff,
2783 /// PC-relative distance to target page in a import section
2784 import_page,
2785 /// Offset to a pointer relative to the start of a page in an import section (e.g., .rdata)
2786 import_pageoff,
2787
2788 // common
2789 /// Absolute pointer value
2790 direct,
2791 },
2792 target: SymbolWithLoc,
2793 offset: u32,
2794 addend: u32,
2795 pcrel: bool,
2796 length: u2,
2797 dirty: bool = true,
2798
2799 /// Returns true if and only if the reloc can be resolved.
2800 fn isResolvable(reloc: Relocation, coff: *Coff) bool {
2801 _ = reloc.getTargetAddress(coff) orelse return false;
2802 return true;
2803 }
2804
2805 fn isGotIndirection(reloc: Relocation) bool {
2806 return switch (reloc.type) {
2807 .got, .got_page, .got_pageoff => true,
2808 else => false,
2809 };
2810 }
2811
2812 /// Returns address of the target if any.
2813 fn getTargetAddress(reloc: Relocation, coff: *const Coff) ?u32 {
2814 switch (reloc.type) {
2815 .got, .got_page, .got_pageoff => {
2816 const got_index = coff.got_table.lookup.get(reloc.target) orelse return null;
2817 const header = coff.sections.items(.header)[coff.got_section_index.?];
2818 return header.virtual_address + got_index * coff.ptr_width.size();
2819 },
2820 .import, .import_page, .import_pageoff => {
2821 const sym = coff.getSymbol(reloc.target);
2822 const index = coff.import_tables.getIndex(sym.value) orelse return null;
2823 const itab = coff.import_tables.values()[index];
2824 return itab.getImportAddress(reloc.target, .{
2825 .coff = coff,
2826 .index = index,
2827 .name_off = sym.value,
2828 });
2829 },
2830 else => {
2831 const target_atom_index = coff.getAtomIndexForSymbol(reloc.target) orelse return null;
2832 const target_atom = coff.getAtom(target_atom_index);
2833 return target_atom.getSymbol(coff).value;
2834 },
2835 }
2836 }
2837
2838 fn resolve(reloc: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff: *Coff) void {
2839 const atom = coff.getAtom(atom_index);
2840 const source_sym = atom.getSymbol(coff);
2841 const source_vaddr = source_sym.value + reloc.offset;
2842
2843 const target_vaddr = reloc.getTargetAddress(coff).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
2844 const target_vaddr_with_addend = target_vaddr + reloc.addend;
2845
2846 log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{
2847 source_vaddr,
2848 target_vaddr_with_addend,
2849 coff.getSymbolName(reloc.target),
2850 @tagName(reloc.type),
2851 });
2852
2853 const ctx: Context = .{
2854 .source_vaddr = source_vaddr,
2855 .target_vaddr = target_vaddr_with_addend,
2856 .image_base = image_base,
2857 .code = code,
2858 .ptr_width = coff.ptr_width,
2859 };
2860
2861 const target = &coff.base.comp.root_mod.resolved_target.result;
2862 switch (target.cpu.arch) {
2863 .aarch64 => reloc.resolveAarch64(ctx),
2864 .x86, .x86_64 => reloc.resolveX86(ctx),
2865 else => unreachable, // unhandled target architecture
2866 }
2867 }
2868
2869 const Context = struct {
2870 source_vaddr: u32,
2871 target_vaddr: u32,
2872 image_base: u64,
2873 code: []u8,
2874 ptr_width: PtrWidth,
2875 };
2876
2877 fn resolveAarch64(reloc: Relocation, ctx: Context) void {
2878 const Instruction = aarch64_util.encoding.Instruction;
2879 var buffer = ctx.code[reloc.offset..];
2880 switch (reloc.type) {
2881 .got_page, .import_page, .page => {
2882 const source_page = @as(i32, @intCast(ctx.source_vaddr >> 12));
2883 const target_page = @as(i32, @intCast(ctx.target_vaddr >> 12));
2884 const pages: i21 = @intCast(target_page - source_page);
2885 var inst: Instruction = .read(buffer[0..Instruction.size]);
2886 inst.data_processing_immediate.pc_relative_addressing.group.immhi = @intCast(pages >> 2);
2887 inst.data_processing_immediate.pc_relative_addressing.group.immlo = @truncate(@as(u21, @bitCast(pages)));
2888 inst.write(buffer[0..Instruction.size]);
2889 },
2890 .got_pageoff, .import_pageoff, .pageoff => {
2891 assert(!reloc.pcrel);
2892
2893 const narrowed: u12 = @truncate(@as(u64, @intCast(ctx.target_vaddr)));
2894 var inst: Instruction = .read(buffer[0..Instruction.size]);
2895 switch (inst.decode()) {
2896 else => unreachable,
2897 .data_processing_immediate => inst.data_processing_immediate.add_subtract_immediate.group.imm12 = narrowed,
2898 .load_store => |load_store| inst.load_store.register_unsigned_immediate.group.imm12 =
2899 switch (load_store.register_unsigned_immediate.decode()) {
2900 .integer => |integer| @shrExact(narrowed, @intFromEnum(integer.group.size)),
2901 .vector => |vector| @shrExact(narrowed, @intFromEnum(vector.group.opc1.decode(vector.group.size))),
2902 },
2903 }
2904 inst.write(buffer[0..Instruction.size]);
2905 },
2906 .direct => {
2907 assert(!reloc.pcrel);
2908 switch (reloc.length) {
2909 2 => mem.writeInt(
2910 u32,
2911 buffer[0..4],
2912 @as(u32, @truncate(ctx.target_vaddr + ctx.image_base)),
2913 .little,
2914 ),
2915 3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little),
2916 else => unreachable,
2917 }
2918 },
2919
2920 .got => unreachable,
2921 .import => unreachable,
2922 }
2923 }
2924
2925 fn resolveX86(reloc: Relocation, ctx: Context) void {
2926 var buffer = ctx.code[reloc.offset..];
2927 switch (reloc.type) {
2928 .got_page => unreachable,
2929 .got_pageoff => unreachable,
2930 .page => unreachable,
2931 .pageoff => unreachable,
2932 .import_page => unreachable,
2933 .import_pageoff => unreachable,
2934
2935 .got, .import => {
2936 assert(reloc.pcrel);
2937 const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4;
2938 mem.writeInt(i32, buffer[0..4], disp, .little);
2939 },
2940 .direct => {
2941 if (reloc.pcrel) {
2942 const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4;
2943 mem.writeInt(i32, buffer[0..4], disp, .little);
2944 } else switch (ctx.ptr_width) {
2945 .p32 => mem.writeInt(u32, buffer[0..4], @as(u32, @intCast(ctx.target_vaddr + ctx.image_base)), .little),
2946 .p64 => switch (reloc.length) {
2947 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(ctx.target_vaddr + ctx.image_base)), .little),
2948 3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little),
2949 else => unreachable,
2950 },
2951 }
2952 },
2953 }
2954 }
2955};
2956
2957pub fn addRelocation(coff: *Coff, atom_index: Atom.Index, reloc: Relocation) !void {
2958 const comp = coff.base.comp;
2959 const gpa = comp.gpa;
2960 log.debug(" (adding reloc of type {s} to target %{d})", .{ @tagName(reloc.type), reloc.target.sym_index });
2961 const gop = try coff.relocs.getOrPut(gpa, atom_index);
2962 if (!gop.found_existing) {
2963 gop.value_ptr.* = .{};
2964 }
2965 try gop.value_ptr.append(gpa, reloc);
2966}
2967
2968fn addBaseRelocation(coff: *Coff, atom_index: Atom.Index, offset: u32) !void {
2969 const comp = coff.base.comp;
2970 const gpa = comp.gpa;
2971 log.debug(" (adding base relocation at offset 0x{x} in %{d})", .{
2972 offset,
2973 coff.getAtom(atom_index).getSymbolIndex().?,
2974 });
2975 const gop = try coff.base_relocs.getOrPut(gpa, atom_index);
2976 if (!gop.found_existing) {
2977 gop.value_ptr.* = .{};
2978 }
2979 try gop.value_ptr.append(gpa, offset);
2980}
2981
2982fn freeRelocations(coff: *Coff, atom_index: Atom.Index) void {
2983 const comp = coff.base.comp;
2984 const gpa = comp.gpa;
2985 var removed_relocs = coff.relocs.fetchOrderedRemove(atom_index);
2986 if (removed_relocs) |*relocs| relocs.value.deinit(gpa);
2987 var removed_base_relocs = coff.base_relocs.fetchOrderedRemove(atom_index);
2988 if (removed_base_relocs) |*base_relocs| base_relocs.value.deinit(gpa);
2989}
2990
2991/// Represents an import table in the .idata section where each contained pointer
2992/// is to a symbol from the same DLL.
2993///
2994/// The layout of .idata section is as follows:
2995///
2996/// --- ADDR1 : IAT (all import tables concatenated together)
2997/// ptr
2998/// ptr
2999/// 0 sentinel
3000/// ptr
3001/// 0 sentinel
3002/// --- ADDR2: headers
3003/// ImportDirectoryEntry header
3004/// ImportDirectoryEntry header
3005/// sentinel
3006/// --- ADDR2: lookup tables
3007/// Lookup table
3008/// 0 sentinel
3009/// Lookup table
3010/// 0 sentinel
3011/// --- ADDR3: name hint tables
3012/// hint-symname
3013/// hint-symname
3014/// --- ADDR4: DLL names
3015/// DLL#1 name
3016/// DLL#2 name
3017/// --- END
3018const ImportTable = struct {
3019 entries: std.ArrayListUnmanaged(SymbolWithLoc) = .empty,
3020 free_list: std.ArrayListUnmanaged(u32) = .empty,
3021 lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .empty,
3022
3023 fn deinit(itab: *ImportTable, allocator: Allocator) void {
3024 itab.entries.deinit(allocator);
3025 itab.free_list.deinit(allocator);
3026 itab.lookup.deinit(allocator);
3027 }
3028
3029 /// Size of the import table does not include the sentinel.
3030 fn size(itab: ImportTable) u32 {
3031 return @as(u32, @intCast(itab.entries.items.len)) * @sizeOf(u64);
3032 }
3033
3034 fn addImport(itab: *ImportTable, allocator: Allocator, target: SymbolWithLoc) !ImportIndex {
3035 try itab.entries.ensureUnusedCapacity(allocator, 1);
3036 const index: u32 = blk: {
3037 if (itab.free_list.pop()) |index| {
3038 log.debug(" (reusing import entry index {d})", .{index});
3039 break :blk index;
3040 } else {
3041 log.debug(" (allocating import entry at index {d})", .{itab.entries.items.len});
3042 const index = @as(u32, @intCast(itab.entries.items.len));
3043 _ = itab.entries.addOneAssumeCapacity();
3044 break :blk index;
3045 }
3046 };
3047 itab.entries.items[index] = target;
3048 try itab.lookup.putNoClobber(allocator, target, index);
3049 return index;
3050 }
3051
3052 const Context = struct {
3053 coff: *const Coff,
3054 /// Index of this ImportTable in a global list of all tables.
3055 /// This is required in order to calculate the base vaddr of this ImportTable.
3056 index: usize,
3057 /// Offset into the string interning table of the DLL this ImportTable corresponds to.
3058 name_off: u32,
3059 };
3060
3061 fn getBaseAddress(ctx: Context) u32 {
3062 const header = ctx.coff.sections.items(.header)[ctx.coff.idata_section_index.?];
3063 var addr = header.virtual_address;
3064 for (ctx.coff.import_tables.values(), 0..) |other_itab, i| {
3065 if (ctx.index == i) break;
3066 addr += @as(u32, @intCast(other_itab.entries.items.len * @sizeOf(u64))) + 8;
3067 }
3068 return addr;
3069 }
3070
3071 fn getImportAddress(itab: *const ImportTable, target: SymbolWithLoc, ctx: Context) ?u32 {
3072 const index = itab.lookup.get(target) orelse return null;
3073 const base_vaddr = getBaseAddress(ctx);
3074 return base_vaddr + index * @sizeOf(u64);
3075 }
3076
3077 const Format = struct {
3078 itab: ImportTable,
3079 ctx: Context,
3080
3081 fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
3082 const lib_name = f.ctx.coff.temp_strtab.getAssumeExists(f.ctx.name_off);
3083 const base_vaddr = getBaseAddress(f.ctx);
3084 try writer.print("IAT({s}.dll) @{x}:", .{ lib_name, base_vaddr });
3085 for (f.itab.entries.items, 0..) |entry, i| {
3086 try writer.print("\n {d}@{?x} => {s}", .{
3087 i,
3088 f.itab.getImportAddress(entry, f.ctx),
3089 f.ctx.coff.getSymbolName(entry),
3090 });
3091 }
3092 }
3093 };
3094
3095 fn fmtDebug(itab: ImportTable, ctx: Context) fmt.Alt(Format, Format.default) {
3096 return .{ .data = .{ .itab = itab, .ctx = ctx } };
3097 }
3098
3099 const ImportIndex = u32;
3100};
3101
3102fn pwriteAll(coff: *Coff, bytes: []const u8, offset: u64) error{LinkFailure}!void {
3103 const comp = coff.base.comp;
3104 const diags = &comp.link_diags;
3105 coff.base.file.?.pwriteAll(bytes, offset) catch |err| {
3106 return diags.fail("failed to write: {s}", .{@errorName(err)});
3107 };
3108}
3109
3110/// This is the start of a Portable Executable (PE) file.
3111/// It starts with a MS-DOS header followed by a MS-DOS stub program.
3112/// This data does not change so we include it as follows in all binaries.
3113///
3114/// In this context,
3115/// A "paragraph" is 16 bytes.
3116/// A "page" is 512 bytes.
3117/// A "long" is 4 bytes.
3118/// A "word" is 2 bytes.
3119pub const msdos_stub: [120]u8 = .{
3120 'M', 'Z', // Magic number. Stands for Mark Zbikowski (designer of the MS-DOS executable format).
3121 0x78, 0x00, // Number of bytes in the last page. This matches the size of this entire MS-DOS stub.
3122 0x01, 0x00, // Number of pages.
3123 0x00, 0x00, // Number of entries in the relocation table.
3124 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).
3125 0x00, 0x00, // The number of paragraphs required by the program.
3126 0x00, 0x00, // The number of paragraphs requested by the program.
3127 0x00, 0x00, // Initial value for SS (relocatable segment address).
3128 0x00, 0x00, // Initial value for SP.
3129 0x00, 0x00, // Checksum.
3130 0x00, 0x00, // Initial value for IP.
3131 0x00, 0x00, // Initial value for CS (relocatable segment address).
3132 0x40, 0x00, // Absolute offset to relocation table. 64 matches the header size (all bytes before the MS-DOS stub program).
3133 0x00, 0x00, // Overlay number. Zero means this is the main executable.
3134}
3135 // Reserved words.
3136 ++ .{ 0x00, 0x00 } ** 4
3137 // OEM-related fields.
3138 ++ .{
3139 0x00, 0x00, // OEM identifier.
3140 0x00, 0x00, // OEM information.
3141 }
3142 // Reserved words.
3143 ++ .{ 0x00, 0x00 } ** 10
3144 // 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.
3145 ++ .{ 0x78, 0x00, 0x00, 0x00 }
3146 // What follows is a 16-bit x86 MS-DOS program of 7 instructions that prints the bytes after these instructions and then exits.
3147 ++ .{
3148 // Set the value of the data segment to the same value as the code segment.
3149 0x0e, // push cs
3150 0x1f, // pop ds
3151 // Set the DX register to the address of the message.
3152 // If you count all bytes of these 7 instructions you get 14, so that's the address of what's after these instructions.
3153 0xba, 14, 0x00, // mov dx, 14
3154 // Set AH to the system call code for printing a message.
3155 0xb4, 0x09, // mov ah, 0x09
3156 // Perform the system call to print the message.
3157 0xcd, 0x21, // int 0x21
3158 // Set AH to 0x4c which is the system call code for exiting, and set AL to 0x01 which is the exit code.
3159 0xb8, 0x01, 0x4c, // mov ax, 0x4c01
3160 // Peform the system call to exit the program with exit code 1.
3161 0xcd, 0x21, // int 0x21
3162 }
3163 // Message to print.
3164 ++ "This program cannot be run in DOS mode.".*
3165 // Message terminators.
3166 ++ .{
3167 '$', // We do not pass a length to the print system call; the string is terminated by this character.
3168 0x00, 0x00, // Terminating zero bytes.
3169 };
src/link/Coff2.zig+78-13
......@@ -28,6 +28,73 @@ relocs: std.ArrayList(Reloc),
2828/// This is hiding actual bugs with global symbols! Reconsider once they are implemented correctly.
2929entry_hack: Symbol.Index,
3030
31pub const default_file_alignment: u16 = 0x200;
32pub const default_size_of_stack_reserve: u32 = 0x1000000;
33pub const default_size_of_stack_commit: u32 = 0x1000;
34pub const default_size_of_heap_reserve: u32 = 0x100000;
35pub const default_size_of_heap_commit: u32 = 0x1000;
36
37/// This is the start of a Portable Executable (PE) file.
38/// It starts with a MS-DOS header followed by a MS-DOS stub program.
39/// This data does not change so we include it as follows in all binaries.
40///
41/// In this context,
42/// A "paragraph" is 16 bytes.
43/// A "page" is 512 bytes.
44/// A "long" is 4 bytes.
45/// A "word" is 2 bytes.
46pub const msdos_stub: [120]u8 = .{
47 'M', 'Z', // Magic number. Stands for Mark Zbikowski (designer of the MS-DOS executable format).
48 0x78, 0x00, // Number of bytes in the last page. This matches the size of this entire MS-DOS stub.
49 0x01, 0x00, // Number of pages.
50 0x00, 0x00, // Number of entries in the relocation table.
51 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).
52 0x00, 0x00, // The number of paragraphs required by the program.
53 0x00, 0x00, // The number of paragraphs requested by the program.
54 0x00, 0x00, // Initial value for SS (relocatable segment address).
55 0x00, 0x00, // Initial value for SP.
56 0x00, 0x00, // Checksum.
57 0x00, 0x00, // Initial value for IP.
58 0x00, 0x00, // Initial value for CS (relocatable segment address).
59 0x40, 0x00, // Absolute offset to relocation table. 64 matches the header size (all bytes before the MS-DOS stub program).
60 0x00, 0x00, // Overlay number. Zero means this is the main executable.
61}
62 // Reserved words.
63 ++ .{ 0x00, 0x00 } ** 4
64 // OEM-related fields.
65 ++ .{
66 0x00, 0x00, // OEM identifier.
67 0x00, 0x00, // OEM information.
68 }
69 // Reserved words.
70 ++ .{ 0x00, 0x00 } ** 10
71 // 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.
72 ++ .{ 0x78, 0x00, 0x00, 0x00 }
73 // What follows is a 16-bit x86 MS-DOS program of 7 instructions that prints the bytes after these instructions and then exits.
74 ++ .{
75 // Set the value of the data segment to the same value as the code segment.
76 0x0e, // push cs
77 0x1f, // pop ds
78 // Set the DX register to the address of the message.
79 // If you count all bytes of these 7 instructions you get 14, so that's the address of what's after these instructions.
80 0xba, 14, 0x00, // mov dx, 14
81 // Set AH to the system call code for printing a message.
82 0xb4, 0x09, // mov ah, 0x09
83 // Perform the system call to print the message.
84 0xcd, 0x21, // int 0x21
85 // Set AH to 0x4c which is the system call code for exiting, and set AL to 0x01 which is the exit code.
86 0xb8, 0x01, 0x4c, // mov ax, 0x4c01
87 // Peform the system call to exit the program with exit code 1.
88 0xcd, 0x21, // int 0x21
89 }
90 // Message to print.
91 ++ "This program cannot be run in DOS mode.".*
92 // Message terminators.
93 ++ .{
94 '$', // We do not pass a length to the print system call; the string is terminated by this character.
95 0x00, 0x00, // Terminating zero bytes.
96 };
97
3198pub const Node = union(enum) {
3299 file,
33100 header,
......@@ -613,8 +680,7 @@ fn initHeaders(
613680) !void {
614681 const comp = coff.base.comp;
615682 const gpa = comp.gpa;
616 const file_align: std.mem.Alignment =
617 comptime .fromByteUnits(link.File.Coff.default_file_alignment);
683 const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment);
618684 const target_endian = coff.targetEndian();
619685
620686 const optional_header_size: u16 = if (is_image) switch (magic) {
......@@ -639,15 +705,14 @@ fn initHeaders(
639705
640706 const signature_ni = Node.known.signature;
641707 assert(signature_ni == try coff.mf.addOnlyChildNode(gpa, header_ni, .{
642 .size = (if (is_image) link.File.Coff.msdos_stub.len else 0) + "PE\x00\x00".len,
708 .size = (if (is_image) msdos_stub.len else 0) + "PE\x00\x00".len,
643709 .alignment = .@"4",
644710 .fixed = true,
645711 }));
646712 coff.nodes.appendAssumeCapacity(.signature);
647713 {
648714 const signature_slice = signature_ni.slice(&coff.mf);
649 if (is_image)
650 @memcpy(signature_slice[0..link.File.Coff.msdos_stub.len], &link.File.Coff.msdos_stub);
715 if (is_image) @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
651716 @memcpy(signature_slice[signature_slice.len - 4 ..], "PE\x00\x00");
652717 }
653718
......@@ -730,10 +795,10 @@ fn initHeaders(
730795 .TERMINAL_SERVER_AWARE = true,
731796 .NX_COMPAT = true,
732797 },
733 .size_of_stack_reserve = link.File.Coff.default_size_of_stack_reserve,
734 .size_of_stack_commit = link.File.Coff.default_size_of_stack_commit,
735 .size_of_heap_reserve = link.File.Coff.default_size_of_heap_reserve,
736 .size_of_heap_commit = link.File.Coff.default_size_of_heap_commit,
798 .size_of_stack_reserve = default_size_of_stack_reserve,
799 .size_of_stack_commit = default_size_of_stack_commit,
800 .size_of_heap_reserve = default_size_of_heap_reserve,
801 .size_of_heap_commit = default_size_of_heap_commit,
737802 .loader_flags = 0,
738803 .number_of_rva_and_sizes = data_directories_len,
739804 };
......@@ -781,10 +846,10 @@ fn initHeaders(
781846 .TERMINAL_SERVER_AWARE = true,
782847 .NX_COMPAT = true,
783848 },
784 .size_of_stack_reserve = link.File.Coff.default_size_of_stack_reserve,
785 .size_of_stack_commit = link.File.Coff.default_size_of_stack_commit,
786 .size_of_heap_reserve = link.File.Coff.default_size_of_heap_reserve,
787 .size_of_heap_commit = link.File.Coff.default_size_of_heap_commit,
849 .size_of_stack_reserve = default_size_of_stack_reserve,
850 .size_of_stack_commit = default_size_of_stack_commit,
851 .size_of_heap_reserve = default_size_of_heap_reserve,
852 .size_of_heap_commit = default_size_of_heap_commit,
788853 .loader_flags = 0,
789854 .number_of_rva_and_sizes = data_directories_len,
790855 };