From 1fa11e0954fe84e1c571384d9bcd8a7ffadc6425 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 1 Oct 2025 15:21:49 -0400 Subject: [PATCH] Coff: delete --- CMakeLists.txt | 1 - src/Compilation.zig | 2 +- src/Compilation/Config.zig | 2 - src/codegen.zig | 15 - src/codegen/aarch64/Mir.zig | 7 - src/codegen/x86_64/Emit.zig | 74 +- src/link.zig | 18 +- src/link/Coff.zig | 3169 ----------------------------------- src/link/Coff2.zig | 91 +- 9 files changed, 85 insertions(+), 3294 deletions(-) delete mode 100644 src/link/Coff.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 690e2e35b1..1b19e728d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -561,7 +561,6 @@ set(ZIG_STAGE2_SOURCES src/libs/libunwind.zig src/link.zig src/link/C.zig - src/link/Coff.zig src/link/Dwarf.zig src/link/Elf.zig src/link/Elf/Archive.zig diff --git a/src/Compilation.zig b/src/Compilation.zig index 593841deb7..434a790695 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3226,7 +3226,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE .root_dir = comp.dirs.local_cache, .sub_path = try fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }), }; - const result: link.File.OpenError!void = switch (need_writable_dance) { + const result: (link.File.OpenError || error{HotSwapUnavailableOnHostOperatingSystem})!void = switch (need_writable_dance) { .no => {}, .lf_only => lf.makeWritable(), .lf_and_debug => res: { diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 6fe949887f..a4f8eefab7 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -438,8 +438,6 @@ pub fn resolve(options: Options) ResolveError!Config { if (options.use_new_linker) |x| break :b x; - if (target.ofmt == .coff) break :b true; - break :b options.incremental; }; diff --git a/src/codegen.zig b/src/codegen.zig index 3c5d519dd0..caff954e07 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -978,21 +978,6 @@ pub fn genNavRef( }, .link_once => unreachable, } - } else if (lf.cast(.coff)) |coff_file| { - // TODO audit this - switch (linkage) { - .internal => { - const atom_index = try coff_file.getOrCreateAtomForNav(nav_index); - const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; - return .{ .sym_index = sym_index }; - }, - .strong, .weak => { - const global_index = try coff_file.getGlobalSymbol(nav.name.toSlice(ip), lib_name.toSlice(ip)); - try coff_file.need_got_table.put(zcu.gpa, global_index, {}); // needs GOT - return .{ .sym_index = global_index }; - }, - .link_once => unreachable, - } } else if (lf.cast(.coff2)) |coff| { return .{ .sym_index = @intFromEnum(try coff.navSymbol(zcu, nav_index)) }; } else { diff --git a/src/codegen/aarch64/Mir.zig b/src/codegen/aarch64/Mir.zig index be6478eae8..3e89e28825 100644 --- a/src/codegen/aarch64/Mir.zig +++ b/src/codegen/aarch64/Mir.zig @@ -135,11 +135,6 @@ pub fn emit( else if (lf.cast(.macho)) |mf| mf.getZigObject().?.getOrCreateMetadataForLazySymbol(mf, pt, lazy_reloc.symbol) catch |err| return zcu.codegenFail(func.owner_nav, "{s} creating lazy symbol", .{@errorName(err)}) - else if (lf.cast(.coff)) |cf| - if (cf.getOrCreateAtomForLazySymbol(pt, lazy_reloc.symbol)) |atom| - cf.getAtom(atom).getSymbolIndex().? - else |err| - return zcu.codegenFail(func.owner_nav, "{s} creating lazy symbol", .{@errorName(err)}) else return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}), mir.body[lazy_reloc.reloc.label], @@ -154,8 +149,6 @@ pub fn emit( try ef.getGlobalSymbol(std.mem.span(global_reloc.name), null) else if (lf.cast(.macho)) |mf| try mf.getGlobalSymbol(std.mem.span(global_reloc.name), null) - else if (lf.cast(.coff)) |cf| - try cf.getGlobalSymbol(std.mem.span(global_reloc.name), "compiler_rt") else return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}), mir.body[global_reloc.reloc.label], diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index e02523d96d..377171683b 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -170,11 +170,6 @@ pub fn emitMir(emit: *Emit) Error!void { else if (emit.bin_file.cast(.macho)) |macho_file| macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, emit.pt, lazy_sym) catch |err| return emit.fail("{s} creating lazy symbol", .{@errorName(err)}) - else if (emit.bin_file.cast(.coff)) |coff_file| - if (coff_file.getOrCreateAtomForLazySymbol(emit.pt, lazy_sym)) |atom| - coff_file.getAtom(atom).getSymbolIndex().? - else |err| - return emit.fail("{s} creating lazy symbol", .{@errorName(err)}) else if (emit.bin_file.cast(.coff2)) |elf| @intFromEnum(try elf.lazySymbol(lazy_sym)) else @@ -190,8 +185,6 @@ pub fn emitMir(emit: *Emit) Error!void { .type = .FUNC, })) else if (emit.bin_file.cast(.macho)) |macho_file| try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null) - else if (emit.bin_file.cast(.coff)) |coff_file| - try coff_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, "compiler_rt") else if (emit.bin_file.cast(.coff2)) |coff| @intFromEnum(try coff.globalSymbol( extern_func.toSlice(&emit.lower.mir).?, switch (comp.compiler_rt_strat) { @@ -211,9 +204,7 @@ pub fn emitMir(emit: *Emit) Error!void { switch (lowered_inst.encoding.mnemonic) { .call => { reloc.target.type = .branch; - if (emit.bin_file.cast(.coff)) |_| try emit.encodeInst(try .new(.none, .call, &.{ - .{ .mem = .initRip(.ptr, 0) }, - }, emit.lower.target), reloc_info) else try emit.encodeInst(lowered_inst, reloc_info); + try emit.encodeInst(lowered_inst, reloc_info); continue :lowered_inst; }, else => {}, @@ -290,37 +281,6 @@ pub fn emitMir(emit: *Emit) Error!void { }, emit.lower.target), reloc_info), else => unreachable, } - } else if (emit.bin_file.cast(.coff)) |_| { - if (reloc.target.is_extern) switch (lowered_inst.encoding.mnemonic) { - .lea => try emit.encodeInst(try .new(.none, .mov, &.{ - lowered_inst.ops[0], - .{ .mem = .initRip(.ptr, 0) }, - }, emit.lower.target), reloc_info), - .mov => { - const dst_reg = lowered_inst.ops[0].reg.to64(); - try emit.encodeInst(try .new(.none, .mov, &.{ - .{ .reg = dst_reg }, - .{ .mem = .initRip(.ptr, 0) }, - }, emit.lower.target), reloc_info); - try emit.encodeInst(try .new(.none, .mov, &.{ - lowered_inst.ops[0], - .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{ - .reg = dst_reg, - } }) }, - }, emit.lower.target), &.{}); - }, - else => unreachable, - } else switch (lowered_inst.encoding.mnemonic) { - .lea => try emit.encodeInst(try .new(.none, .lea, &.{ - lowered_inst.ops[0], - .{ .mem = .initRip(.none, 0) }, - }, emit.lower.target), reloc_info), - .mov => try emit.encodeInst(try .new(.none, .mov, &.{ - lowered_inst.ops[0], - .{ .mem = .initRip(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, 0) }, - }, emit.lower.target), reloc_info), - else => unreachable, - } } else if (emit.bin_file.cast(.coff2)) |_| { switch (lowered_inst.encoding.mnemonic) { .lea => try emit.encodeInst(try .new(.none, .lea, &.{ @@ -820,22 +780,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI @enumFromInt(reloc.target.index), reloc.off, .{ .X86_64 = .@"32" }, - ) else if (emit.bin_file.cast(.coff)) |coff_file| { - const atom_index = coff_file.getAtomIndexForSymbol( - .{ .sym_index = emit.atom_index, .file = null }, - ).?; - try coff_file.addRelocation(atom_index, .{ - .type = if (reloc.target.is_extern) .got else .direct, - .target = if (reloc.target.is_extern) - coff_file.getGlobalByIndex(reloc.target.index) - else - .{ .sym_index = reloc.target.index, .file = null }, - .offset = end_offset - 4, - .addend = @intCast(reloc.off), - .pcrel = true, - .length = 2, - }); - } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( + ) else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( @enumFromInt(emit.atom_index), end_offset - 4, @enumFromInt(reloc.target.index), @@ -873,21 +818,6 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI .symbolnum = @intCast(reloc.target.index), }, }); - } else if (emit.bin_file.cast(.coff)) |coff_file| { - const atom_index = coff_file.getAtomIndexForSymbol( - .{ .sym_index = emit.atom_index, .file = null }, - ).?; - try coff_file.addRelocation(atom_index, .{ - .type = if (reloc.target.is_extern) .import else .got, - .target = if (reloc.target.is_extern) - coff_file.getGlobalByIndex(reloc.target.index) - else - .{ .sym_index = reloc.target.index, .file = null }, - .offset = end_offset - 4, - .addend = @intCast(reloc.off), - .pcrel = true, - .length = 2, - }); } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( @enumFromInt(emit.atom_index), end_offset - 4, diff --git a/src/link.zig b/src/link.zig index df9c152aef..e876e8677a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -574,16 +574,13 @@ pub const File = struct { const gpa = comp.gpa; switch (base.tag) { .lld => assert(base.file == null), - .coff, .elf, .macho, .wasm, .goff, .xcoff => { + .elf, .macho, .wasm, .goff, .xcoff => { if (base.file != null) return; dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker }); const emit = base.emit; if (base.child_pid) |pid| { if (builtin.os.tag == .windows) { - const coff_file = base.cast(.coff).?; - coff_file.ptraceAttach(pid) catch |err| { - log.warn("attaching failed with error: {s}", .{@errorName(err)}); - }; + return error.HotSwapUnavailableOnHostOperatingSystem; } else { // If we try to open the output file in write mode while it is running, // it will return ETXTBSY. So instead, we copy the file, atomically rename it @@ -671,7 +668,7 @@ pub const File = struct { } } }, - .coff, .macho, .wasm, .goff, .xcoff => if (base.file) |f| { + .macho, .wasm, .goff, .xcoff => if (base.file) |f| { dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker }); f.close(); base.file = null; @@ -684,10 +681,6 @@ pub const File = struct { log.warn("detaching failed with error: {s}", .{@errorName(err)}); }; }, - .windows => { - const coff_file = base.cast(.coff).?; - coff_file.ptraceDetach(pid); - }, else => return error.HotSwapUnavailableOnHostOperatingSystem, } } @@ -1157,7 +1150,6 @@ pub const File = struct { } pub const Tag = enum { - coff, coff2, elf, elf2, @@ -1172,7 +1164,6 @@ pub const File = struct { pub fn Type(comptime tag: Tag) type { return switch (tag) { - .coff => Coff, .coff2 => Coff2, .elf => Elf, .elf2 => Elf2, @@ -1189,7 +1180,7 @@ pub const File = struct { fn fromObjectFormat(ofmt: std.Target.ObjectFormat, use_new_linker: bool) Tag { return switch (ofmt) { - .coff => if (use_new_linker) .coff2 else .coff, + .coff => .coff2, .elf => if (use_new_linker) .elf2 else .elf, .macho => .macho, .wasm => .wasm, @@ -1274,7 +1265,6 @@ pub const File = struct { pub const Lld = @import("link/Lld.zig"); pub const C = @import("link/C.zig"); - pub const Coff = @import("link/Coff.zig"); pub const Coff2 = @import("link/Coff2.zig"); pub const Elf = @import("link/Elf.zig"); pub const Elf2 = @import("link/Elf2.zig"); diff --git a/src/link/Coff.zig b/src/link/Coff.zig deleted file mode 100644 index 8ec6e1c5ab..0000000000 --- a/src/link/Coff.zig +++ /dev/null @@ -1,3169 +0,0 @@ -//! The main driver of the self-hosted COFF linker. -const Coff = @This(); - -const std = @import("std"); -const build_options = @import("build_options"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const coff_util = std.coff; -const fmt = std.fmt; -const fs = std.fs; -const log = std.log.scoped(.link); -const math = std.math; -const mem = std.mem; - -const Allocator = std.mem.Allocator; -const Path = std.Build.Cache.Path; -const Directory = std.Build.Cache.Directory; -const Cache = std.Build.Cache; - -const aarch64_util = link.aarch64; -const allocPrint = std.fmt.allocPrint; -const codegen = @import("../codegen.zig"); -const link = @import("../link.zig"); -const target_util = @import("../target.zig"); -const trace = @import("../tracy.zig").trace; - -const Compilation = @import("../Compilation.zig"); -const Zcu = @import("../Zcu.zig"); -const InternPool = @import("../InternPool.zig"); -const TableSection = @import("table_section.zig").TableSection; -const StringTable = @import("StringTable.zig"); -const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); -const AnalUnit = InternPool.AnalUnit; -const dev = @import("../dev.zig"); - -base: link.File, -image_base: u64, -/// TODO this and minor_subsystem_version should be combined into one property and left as -/// default or populated together. They should not be separate fields. -major_subsystem_version: u16, -minor_subsystem_version: u16, -entry: link.File.OpenOptions.Entry, -entry_addr: ?u32, -module_definition_file: ?[]const u8, -repro: bool, - -ptr_width: PtrWidth, -page_size: u32, - -sections: std.MultiArrayList(Section) = .{}, -data_directories: [coff_util.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]coff_util.ImageDataDirectory, - -text_section_index: ?u16 = null, -got_section_index: ?u16 = null, -rdata_section_index: ?u16 = null, -data_section_index: ?u16 = null, -reloc_section_index: ?u16 = null, -idata_section_index: ?u16 = null, - -locals: std.ArrayListUnmanaged(coff_util.Symbol) = .empty, -globals: std.ArrayListUnmanaged(SymbolWithLoc) = .empty, -resolver: std.StringHashMapUnmanaged(u32) = .empty, -unresolved: std.AutoArrayHashMapUnmanaged(u32, bool) = .empty, -need_got_table: std.AutoHashMapUnmanaged(u32, void) = .empty, - -locals_free_list: std.ArrayListUnmanaged(u32) = .empty, -globals_free_list: std.ArrayListUnmanaged(u32) = .empty, - -strtab: StringTable = .{}, -strtab_offset: ?u32 = null, - -temp_strtab: StringTable = .{}, - -got_table: TableSection(SymbolWithLoc) = .{}, - -/// A table of ImportTables partitioned by the library name. -/// Key is an offset into the interning string table `temp_strtab`. -import_tables: std.AutoArrayHashMapUnmanaged(u32, ImportTable) = .empty, - -got_table_count_dirty: bool = true, -got_table_contents_dirty: bool = true, -imports_count_dirty: bool = true, - -/// Table of tracked LazySymbols. -lazy_syms: LazySymbolTable = .{}, - -/// Table of tracked `Nav`s. -navs: NavTable = .{}, - -/// List of atoms that are either synthetic or map directly to the Zig source program. -atoms: std.ArrayListUnmanaged(Atom) = .empty, - -/// Table of atoms indexed by the symbol index. -atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .empty, - -uavs: UavTable = .{}, - -/// A table of relocations indexed by the owning them `Atom`. -/// Note that once we refactor `Atom`'s lifetime and ownership rules, -/// this will be a table indexed by index into the list of Atoms. -relocs: RelocTable = .{}, - -/// A table of base relocations indexed by the owning them `Atom`. -/// Note that once we refactor `Atom`'s lifetime and ownership rules, -/// this will be a table indexed by index into the list of Atoms. -base_relocs: BaseRelocationTable = .{}, - -/// Hot-code swapping state. -hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, - -const is_hot_update_compatible = switch (builtin.target.os.tag) { - .windows => true, - else => false, -}; - -const HotUpdateState = struct { - /// Base address at which the process (image) got loaded. - /// We need this info to correctly slide pointers when relocating. - loaded_base_address: ?std.os.windows.HMODULE = null, -}; - -const NavTable = std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, AvMetadata); -const UavTable = std.AutoHashMapUnmanaged(InternPool.Index, AvMetadata); -const RelocTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation)); -const BaseRelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32)); - -pub const default_file_alignment: u16 = 0x200; -pub const default_size_of_stack_reserve: u32 = 0x1000000; -pub const default_size_of_stack_commit: u32 = 0x1000; -pub const default_size_of_heap_reserve: u32 = 0x100000; -pub const default_size_of_heap_commit: u32 = 0x1000; - -const Section = struct { - header: coff_util.SectionHeader, - - last_atom_index: ?Atom.Index = null, - - /// A list of atoms that have surplus capacity. This list can have false - /// positives, as functions grow and shrink over time, only sometimes being added - /// or removed from the freelist. - /// - /// An atom has surplus capacity when its overcapacity value is greater than - /// padToIdeal(minimum_atom_size). That is, when it has so - /// much extra capacity, that we could fit a small new symbol in it, itself with - /// ideal_capacity or more. - /// - /// Ideal capacity is defined by size + (size / ideal_factor). - /// - /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that - /// overcapacity can be negative. A simple way to have negative overcapacity is to - /// allocate a fresh atom, which will have ideal capacity, and then grow it - /// by 1 byte. It will then have -1 overcapacity. - free_list: std.ArrayListUnmanaged(Atom.Index) = .empty, -}; - -const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.Index, LazySymbolMetadata); - -const LazySymbolMetadata = struct { - const State = enum { unused, pending_flush, flushed }; - text_atom: Atom.Index = undefined, - rdata_atom: Atom.Index = undefined, - text_state: State = .unused, - rdata_state: State = .unused, -}; - -const AvMetadata = struct { - atom: Atom.Index, - section: u16, - /// A list of all exports aliases of this Decl. - exports: std.ArrayListUnmanaged(u32) = .empty, - - fn deinit(m: *AvMetadata, allocator: Allocator) void { - m.exports.deinit(allocator); - } - - fn getExport(m: AvMetadata, coff: *const Coff, name: []const u8) ?u32 { - for (m.exports.items) |exp| { - if (mem.eql(u8, name, coff.getSymbolName(.{ - .sym_index = exp, - .file = null, - }))) return exp; - } - return null; - } - - fn getExportPtr(m: *AvMetadata, coff: *Coff, name: []const u8) ?*u32 { - for (m.exports.items) |*exp| { - if (mem.eql(u8, name, coff.getSymbolName(.{ - .sym_index = exp.*, - .file = null, - }))) return exp; - } - return null; - } -}; - -pub const PtrWidth = enum { - p32, - p64, - - /// Size in bytes. - pub fn size(pw: PtrWidth) u4 { - return switch (pw) { - .p32 => 4, - .p64 => 8, - }; - } -}; - -pub const SymbolWithLoc = struct { - // Index into the respective symbol table. - sym_index: u32, - - // null means it's a synthetic global or Zig source. - file: ?u32 = null, - - pub fn eql(this: SymbolWithLoc, other: SymbolWithLoc) bool { - if (this.file == null and other.file == null) { - return this.sym_index == other.sym_index; - } - if (this.file != null and other.file != null) { - return this.sym_index == other.sym_index and this.file.? == other.file.?; - } - return false; - } -}; - -/// When allocating, the ideal_capacity is calculated by -/// actual_capacity + (actual_capacity / ideal_factor) -const ideal_factor = 3; - -/// In order for a slice of bytes to be considered eligible to keep metadata pointing at -/// it as a possible place to put new symbols, it must have enough room for this many bytes -/// (plus extra for reserved capacity). -const minimum_text_block_size = 64; -pub const min_text_capacity = padToIdeal(minimum_text_block_size); - -pub fn createEmpty( - arena: Allocator, - comp: *Compilation, - emit: Path, - options: link.File.OpenOptions, -) !*Coff { - const target = &comp.root_mod.resolved_target.result; - assert(target.ofmt == .coff); - const optimize_mode = comp.root_mod.optimize_mode; - const output_mode = comp.config.output_mode; - const link_mode = comp.config.link_mode; - const use_llvm = comp.config.use_llvm; - - const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { - 0...32 => .p32, - 33...64 => .p64, - else => return error.UnsupportedCOFFArchitecture, - }; - const page_size: u32 = switch (target.cpu.arch) { - else => 0x1000, - }; - - const coff = try arena.create(Coff); - coff.* = .{ - .base = .{ - .tag = .coff, - .comp = comp, - .emit = emit, - .zcu_object_basename = if (use_llvm) - try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)}) - else - null, - .stack_size = options.stack_size orelse 16777216, - .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), - .print_gc_sections = options.print_gc_sections, - .allow_shlib_undefined = options.allow_shlib_undefined orelse false, - .file = null, - .build_id = options.build_id, - }, - .ptr_width = ptr_width, - .page_size = page_size, - - .data_directories = [1]coff_util.ImageDataDirectory{.{ - .virtual_address = 0, - .size = 0, - }} ** coff_util.IMAGE_NUMBEROF_DIRECTORY_ENTRIES, - - .image_base = options.image_base orelse switch (output_mode) { - .Exe => switch (target.cpu.arch) { - .aarch64, .x86_64 => 0x140000000, - .thumb, .x86 => 0x400000, - else => unreachable, - }, - .Lib => switch (target.cpu.arch) { - .aarch64, .x86_64 => 0x180000000, - .thumb, .x86 => 0x10000000, - else => unreachable, - }, - .Obj => 0, - }, - - .entry = options.entry, - - .major_subsystem_version = options.major_subsystem_version orelse 6, - .minor_subsystem_version = options.minor_subsystem_version orelse 0, - .entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse - return error.EntryAddressTooBig, - .module_definition_file = options.module_definition_file, - .repro = options.repro, - }; - errdefer coff.base.destroy(); - - coff.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ - .truncate = true, - .read = true, - .mode = link.File.determineMode(output_mode, link_mode), - }); - - const gpa = comp.gpa; - - try coff.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32)); - coff.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32)); - - try coff.temp_strtab.buffer.append(gpa, 0); - - // Index 0 is always a null symbol. - try coff.locals.append(gpa, .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = .UNDEFINED, - .type = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }); - - if (coff.text_section_index == null) { - const file_size: u32 = @intCast(options.program_code_size_hint); - coff.text_section_index = try coff.allocateSection(".text", file_size, .{ - .CNT_CODE = true, - .MEM_EXECUTE = true, - .MEM_READ = true, - }); - } - - if (coff.got_section_index == null) { - const file_size = @as(u32, @intCast(options.symbol_count_hint)) * coff.ptr_width.size(); - coff.got_section_index = try coff.allocateSection(".got", file_size, .{ - .CNT_INITIALIZED_DATA = true, - .MEM_READ = true, - }); - } - - if (coff.rdata_section_index == null) { - const file_size: u32 = coff.page_size; - coff.rdata_section_index = try coff.allocateSection(".rdata", file_size, .{ - .CNT_INITIALIZED_DATA = true, - .MEM_READ = true, - }); - } - - if (coff.data_section_index == null) { - const file_size: u32 = coff.page_size; - coff.data_section_index = try coff.allocateSection(".data", file_size, .{ - .CNT_INITIALIZED_DATA = true, - .MEM_READ = true, - .MEM_WRITE = true, - }); - } - - if (coff.idata_section_index == null) { - const file_size = @as(u32, @intCast(options.symbol_count_hint)) * coff.ptr_width.size(); - coff.idata_section_index = try coff.allocateSection(".idata", file_size, .{ - .CNT_INITIALIZED_DATA = true, - .MEM_READ = true, - }); - } - - if (coff.reloc_section_index == null) { - const file_size = @as(u32, @intCast(options.symbol_count_hint)) * @sizeOf(coff_util.BaseRelocation); - coff.reloc_section_index = try coff.allocateSection(".reloc", file_size, .{ - .CNT_INITIALIZED_DATA = true, - .MEM_DISCARDABLE = true, - .MEM_READ = true, - }); - } - - if (coff.strtab_offset == null) { - const file_size = @as(u32, @intCast(coff.strtab.buffer.items.len)); - coff.strtab_offset = coff.findFreeSpace(file_size, @alignOf(u32)); // 4bytes aligned seems like a good idea here - log.debug("found strtab free space 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + file_size }); - } - - { - // We need to find out what the max file offset is according to section headers. - // Otherwise, we may end up with an COFF binary with file size not matching the final section's - // offset + it's filesize. - // TODO I don't like this here one bit - var max_file_offset: u64 = 0; - for (coff.sections.items(.header)) |header| { - if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { - max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; - } - } - try coff.pwriteAll(&[_]u8{0}, max_file_offset); - } - - return coff; -} - -pub fn open( - arena: Allocator, - comp: *Compilation, - emit: Path, - options: link.File.OpenOptions, -) !*Coff { - // TODO: restore saved linker state, don't truncate the file, and - // participate in incremental compilation. - return createEmpty(arena, comp, emit, options); -} - -pub fn deinit(coff: *Coff) void { - const gpa = coff.base.comp.gpa; - - for (coff.sections.items(.free_list)) |*free_list| { - free_list.deinit(gpa); - } - coff.sections.deinit(gpa); - - coff.atoms.deinit(gpa); - coff.locals.deinit(gpa); - coff.globals.deinit(gpa); - - { - var it = coff.resolver.keyIterator(); - while (it.next()) |key_ptr| { - gpa.free(key_ptr.*); - } - coff.resolver.deinit(gpa); - } - - coff.unresolved.deinit(gpa); - coff.need_got_table.deinit(gpa); - coff.locals_free_list.deinit(gpa); - coff.globals_free_list.deinit(gpa); - coff.strtab.deinit(gpa); - coff.temp_strtab.deinit(gpa); - coff.got_table.deinit(gpa); - - for (coff.import_tables.values()) |*itab| { - itab.deinit(gpa); - } - coff.import_tables.deinit(gpa); - - coff.lazy_syms.deinit(gpa); - - for (coff.navs.values()) |*metadata| { - metadata.deinit(gpa); - } - coff.navs.deinit(gpa); - - coff.atom_by_index_table.deinit(gpa); - - { - var it = coff.uavs.iterator(); - while (it.next()) |entry| { - entry.value_ptr.exports.deinit(gpa); - } - coff.uavs.deinit(gpa); - } - - for (coff.relocs.values()) |*relocs| { - relocs.deinit(gpa); - } - coff.relocs.deinit(gpa); - - for (coff.base_relocs.values()) |*relocs| { - relocs.deinit(gpa); - } - coff.base_relocs.deinit(gpa); -} - -fn allocateSection(coff: *Coff, name: []const u8, size: u32, flags: coff_util.SectionHeader.Flags) !u16 { - const index = @as(u16, @intCast(coff.sections.slice().len)); - const off = coff.findFreeSpace(size, default_file_alignment); - // Memory is always allocated in sequence - // TODO: investigate if we can allocate .text last; this way it would never need to grow in memory! - const vaddr = blk: { - if (index == 0) break :blk coff.page_size; - const prev_header = coff.sections.items(.header)[index - 1]; - break :blk mem.alignForward(u32, prev_header.virtual_address + prev_header.virtual_size, coff.page_size); - }; - // We commit more memory than needed upfront so that we don't have to reallocate too soon. - const memsz = mem.alignForward(u32, size, coff.page_size) * 100; - log.debug("found {s} free space 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ - name, - off, - off + size, - vaddr, - vaddr + size, - }); - var header = coff_util.SectionHeader{ - .name = undefined, - .virtual_size = memsz, - .virtual_address = vaddr, - .size_of_raw_data = size, - .pointer_to_raw_data = off, - .pointer_to_relocations = 0, - .pointer_to_linenumbers = 0, - .number_of_relocations = 0, - .number_of_linenumbers = 0, - .flags = flags, - }; - const gpa = coff.base.comp.gpa; - try coff.setSectionName(&header, name); - try coff.sections.append(gpa, .{ .header = header }); - return index; -} - -fn growSection(coff: *Coff, sect_id: u32, needed_size: u32) !void { - const header = &coff.sections.items(.header)[sect_id]; - const maybe_last_atom_index = coff.sections.items(.last_atom_index)[sect_id]; - const sect_capacity = coff.allocatedSize(header.pointer_to_raw_data); - - if (needed_size > sect_capacity) { - const new_offset = coff.findFreeSpace(needed_size, default_file_alignment); - const current_size = if (maybe_last_atom_index) |last_atom_index| blk: { - const last_atom = coff.getAtom(last_atom_index); - const sym = last_atom.getSymbol(coff); - break :blk (sym.value + last_atom.size) - header.virtual_address; - } else 0; - log.debug("moving {s} from 0x{x} to 0x{x}", .{ - coff.getSectionName(header), - header.pointer_to_raw_data, - new_offset, - }); - const amt = try coff.base.file.?.copyRangeAll( - header.pointer_to_raw_data, - coff.base.file.?, - new_offset, - current_size, - ); - if (amt != current_size) return error.InputOutput; - header.pointer_to_raw_data = new_offset; - } - - const sect_vm_capacity = coff.allocatedVirtualSize(header.virtual_address); - if (needed_size > sect_vm_capacity) { - coff.markRelocsDirtyByAddress(header.virtual_address + header.virtual_size); - try coff.growSectionVirtualMemory(sect_id, needed_size); - } - - header.virtual_size = @max(header.virtual_size, needed_size); - header.size_of_raw_data = needed_size; -} - -fn growSectionVirtualMemory(coff: *Coff, sect_id: u32, needed_size: u32) !void { - const header = &coff.sections.items(.header)[sect_id]; - const increased_size = padToIdeal(needed_size); - const old_aligned_end = header.virtual_address + mem.alignForward(u32, header.virtual_size, coff.page_size); - const new_aligned_end = header.virtual_address + mem.alignForward(u32, increased_size, coff.page_size); - const diff = new_aligned_end - old_aligned_end; - log.debug("growing {s} in virtual memory by {x}", .{ coff.getSectionName(header), diff }); - - // TODO: enforce order by increasing VM addresses in coff.sections container. - // This is required by the loader anyhow as far as I can tell. - for (coff.sections.items(.header)[sect_id + 1 ..], 0..) |*next_header, next_sect_id| { - const maybe_last_atom_index = coff.sections.items(.last_atom_index)[sect_id + 1 + next_sect_id]; - next_header.virtual_address += diff; - - if (maybe_last_atom_index) |last_atom_index| { - var atom_index = last_atom_index; - while (true) { - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbolPtr(coff); - sym.value += diff; - - if (atom.prev_index) |prev_index| { - atom_index = prev_index; - } else break; - } - } - } - - header.virtual_size = increased_size; -} - -fn allocateAtom(coff: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignment: u32) !u32 { - const tracy = trace(@src()); - defer tracy.end(); - - const atom = coff.getAtom(atom_index); - const sect_id = @intFromEnum(atom.getSymbol(coff).section_number) - 1; - const header = &coff.sections.items(.header)[sect_id]; - const free_list = &coff.sections.items(.free_list)[sect_id]; - const maybe_last_atom_index = &coff.sections.items(.last_atom_index)[sect_id]; - const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size; - - // We use these to indicate our intention to update metadata, placing the new atom, - // and possibly removing a free list node. - // It would be simpler to do it inside the for loop below, but that would cause a - // problem if an error was returned later in the function. So this action - // is actually carried out at the end of the function, when errors are no longer possible. - var atom_placement: ?Atom.Index = null; - var free_list_removal: ?usize = null; - - // First we look for an appropriately sized free list node. - // The list is unordered. We'll just take the first thing that works. - const vaddr = blk: { - var i: usize = 0; - while (i < free_list.items.len) { - const big_atom_index = free_list.items[i]; - const big_atom = coff.getAtom(big_atom_index); - // We now have a pointer to a live atom that has too much capacity. - // Is it enough that we could fit this new atom? - const sym = big_atom.getSymbol(coff); - const capacity = big_atom.capacity(coff); - const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; - const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity; - const capacity_end_vaddr = sym.value + capacity; - const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; - const new_start_vaddr = mem.alignBackward(u32, new_start_vaddr_unaligned, alignment); - if (new_start_vaddr < ideal_capacity_end_vaddr) { - // Additional bookkeeping here to notice if this free list node - // should be deleted because the atom that it points to has grown to take up - // more of the extra capacity. - if (!big_atom.freeListEligible(coff)) { - _ = free_list.swapRemove(i); - } else { - i += 1; - } - continue; - } - // At this point we know that we will place the new atom here. But the - // remaining question is whether there is still yet enough capacity left - // over for there to still be a free list node. - const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; - const keep_free_list_node = remaining_capacity >= min_text_capacity; - - // Set up the metadata to be updated, after errors are no longer possible. - atom_placement = big_atom_index; - if (!keep_free_list_node) { - free_list_removal = i; - } - break :blk new_start_vaddr; - } else if (maybe_last_atom_index.*) |last_index| { - const last = coff.getAtom(last_index); - const last_symbol = last.getSymbol(coff); - const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; - const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity; - const new_start_vaddr = mem.alignForward(u32, ideal_capacity_end_vaddr, alignment); - atom_placement = last_index; - break :blk new_start_vaddr; - } else { - break :blk mem.alignForward(u32, header.virtual_address, alignment); - } - }; - - const expand_section = if (atom_placement) |placement_index| - coff.getAtom(placement_index).next_index == null - else - true; - if (expand_section) { - const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address; - try coff.growSection(sect_id, needed_size); - maybe_last_atom_index.* = atom_index; - } - coff.getAtomPtr(atom_index).size = new_atom_size; - - if (atom.prev_index) |prev_index| { - const prev = coff.getAtomPtr(prev_index); - prev.next_index = atom.next_index; - } - if (atom.next_index) |next_index| { - const next = coff.getAtomPtr(next_index); - next.prev_index = atom.prev_index; - } - - if (atom_placement) |big_atom_index| { - const big_atom = coff.getAtomPtr(big_atom_index); - const atom_ptr = coff.getAtomPtr(atom_index); - atom_ptr.prev_index = big_atom_index; - atom_ptr.next_index = big_atom.next_index; - big_atom.next_index = atom_index; - } else { - const atom_ptr = coff.getAtomPtr(atom_index); - atom_ptr.prev_index = null; - atom_ptr.next_index = null; - } - if (free_list_removal) |i| { - _ = free_list.swapRemove(i); - } - - return vaddr; -} - -pub fn allocateSymbol(coff: *Coff) !u32 { - const gpa = coff.base.comp.gpa; - try coff.locals.ensureUnusedCapacity(gpa, 1); - - const index = blk: { - if (coff.locals_free_list.pop()) |index| { - log.debug(" (reusing symbol index {d})", .{index}); - break :blk index; - } else { - log.debug(" (allocating symbol index {d})", .{coff.locals.items.len}); - const index = @as(u32, @intCast(coff.locals.items.len)); - _ = coff.locals.addOneAssumeCapacity(); - break :blk index; - } - }; - - coff.locals.items[index] = .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = .UNDEFINED, - .type = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }; - - return index; -} - -fn allocateGlobal(coff: *Coff) !u32 { - const gpa = coff.base.comp.gpa; - try coff.globals.ensureUnusedCapacity(gpa, 1); - - const index = blk: { - if (coff.globals_free_list.pop()) |index| { - log.debug(" (reusing global index {d})", .{index}); - break :blk index; - } else { - log.debug(" (allocating global index {d})", .{coff.globals.items.len}); - const index = @as(u32, @intCast(coff.globals.items.len)); - _ = coff.globals.addOneAssumeCapacity(); - break :blk index; - } - }; - - coff.globals.items[index] = .{ - .sym_index = 0, - .file = null, - }; - - return index; -} - -fn addGotEntry(coff: *Coff, target: SymbolWithLoc) !void { - const gpa = coff.base.comp.gpa; - if (coff.got_table.lookup.contains(target)) return; - const got_index = try coff.got_table.allocateEntry(gpa, target); - try coff.writeOffsetTableEntry(got_index); - coff.got_table_count_dirty = true; - coff.markRelocsDirtyByTarget(target); -} - -pub fn createAtom(coff: *Coff) !Atom.Index { - const gpa = coff.base.comp.gpa; - const atom_index = @as(Atom.Index, @intCast(coff.atoms.items.len)); - const atom = try coff.atoms.addOne(gpa); - const sym_index = try coff.allocateSymbol(); - try coff.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index); - atom.* = .{ - .sym_index = sym_index, - .file = null, - .size = 0, - .prev_index = null, - .next_index = null, - }; - log.debug("creating ATOM(%{d}) at index {d}", .{ sym_index, atom_index }); - return atom_index; -} - -fn growAtom(coff: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignment: u32) !u32 { - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbol(coff); - const align_ok = mem.alignBackward(u32, sym.value, alignment) == sym.value; - const need_realloc = !align_ok or new_atom_size > atom.capacity(coff); - if (!need_realloc) return sym.value; - return coff.allocateAtom(atom_index, new_atom_size, alignment); -} - -fn shrinkAtom(coff: *Coff, atom_index: Atom.Index, new_block_size: u32) void { - _ = coff; - _ = atom_index; - _ = new_block_size; - // TODO check the new capacity, and if it crosses the size threshold into a big enough - // capacity, insert a free list node for it. -} - -fn writeAtom(coff: *Coff, atom_index: Atom.Index, code: []u8, resolve_relocs: bool) !void { - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbol(coff); - const section = coff.sections.get(@intFromEnum(sym.section_number) - 1); - const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; - - log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{ - atom.getName(coff), - file_offset, - file_offset + code.len, - }); - - const gpa = coff.base.comp.gpa; - - // Gather relocs which can be resolved. - // We need to do this as we will be applying different slide values depending - // if we are running in hot-code swapping mode or not. - // TODO: how crazy would it be to try and apply the actual image base of the loaded - // process for the in-file values rather than the Windows defaults? - var relocs = std.array_list.Managed(*Relocation).init(gpa); - defer relocs.deinit(); - - if (resolve_relocs) { - if (coff.relocs.getPtr(atom_index)) |rels| { - try relocs.ensureTotalCapacityPrecise(rels.items.len); - for (rels.items) |*reloc| { - if (reloc.isResolvable(coff) and reloc.dirty) { - relocs.appendAssumeCapacity(reloc); - } - } - } - } - - if (is_hot_update_compatible) { - if (coff.base.child_pid) |handle| { - const slide = @intFromPtr(coff.hot_state.loaded_base_address.?); - - const mem_code = try gpa.dupe(u8, code); - defer gpa.free(mem_code); - coff.resolveRelocs(atom_index, relocs.items, mem_code, slide); - - const vaddr = sym.value + slide; - const pvaddr = @as(*anyopaque, @ptrFromInt(vaddr)); - - log.debug("writing to memory at address {x}", .{vaddr}); - - if (build_options.enable_logging) { - try debugMem(gpa, handle, pvaddr, mem_code); - } - - if (!section.header.flags.MEM_WRITE) { - writeMemProtected(handle, pvaddr, mem_code) catch |err| { - log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); - }; - } else { - writeMem(handle, pvaddr, mem_code) catch |err| { - log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); - }; - } - } - } - - if (resolve_relocs) { - coff.resolveRelocs(atom_index, relocs.items, code, coff.image_base); - } - try coff.pwriteAll(code, file_offset); - if (resolve_relocs) { - // Now we can mark the relocs as resolved. - while (relocs.pop()) |reloc| { - reloc.dirty = false; - } - } -} - -fn debugMem(allocator: Allocator, handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { - const buffer = try allocator.alloc(u8, code.len); - defer allocator.free(buffer); - const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer); - log.debug("to write: {x}", .{code}); - log.debug("in memory: {x}", .{memread}); -} - -fn writeMemProtected(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { - const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY); - try writeMem(handle, pvaddr, code); - // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes. - // For that though, we want to track which page has already been modified. - _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot); -} - -fn writeMem(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { - const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code); - if (amt != code.len) return error.InputOutput; -} - -fn writeOffsetTableEntry(coff: *Coff, index: usize) !void { - const sect_id = coff.got_section_index.?; - - if (coff.got_table_count_dirty) { - const needed_size: u32 = @intCast(coff.got_table.entries.items.len * coff.ptr_width.size()); - try coff.growSection(sect_id, needed_size); - coff.got_table_count_dirty = false; - } - - const header = &coff.sections.items(.header)[sect_id]; - const entry = coff.got_table.entries.items[index]; - const entry_value = coff.getSymbol(entry).value; - const entry_offset = index * coff.ptr_width.size(); - const file_offset = header.pointer_to_raw_data + entry_offset; - const vmaddr = header.virtual_address + entry_offset; - - log.debug("writing GOT entry {d}: @{x} => {x}", .{ index, vmaddr, entry_value + coff.image_base }); - - switch (coff.ptr_width) { - .p32 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(entry_value + coff.image_base), .little); - try coff.base.file.?.pwriteAll(&buf, file_offset); - }, - .p64 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, entry_value + coff.image_base, .little); - try coff.base.file.?.pwriteAll(&buf, file_offset); - }, - } - - if (is_hot_update_compatible) { - if (coff.base.child_pid) |handle| { - const gpa = coff.base.comp.gpa; - const slide = @intFromPtr(coff.hot_state.loaded_base_address.?); - const actual_vmaddr = vmaddr + slide; - const pvaddr = @as(*anyopaque, @ptrFromInt(actual_vmaddr)); - log.debug("writing GOT entry to memory at address {x}", .{actual_vmaddr}); - if (build_options.enable_logging) { - switch (coff.ptr_width) { - .p32 => { - var buf: [4]u8 = undefined; - try debugMem(gpa, handle, pvaddr, &buf); - }, - .p64 => { - var buf: [8]u8 = undefined; - try debugMem(gpa, handle, pvaddr, &buf); - }, - } - } - - switch (coff.ptr_width) { - .p32 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @as(u32, @intCast(entry_value + slide)), .little); - writeMem(handle, pvaddr, &buf) catch |err| { - log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); - }; - }, - .p64 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, entry_value + slide, .little); - writeMem(handle, pvaddr, &buf) catch |err| { - log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); - }; - }, - } - } - } -} - -fn markRelocsDirtyByTarget(coff: *Coff, target: SymbolWithLoc) void { - if (!coff.base.comp.config.incremental) return; - // TODO: reverse-lookup might come in handy here - for (coff.relocs.values()) |*relocs| { - for (relocs.items) |*reloc| { - if (!reloc.target.eql(target)) continue; - reloc.dirty = true; - } - } -} - -fn markRelocsDirtyByAddress(coff: *Coff, addr: u32) void { - if (!coff.base.comp.config.incremental) return; - const got_moved = blk: { - const sect_id = coff.got_section_index orelse break :blk false; - break :blk coff.sections.items(.header)[sect_id].virtual_address >= addr; - }; - - // TODO: dirty relocations targeting import table if that got moved in memory - - for (coff.relocs.values()) |*relocs| { - for (relocs.items) |*reloc| { - if (reloc.isGotIndirection()) { - reloc.dirty = reloc.dirty or got_moved; - } else { - const target_vaddr = reloc.getTargetAddress(coff) orelse continue; - if (target_vaddr >= addr) reloc.dirty = true; - } - } - } - - // TODO: dirty only really affected GOT cells - for (coff.got_table.entries.items) |entry| { - const target_addr = coff.getSymbol(entry).value; - if (target_addr >= addr) { - coff.got_table_contents_dirty = true; - break; - } - } -} - -fn resolveRelocs(coff: *Coff, atom_index: Atom.Index, relocs: []const *const Relocation, code: []u8, image_base: u64) void { - log.debug("relocating '{s}'", .{coff.getAtom(atom_index).getName(coff)}); - for (relocs) |reloc| { - reloc.resolve(atom_index, code, image_base, coff); - } -} - -pub fn ptraceAttach(coff: *Coff, handle: std.process.Child.Id) !void { - if (!is_hot_update_compatible) return; - - log.debug("attaching to process with handle {*}", .{handle}); - coff.hot_state.loaded_base_address = std.os.windows.ProcessBaseAddress(handle) catch |err| { - log.warn("failed to get base address for the process with error: {s}", .{@errorName(err)}); - return; - }; -} - -pub fn ptraceDetach(coff: *Coff, handle: std.process.Child.Id) void { - if (!is_hot_update_compatible) return; - - log.debug("detaching from process with handle {*}", .{handle}); - coff.hot_state.loaded_base_address = null; -} - -fn freeAtom(coff: *Coff, atom_index: Atom.Index) void { - log.debug("freeAtom {d}", .{atom_index}); - - const gpa = coff.base.comp.gpa; - - // Remove any relocs and base relocs associated with this Atom - coff.freeRelocations(atom_index); - - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbol(coff); - const sect_id = @intFromEnum(sym.section_number) - 1; - const free_list = &coff.sections.items(.free_list)[sect_id]; - var already_have_free_list_node = false; - { - var i: usize = 0; - // TODO turn free_list into a hash map - while (i < free_list.items.len) { - if (free_list.items[i] == atom_index) { - _ = free_list.swapRemove(i); - continue; - } - if (free_list.items[i] == atom.prev_index) { - already_have_free_list_node = true; - } - i += 1; - } - } - - const maybe_last_atom_index = &coff.sections.items(.last_atom_index)[sect_id]; - if (maybe_last_atom_index.*) |last_atom_index| { - if (last_atom_index == atom_index) { - if (atom.prev_index) |prev_index| { - // TODO shrink the section size here - maybe_last_atom_index.* = prev_index; - } else { - maybe_last_atom_index.* = null; - } - } - } - - if (atom.prev_index) |prev_index| { - const prev = coff.getAtomPtr(prev_index); - prev.next_index = atom.next_index; - - if (!already_have_free_list_node and prev.*.freeListEligible(coff)) { - // The free list is heuristics, it doesn't have to be perfect, so we can - // ignore the OOM here. - free_list.append(gpa, prev_index) catch {}; - } - } else { - coff.getAtomPtr(atom_index).prev_index = null; - } - - if (atom.next_index) |next_index| { - coff.getAtomPtr(next_index).prev_index = atom.prev_index; - } else { - coff.getAtomPtr(atom_index).next_index = null; - } - - // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. - const sym_index = atom.getSymbolIndex().?; - coff.locals_free_list.append(gpa, sym_index) catch {}; - - // Try freeing GOT atom if this decl had one - coff.got_table.freeEntry(gpa, .{ .sym_index = sym_index }); - - coff.locals.items[sym_index].section_number = .UNDEFINED; - _ = coff.atom_by_index_table.remove(sym_index); - log.debug(" adding local symbol index {d} to free list", .{sym_index}); - coff.getAtomPtr(atom_index).sym_index = 0; -} - -pub fn updateFunc( - coff: *Coff, - pt: Zcu.PerThread, - func_index: InternPool.Index, - mir: *const codegen.AnyMir, -) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .coff) { - @panic("Attempted to compile for object format that was disabled by build configuration"); - } - const tracy = trace(@src()); - defer tracy.end(); - - const zcu = pt.zcu; - const gpa = zcu.gpa; - const func = zcu.funcInfo(func_index); - const nav_index = func.owner_nav; - - const atom_index = try coff.getOrCreateAtomForNav(nav_index); - coff.freeRelocations(atom_index); - - coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?; - - var aw: std.Io.Writer.Allocating = .init(gpa); - defer aw.deinit(); - - codegen.emitFunction( - &coff.base, - pt, - zcu.navSrcLoc(nav_index), - func_index, - coff.getAtom(atom_index).getSymbolIndex().?, - mir, - &aw.writer, - .none, - ) catch |err| switch (err) { - error.WriteFailed => return error.OutOfMemory, - else => |e| return e, - }; - - try coff.updateNavCode(pt, nav_index, aw.written(), .FUNCTION); - - // Exports will be updated by `Zcu.processExports` after the update. -} - -const LowerConstResult = union(enum) { - ok: Atom.Index, - fail: *Zcu.ErrorMsg, -}; - -fn lowerConst( - coff: *Coff, - pt: Zcu.PerThread, - name: []const u8, - val: Value, - required_alignment: InternPool.Alignment, - sect_id: u16, - src_loc: Zcu.LazySrcLoc, -) !LowerConstResult { - const gpa = coff.base.comp.gpa; - - var aw: std.Io.Writer.Allocating = .init(gpa); - defer aw.deinit(); - - const atom_index = try coff.createAtom(); - const sym = coff.getAtom(atom_index).getSymbolPtr(coff); - try coff.setSymbolName(sym, name); - sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_id + 1)); - - try codegen.generateSymbol(&coff.base, pt, src_loc, val, &aw.writer, .{ - .atom_index = coff.getAtom(atom_index).getSymbolIndex().?, - }); - const code = aw.written(); - - const atom = coff.getAtomPtr(atom_index); - atom.size = @intCast(code.len); - atom.getSymbolPtr(coff).value = try coff.allocateAtom( - atom_index, - atom.size, - @intCast(required_alignment.toByteUnits().?), - ); - errdefer coff.freeAtom(atom_index); - - log.debug("allocated atom for {s} at 0x{x}", .{ name, atom.getSymbol(coff).value }); - log.debug(" (required alignment 0x{x})", .{required_alignment}); - - try coff.writeAtom(atom_index, code, coff.base.comp.config.incremental); - - return .{ .ok = atom_index }; -} - -pub fn updateNav( - coff: *Coff, - pt: Zcu.PerThread, - nav_index: InternPool.Nav.Index, -) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .coff) { - @panic("Attempted to compile for object format that was disabled by build configuration"); - } - const tracy = trace(@src()); - defer tracy.end(); - - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - - const nav_val = zcu.navValue(nav_index); - const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { - .func => return, - .variable => |variable| Value.fromInterned(variable.init), - .@"extern" => |@"extern"| { - if (ip.isFunctionType(@"extern".ty)) return; - // TODO make this part of getGlobalSymbol - const name = nav.name.toSlice(ip); - const lib_name = @"extern".lib_name.toSlice(ip); - const global_index = try coff.getGlobalSymbol(name, lib_name); - try coff.need_got_table.put(gpa, global_index, {}); - return; - }, - else => nav_val, - }; - - if (nav_init.typeOf(zcu).hasRuntimeBits(zcu)) { - const atom_index = try coff.getOrCreateAtomForNav(nav_index); - coff.freeRelocations(atom_index); - const atom = coff.getAtom(atom_index); - - coff.navs.getPtr(nav_index).?.section = coff.getNavOutputSection(nav_index); - - var aw: std.Io.Writer.Allocating = .init(gpa); - defer aw.deinit(); - - codegen.generateSymbol( - &coff.base, - pt, - zcu.navSrcLoc(nav_index), - nav_init, - &aw.writer, - .{ .atom_index = atom.getSymbolIndex().? }, - ) catch |err| switch (err) { - error.WriteFailed => return error.OutOfMemory, - else => |e| return e, - }; - - try coff.updateNavCode(pt, nav_index, aw.written(), .NULL); - } - - // Exports will be updated by `Zcu.processExports` after the update. -} - -fn updateLazySymbolAtom( - coff: *Coff, - pt: Zcu.PerThread, - sym: link.File.LazySymbol, - atom_index: Atom.Index, - section_index: u16, -) !void { - const zcu = pt.zcu; - const comp = coff.base.comp; - const gpa = comp.gpa; - - var required_alignment: InternPool.Alignment = .none; - var aw: std.Io.Writer.Allocating = .init(gpa); - defer aw.deinit(); - - const name = try allocPrint(gpa, "__lazy_{s}_{f}", .{ - @tagName(sym.kind), - Type.fromInterned(sym.ty).fmt(pt), - }); - defer gpa.free(name); - - const local_sym_index = coff.getAtomPtr(atom_index).getSymbolIndex().?; - - const src = Type.fromInterned(sym.ty).srcLocOrNull(zcu) orelse Zcu.LazySrcLoc.unneeded; - try codegen.generateLazySymbol( - &coff.base, - pt, - src, - sym, - &required_alignment, - &aw.writer, - .none, - .{ .atom_index = local_sym_index }, - ); - const code = aw.written(); - - const atom = coff.getAtomPtr(atom_index); - const symbol = atom.getSymbolPtr(coff); - try coff.setSymbolName(symbol, name); - symbol.section_number = @enumFromInt(section_index + 1); - symbol.type = .{ .complex_type = .NULL, .base_type = .NULL }; - - const code_len: u32 = @intCast(code.len); - const vaddr = try coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)); - errdefer coff.freeAtom(atom_index); - - log.debug("allocated atom for {s} at 0x{x}", .{ name, vaddr }); - log.debug(" (required alignment 0x{x})", .{required_alignment}); - - atom.size = code_len; - symbol.value = vaddr; - - try coff.addGotEntry(.{ .sym_index = local_sym_index }); - try coff.writeAtom(atom_index, code, coff.base.comp.config.incremental); -} - -pub fn getOrCreateAtomForLazySymbol( - coff: *Coff, - pt: Zcu.PerThread, - lazy_sym: link.File.LazySymbol, -) !Atom.Index { - const gop = try coff.lazy_syms.getOrPut(pt.zcu.gpa, lazy_sym.ty); - errdefer _ = if (!gop.found_existing) coff.lazy_syms.pop(); - if (!gop.found_existing) gop.value_ptr.* = .{}; - const atom_ptr, const state_ptr = switch (lazy_sym.kind) { - .code => .{ &gop.value_ptr.text_atom, &gop.value_ptr.text_state }, - .const_data => .{ &gop.value_ptr.rdata_atom, &gop.value_ptr.rdata_state }, - }; - switch (state_ptr.*) { - .unused => atom_ptr.* = try coff.createAtom(), - .pending_flush => return atom_ptr.*, - .flushed => {}, - } - state_ptr.* = .pending_flush; - const atom = atom_ptr.*; - // anyerror needs to be deferred until flush - if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) { - .code => coff.text_section_index.?, - .const_data => coff.rdata_section_index.?, - }); - return atom; -} - -pub fn getOrCreateAtomForNav(coff: *Coff, nav_index: InternPool.Nav.Index) !Atom.Index { - const gpa = coff.base.comp.gpa; - const gop = try coff.navs.getOrPut(gpa, nav_index); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .atom = try coff.createAtom(), - // If necessary, this will be modified by `updateNav` or `updateFunc`. - .section = coff.rdata_section_index.?, - .exports = .{}, - }; - } - return gop.value_ptr.atom; -} - -fn getNavOutputSection(coff: *Coff, nav_index: InternPool.Nav.Index) u16 { - const zcu = coff.base.comp.zcu.?; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - const ty = Type.fromInterned(nav.typeOf(ip)); - const zig_ty = ty.zigTypeTag(zcu); - const val = Value.fromInterned(nav.status.fully_resolved.val); - const index: u16 = blk: { - if (val.isUndef(zcu)) { - // TODO in release-fast and release-small, we should put undef in .bss - break :blk coff.data_section_index.?; - } - - switch (zig_ty) { - // TODO: what if this is a function pointer? - .@"fn" => break :blk coff.text_section_index.?, - else => { - if (val.getVariable(zcu)) |_| { - break :blk coff.data_section_index.?; - } - break :blk coff.rdata_section_index.?; - }, - } - }; - return index; -} - -fn updateNavCode( - coff: *Coff, - pt: Zcu.PerThread, - nav_index: InternPool.Nav.Index, - code: []u8, - complex_type: coff_util.ComplexType, -) link.File.UpdateNavError!void { - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - - log.debug("updateNavCode {f} 0x{x}", .{ nav.fqn.fmt(ip), nav_index }); - - const mod = zcu.navFileScope(nav_index).mod.?; - const target = &mod.resolved_target.result; - const required_alignment = switch (nav.status.fully_resolved.alignment) { - .none => switch (mod.optimize_mode) { - .Debug, .ReleaseSafe, .ReleaseFast => target_util.defaultFunctionAlignment(target), - .ReleaseSmall => target_util.minFunctionAlignment(target), - }, - else => |a| a.maxStrict(target_util.minFunctionAlignment(target)), - }; - - const nav_metadata = coff.navs.get(nav_index).?; - const atom_index = nav_metadata.atom; - const atom = coff.getAtom(atom_index); - const sym_index = atom.getSymbolIndex().?; - const sect_index = nav_metadata.section; - const code_len: u32 = @intCast(code.len); - - if (atom.size != 0) { - const sym = atom.getSymbolPtr(coff); - try coff.setSymbolName(sym, nav.fqn.toSlice(ip)); - sym.section_number = @enumFromInt(sect_index + 1); - sym.type = .{ .complex_type = complex_type, .base_type = .NULL }; - - const capacity = atom.capacity(coff); - const need_realloc = code.len > capacity or !required_alignment.check(sym.value); - if (need_realloc) { - const vaddr = coff.growAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.cgFail(nav_index, "failed to grow atom: {s}", .{@errorName(e)}), - }; - log.debug("growing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), sym.value, vaddr }); - log.debug(" (required alignment 0x{x}", .{required_alignment}); - - if (vaddr != sym.value) { - sym.value = vaddr; - log.debug(" (updating GOT entry)", .{}); - const got_entry_index = coff.got_table.lookup.get(.{ .sym_index = sym_index }).?; - coff.writeOffsetTableEntry(got_entry_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.cgFail(nav_index, "failed to write offset table entry: {s}", .{@errorName(e)}), - }; - coff.markRelocsDirtyByTarget(.{ .sym_index = sym_index }); - } - } else if (code_len < atom.size) { - coff.shrinkAtom(atom_index, code_len); - } - coff.getAtomPtr(atom_index).size = code_len; - } else { - const sym = atom.getSymbolPtr(coff); - try coff.setSymbolName(sym, nav.fqn.toSlice(ip)); - sym.section_number = @enumFromInt(sect_index + 1); - sym.type = .{ .complex_type = complex_type, .base_type = .NULL }; - - const vaddr = coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.cgFail(nav_index, "failed to allocate atom: {s}", .{@errorName(e)}), - }; - errdefer coff.freeAtom(atom_index); - log.debug("allocated atom for {f} at 0x{x}", .{ nav.fqn.fmt(ip), vaddr }); - coff.getAtomPtr(atom_index).size = code_len; - sym.value = vaddr; - - coff.addGotEntry(.{ .sym_index = sym_index }) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.cgFail(nav_index, "failed to add GOT entry: {s}", .{@errorName(e)}), - }; - } - - coff.writeAtom(atom_index, code, coff.base.comp.config.incremental) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.cgFail(nav_index, "failed to write atom: {s}", .{@errorName(e)}), - }; -} - -pub fn freeNav(coff: *Coff, nav_index: InternPool.NavIndex) void { - const gpa = coff.base.comp.gpa; - - if (coff.decls.fetchOrderedRemove(nav_index)) |const_kv| { - var kv = const_kv; - coff.freeAtom(kv.value.atom); - kv.value.exports.deinit(gpa); - } -} - -pub fn updateExports( - coff: *Coff, - pt: Zcu.PerThread, - exported: Zcu.Exported, - export_indices: []const Zcu.Export.Index, -) link.File.UpdateExportsError!void { - if (build_options.skip_non_native and builtin.object_format != .coff) { - @panic("Attempted to compile for object format that was disabled by build configuration"); - } - - const zcu = pt.zcu; - const gpa = zcu.gpa; - - const metadata = switch (exported) { - .nav => |nav| blk: { - _ = try coff.getOrCreateAtomForNav(nav); - break :blk coff.navs.getPtr(nav).?; - }, - .uav => |uav| coff.uavs.getPtr(uav) orelse blk: { - const first_exp = export_indices[0].ptr(zcu); - const res = try coff.lowerUav(pt, uav, .none, first_exp.src); - switch (res) { - .sym_index => {}, - .fail => |em| { - // TODO maybe it's enough to return an error here and let Module.processExportsInner - // handle the error? - try zcu.failed_exports.ensureUnusedCapacity(zcu.gpa, 1); - zcu.failed_exports.putAssumeCapacityNoClobber(export_indices[0], em); - return; - }, - } - break :blk coff.uavs.getPtr(uav).?; - }, - }; - const atom_index = metadata.atom; - const atom = coff.getAtom(atom_index); - - for (export_indices) |export_idx| { - const exp = export_idx.ptr(zcu); - log.debug("adding new export '{f}'", .{exp.opts.name.fmt(&zcu.intern_pool)}); - - if (exp.opts.section.toSlice(&zcu.intern_pool)) |section_name| { - if (!mem.eql(u8, section_name, ".text")) { - try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create( - gpa, - exp.src, - "Unimplemented: ExportOptions.section", - .{}, - )); - continue; - } - } - - if (exp.opts.linkage == .link_once) { - try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create( - gpa, - exp.src, - "Unimplemented: GlobalLinkage.link_once", - .{}, - )); - continue; - } - - const exp_name = exp.opts.name.toSlice(&zcu.intern_pool); - const sym_index = metadata.getExport(coff, exp_name) orelse blk: { - const sym_index = if (coff.getGlobalIndex(exp_name)) |global_index| ind: { - const global = coff.globals.items[global_index]; - // TODO this is just plain wrong as it all should happen in a single `resolveSymbols` - // pass. This will go away once we abstact away Zig's incremental compilation into - // its own module. - if (global.file == null and coff.getSymbol(global).section_number == .UNDEFINED) { - _ = coff.unresolved.swapRemove(global_index); - break :ind global.sym_index; - } - break :ind try coff.allocateSymbol(); - } else try coff.allocateSymbol(); - try metadata.exports.append(gpa, sym_index); - break :blk sym_index; - }; - const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; - const sym = coff.getSymbolPtr(sym_loc); - try coff.setSymbolName(sym, exp_name); - sym.value = atom.getSymbol(coff).value; - sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(metadata.section + 1)); - sym.type = atom.getSymbol(coff).type; - - sym.storage_class = switch (exp.opts.linkage) { - .internal => .EXTERNAL, - .strong => .EXTERNAL, - .weak => @panic("TODO WeakExternal"), - else => unreachable, - }; - - try coff.resolveGlobalSymbol(sym_loc); - } -} - -pub fn deleteExport( - coff: *Coff, - exported: Zcu.Exported, - name: InternPool.NullTerminatedString, -) void { - const metadata = switch (exported) { - .nav => |nav| coff.navs.getPtr(nav), - .uav => |uav| coff.uavs.getPtr(uav), - } orelse return; - const zcu = coff.base.comp.zcu.?; - const name_slice = name.toSlice(&zcu.intern_pool); - const sym_index = metadata.getExportPtr(coff, name_slice) orelse return; - - const gpa = coff.base.comp.gpa; - const sym_loc = SymbolWithLoc{ .sym_index = sym_index.*, .file = null }; - const sym = coff.getSymbolPtr(sym_loc); - log.debug("deleting export '{f}'", .{name.fmt(&zcu.intern_pool)}); - assert(sym.storage_class == .EXTERNAL and sym.section_number != .UNDEFINED); - sym.* = .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = .UNDEFINED, - .type = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }; - coff.locals_free_list.append(gpa, sym_index.*) catch {}; - - if (coff.resolver.fetchRemove(name_slice)) |entry| { - defer gpa.free(entry.key); - coff.globals_free_list.append(gpa, entry.value) catch {}; - coff.globals.items[entry.value] = .{ - .sym_index = 0, - .file = null, - }; - } - - sym_index.* = 0; -} - -fn resolveGlobalSymbol(coff: *Coff, current: SymbolWithLoc) !void { - const gpa = coff.base.comp.gpa; - const sym = coff.getSymbol(current); - const sym_name = coff.getSymbolName(current); - - const gop = try coff.getOrPutGlobalPtr(sym_name); - if (!gop.found_existing) { - gop.value_ptr.* = current; - if (sym.section_number == .UNDEFINED) { - try coff.unresolved.putNoClobber(gpa, coff.getGlobalIndex(sym_name).?, false); - } - return; - } - - log.debug("TODO finish resolveGlobalSymbols implementation", .{}); - - if (sym.section_number == .UNDEFINED) return; - - _ = coff.unresolved.swapRemove(coff.getGlobalIndex(sym_name).?); - - gop.value_ptr.* = current; -} - -pub fn flush( - coff: *Coff, - arena: Allocator, - tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, -) link.File.FlushError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const comp = coff.base.comp; - const diags = &comp.link_diags; - - switch (coff.base.comp.config.output_mode) { - .Exe, .Obj => {}, - .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), - } - - const sub_prog_node = prog_node.start("COFF Flush", 0); - defer sub_prog_node.end(); - - return flushInner(coff, arena, tid) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}), - }; -} - -fn flushInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { - _ = arena; - - const comp = coff.base.comp; - const gpa = comp.gpa; - const diags = &comp.link_diags; - - const pt: Zcu.PerThread = .activate( - comp.zcu orelse return diags.fail("linking without zig source is not yet implemented", .{}), - tid, - ); - defer pt.deactivate(); - - if (coff.lazy_syms.getPtr(.anyerror_type)) |metadata| { - // Most lazy symbols can be updated on first use, but - // anyerror needs to wait for everything to be flushed. - if (metadata.text_state != .unused) try coff.updateLazySymbolAtom( - pt, - .{ .kind = .code, .ty = .anyerror_type }, - metadata.text_atom, - coff.text_section_index.?, - ); - if (metadata.rdata_state != .unused) try coff.updateLazySymbolAtom( - pt, - .{ .kind = .const_data, .ty = .anyerror_type }, - metadata.rdata_atom, - coff.rdata_section_index.?, - ); - } - for (coff.lazy_syms.values()) |*metadata| { - if (metadata.text_state != .unused) metadata.text_state = .flushed; - if (metadata.rdata_state != .unused) metadata.rdata_state = .flushed; - } - - { - var it = coff.need_got_table.iterator(); - while (it.next()) |entry| { - const global = coff.globals.items[entry.key_ptr.*]; - try coff.addGotEntry(global); - } - } - - while (coff.unresolved.pop()) |entry| { - assert(entry.value); - const global = coff.globals.items[entry.key]; - const sym = coff.getSymbol(global); - const res = try coff.import_tables.getOrPut(gpa, sym.value); - const itable = res.value_ptr; - if (!res.found_existing) { - itable.* = .{}; - } - if (itable.lookup.contains(global)) continue; - // TODO: we could technically write the pointer placeholder for to-be-bound import here, - // but since this happens in flush, there is currently no point. - _ = try itable.addImport(gpa, global); - coff.imports_count_dirty = true; - } - - try coff.writeImportTables(); - - for (coff.relocs.keys(), coff.relocs.values()) |atom_index, relocs| { - const needs_update = for (relocs.items) |reloc| { - if (reloc.dirty) break true; - } else false; - - if (!needs_update) continue; - - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbol(coff); - const section = coff.sections.get(@intFromEnum(sym.section_number) - 1).header; - const file_offset = section.pointer_to_raw_data + sym.value - section.virtual_address; - - var code = std.array_list.Managed(u8).init(gpa); - defer code.deinit(); - try code.resize(math.cast(usize, atom.size) orelse return error.Overflow); - assert(atom.size > 0); - - const amt = try coff.base.file.?.preadAll(code.items, file_offset); - if (amt != code.items.len) return error.InputOutput; - - try coff.writeAtom(atom_index, code.items, true); - } - - // Update GOT if it got moved in memory. - if (coff.got_table_contents_dirty) { - for (coff.got_table.entries.items, 0..) |entry, i| { - if (!coff.got_table.lookup.contains(entry)) continue; - // TODO: write all in one go rather than incrementally. - try coff.writeOffsetTableEntry(i); - } - coff.got_table_contents_dirty = false; - } - - try coff.writeBaseRelocations(); - - if (coff.getEntryPoint()) |entry_sym_loc| { - coff.entry_addr = coff.getSymbol(entry_sym_loc).value; - } - - if (build_options.enable_logging) { - coff.logSymtab(); - coff.logImportTables(); - } - - try coff.writeStrtab(); - try coff.writeDataDirectoriesHeaders(); - try coff.writeSectionHeaders(); - - if (coff.entry_addr == null and comp.config.output_mode == .Exe) { - log.debug("flushing. no_entry_point_found = true\n", .{}); - diags.flags.no_entry_point_found = true; - } else { - log.debug("flushing. no_entry_point_found = false\n", .{}); - diags.flags.no_entry_point_found = false; - try coff.writeHeader(); - } - - assert(!coff.imports_count_dirty); - - // hack for stage2_x86_64 + coff - if (comp.compiler_rt_dyn_lib) |crt_file| { - const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ - std.fs.path.dirname(coff.base.emit.sub_path) orelse "", - std.fs.path.basename(crt_file.full_object_path.sub_path), - }); - defer gpa.free(compiler_rt_sub_path); - try crt_file.full_object_path.root_dir.handle.copyFile( - crt_file.full_object_path.sub_path, - coff.base.emit.root_dir.handle, - compiler_rt_sub_path, - .{}, - ); - } -} - -pub fn getNavVAddr( - coff: *Coff, - pt: Zcu.PerThread, - nav_index: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - log.debug("getNavVAddr {f}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = if (nav.getExtern(ip)) |e| - try coff.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip)) - else - coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?; - const atom_index = coff.getAtomIndexForSymbol(.{ - .sym_index = reloc_info.parent.atom_index, - .file = null, - }).?; - const target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; - try coff.addRelocation(atom_index, .{ - .type = .direct, - .target = target, - .offset = @as(u32, @intCast(reloc_info.offset)), - .addend = reloc_info.addend, - .pcrel = false, - .length = 3, - }); - try coff.addBaseRelocation(atom_index, @as(u32, @intCast(reloc_info.offset))); - - return 0; -} - -pub fn lowerUav( - coff: *Coff, - pt: Zcu.PerThread, - uav: InternPool.Index, - explicit_alignment: InternPool.Alignment, - src_loc: Zcu.LazySrcLoc, -) !codegen.SymbolResult { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const val = Value.fromInterned(uav); - const uav_alignment = switch (explicit_alignment) { - .none => val.typeOf(zcu).abiAlignment(zcu), - else => explicit_alignment, - }; - if (coff.uavs.get(uav)) |metadata| { - const atom = coff.getAtom(metadata.atom); - const existing_addr = atom.getSymbol(coff).value; - if (uav_alignment.check(existing_addr)) - return .{ .sym_index = atom.getSymbolIndex().? }; - } - - var name_buf: [32]u8 = undefined; - const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{ - @intFromEnum(uav), - }) catch unreachable; - const res = coff.lowerConst( - pt, - name, - val, - uav_alignment, - coff.rdata_section_index.?, - src_loc, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return .{ .fail = try Zcu.ErrorMsg.create( - gpa, - src_loc, - "lowerAnonDecl failed with error: {s}", - .{@errorName(e)}, - ) }, - }; - const atom_index = switch (res) { - .ok => |atom_index| atom_index, - .fail => |em| return .{ .fail = em }, - }; - try coff.uavs.put(gpa, uav, .{ - .atom = atom_index, - .section = coff.rdata_section_index.?, - }); - return .{ .sym_index = coff.getAtom(atom_index).getSymbolIndex().? }; -} - -pub fn getUavVAddr( - coff: *Coff, - uav: InternPool.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - const this_atom_index = coff.uavs.get(uav).?.atom; - const sym_index = coff.getAtom(this_atom_index).getSymbolIndex().?; - const atom_index = coff.getAtomIndexForSymbol(.{ - .sym_index = reloc_info.parent.atom_index, - .file = null, - }).?; - const target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; - try coff.addRelocation(atom_index, .{ - .type = .direct, - .target = target, - .offset = @as(u32, @intCast(reloc_info.offset)), - .addend = reloc_info.addend, - .pcrel = false, - .length = 3, - }); - try coff.addBaseRelocation(atom_index, @as(u32, @intCast(reloc_info.offset))); - - return 0; -} - -pub fn getGlobalSymbol(coff: *Coff, name: []const u8, lib_name_name: ?[]const u8) !u32 { - const gop = try coff.getOrPutGlobalPtr(name); - const global_index = coff.getGlobalIndex(name).?; - - if (gop.found_existing) { - return global_index; - } - - const sym_index = try coff.allocateSymbol(); - const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; - gop.value_ptr.* = sym_loc; - - const gpa = coff.base.comp.gpa; - const sym = coff.getSymbolPtr(sym_loc); - try coff.setSymbolName(sym, name); - sym.storage_class = .EXTERNAL; - - if (lib_name_name) |lib_name| { - // We repurpose the 'value' of the Symbol struct to store an offset into - // temporary string table where we will store the library name hint. - sym.value = try coff.temp_strtab.insert(gpa, lib_name); - } - - try coff.unresolved.putNoClobber(gpa, global_index, true); - - return global_index; -} - -pub fn updateLineNumber(coff: *Coff, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { - _ = coff; - _ = pt; - _ = ti_id; - log.debug("TODO implement updateLineNumber", .{}); -} - -/// TODO: note if we need to rewrite base relocations by dirtying any of the entries in the global table -/// TODO: note that .ABSOLUTE is used as padding within each block; we could use this fact to do -/// incremental updates and writes into the table instead of doing it all at once -fn writeBaseRelocations(coff: *Coff) !void { - const gpa = coff.base.comp.gpa; - - var page_table = std.AutoHashMap(u32, std.array_list.Managed(coff_util.BaseRelocation)).init(gpa); - defer { - var it = page_table.valueIterator(); - while (it.next()) |inner| { - inner.deinit(); - } - page_table.deinit(); - } - - { - var it = coff.base_relocs.iterator(); - while (it.next()) |entry| { - const atom_index = entry.key_ptr.*; - const atom = coff.getAtom(atom_index); - const sym = atom.getSymbol(coff); - const offsets = entry.value_ptr.*; - - for (offsets.items) |offset| { - const rva = sym.value + offset; - const page = mem.alignBackward(u32, rva, coff.page_size); - const gop = try page_table.getOrPut(page); - if (!gop.found_existing) { - gop.value_ptr.* = std.array_list.Managed(coff_util.BaseRelocation).init(gpa); - } - try gop.value_ptr.append(.{ - .offset = @as(u12, @intCast(rva - page)), - .type = .DIR64, - }); - } - } - - { - const header = &coff.sections.items(.header)[coff.got_section_index.?]; - for (coff.got_table.entries.items, 0..) |entry, index| { - if (!coff.got_table.lookup.contains(entry)) continue; - - const sym = coff.getSymbol(entry); - if (sym.section_number == .UNDEFINED) continue; - - const rva = @as(u32, @intCast(header.virtual_address + index * coff.ptr_width.size())); - const page = mem.alignBackward(u32, rva, coff.page_size); - const gop = try page_table.getOrPut(page); - if (!gop.found_existing) { - gop.value_ptr.* = std.array_list.Managed(coff_util.BaseRelocation).init(gpa); - } - try gop.value_ptr.append(.{ - .offset = @as(u12, @intCast(rva - page)), - .type = .DIR64, - }); - } - } - } - - // Sort pages by address. - var pages = try std.array_list.Managed(u32).initCapacity(gpa, page_table.count()); - defer pages.deinit(); - { - var it = page_table.keyIterator(); - while (it.next()) |page| { - pages.appendAssumeCapacity(page.*); - } - } - mem.sort(u32, pages.items, {}, std.sort.asc(u32)); - - var buffer = std.array_list.Managed(u8).init(gpa); - defer buffer.deinit(); - - for (pages.items) |page| { - const entries = page_table.getPtr(page).?; - // Pad to required 4byte alignment - if (!mem.isAlignedGeneric( - usize, - entries.items.len * @sizeOf(coff_util.BaseRelocation), - @sizeOf(u32), - )) { - try entries.append(.{ - .offset = 0, - .type = .ABSOLUTE, - }); - } - - const block_size = @as( - u32, - @intCast(entries.items.len * @sizeOf(coff_util.BaseRelocation) + @sizeOf(coff_util.BaseRelocationDirectoryEntry)), - ); - try buffer.ensureUnusedCapacity(block_size); - buffer.appendSliceAssumeCapacity(mem.asBytes(&coff_util.BaseRelocationDirectoryEntry{ - .page_rva = page, - .block_size = block_size, - })); - buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(entries.items)); - } - - const header = &coff.sections.items(.header)[coff.reloc_section_index.?]; - const needed_size = @as(u32, @intCast(buffer.items.len)); - try coff.growSection(coff.reloc_section_index.?, needed_size); - - try coff.pwriteAll(buffer.items, header.pointer_to_raw_data); - - coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.BASERELOC)] = .{ - .virtual_address = header.virtual_address, - .size = needed_size, - }; -} - -fn writeImportTables(coff: *Coff) !void { - if (coff.idata_section_index == null) return; - if (!coff.imports_count_dirty) return; - - const gpa = coff.base.comp.gpa; - - const ext = ".dll"; - const header = &coff.sections.items(.header)[coff.idata_section_index.?]; - - // Calculate needed size - var iat_size: u32 = 0; - var dir_table_size: u32 = @sizeOf(coff_util.ImportDirectoryEntry); // sentinel - var lookup_table_size: u32 = 0; - var names_table_size: u32 = 0; - var dll_names_size: u32 = 0; - for (coff.import_tables.keys(), 0..) |off, i| { - const lib_name = coff.temp_strtab.getAssumeExists(off); - const itable = coff.import_tables.values()[i]; - iat_size += itable.size() + 8; - dir_table_size += @sizeOf(coff_util.ImportDirectoryEntry); - lookup_table_size += @as(u32, @intCast(itable.entries.items.len + 1)) * @sizeOf(coff_util.ImportLookupEntry64.ByName); - for (itable.entries.items) |entry| { - const sym_name = coff.getSymbolName(entry); - names_table_size += 2 + mem.alignForward(u32, @as(u32, @intCast(sym_name.len + 1)), 2); - } - dll_names_size += @as(u32, @intCast(lib_name.len + ext.len + 1)); - } - - const needed_size = iat_size + dir_table_size + lookup_table_size + names_table_size + dll_names_size; - try coff.growSection(coff.idata_section_index.?, needed_size); - - // Do the actual writes - var buffer = std.array_list.Managed(u8).init(gpa); - defer buffer.deinit(); - try buffer.ensureTotalCapacityPrecise(needed_size); - buffer.resize(needed_size) catch unreachable; - - const dir_header_size = @sizeOf(coff_util.ImportDirectoryEntry); - const lookup_entry_size = @sizeOf(coff_util.ImportLookupEntry64.ByName); - - var iat_offset: u32 = 0; - var dir_table_offset = iat_size; - var lookup_table_offset = dir_table_offset + dir_table_size; - var names_table_offset = lookup_table_offset + lookup_table_size; - var dll_names_offset = names_table_offset + names_table_size; - for (coff.import_tables.keys(), 0..) |off, i| { - const lib_name = coff.temp_strtab.getAssumeExists(off); - const itable = coff.import_tables.values()[i]; - - // Lookup table header - const lookup_header = coff_util.ImportDirectoryEntry{ - .import_lookup_table_rva = header.virtual_address + lookup_table_offset, - .time_date_stamp = 0, - .forwarder_chain = 0, - .name_rva = header.virtual_address + dll_names_offset, - .import_address_table_rva = header.virtual_address + iat_offset, - }; - @memcpy(buffer.items[dir_table_offset..][0..@sizeOf(coff_util.ImportDirectoryEntry)], mem.asBytes(&lookup_header)); - dir_table_offset += dir_header_size; - - for (itable.entries.items) |entry| { - const import_name = coff.getSymbolName(entry); - - // IAT and lookup table entry - const lookup = coff_util.ImportLookupEntry64.ByName{ .name_table_rva = @as(u31, @intCast(header.virtual_address + names_table_offset)) }; - @memcpy( - buffer.items[iat_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)], - mem.asBytes(&lookup), - ); - iat_offset += lookup_entry_size; - @memcpy( - buffer.items[lookup_table_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)], - mem.asBytes(&lookup), - ); - lookup_table_offset += lookup_entry_size; - - // Names table entry - mem.writeInt(u16, buffer.items[names_table_offset..][0..2], 0, .little); // Hint set to 0 until we learn how to parse DLLs - names_table_offset += 2; - @memcpy(buffer.items[names_table_offset..][0..import_name.len], import_name); - names_table_offset += @as(u32, @intCast(import_name.len)); - buffer.items[names_table_offset] = 0; - names_table_offset += 1; - if (!mem.isAlignedGeneric(usize, names_table_offset, @sizeOf(u16))) { - buffer.items[names_table_offset] = 0; - names_table_offset += 1; - } - } - - // IAT sentinel - mem.writeInt(u64, buffer.items[iat_offset..][0..lookup_entry_size], 0, .little); - iat_offset += 8; - - // Lookup table sentinel - @memcpy( - buffer.items[lookup_table_offset..][0..@sizeOf(coff_util.ImportLookupEntry64.ByName)], - mem.asBytes(&coff_util.ImportLookupEntry64.ByName{ .name_table_rva = 0 }), - ); - lookup_table_offset += lookup_entry_size; - - // DLL name - @memcpy(buffer.items[dll_names_offset..][0..lib_name.len], lib_name); - dll_names_offset += @as(u32, @intCast(lib_name.len)); - @memcpy(buffer.items[dll_names_offset..][0..ext.len], ext); - dll_names_offset += @as(u32, @intCast(ext.len)); - buffer.items[dll_names_offset] = 0; - dll_names_offset += 1; - } - - // Sentinel - const lookup_header = coff_util.ImportDirectoryEntry{ - .import_lookup_table_rva = 0, - .time_date_stamp = 0, - .forwarder_chain = 0, - .name_rva = 0, - .import_address_table_rva = 0, - }; - @memcpy( - buffer.items[dir_table_offset..][0..@sizeOf(coff_util.ImportDirectoryEntry)], - mem.asBytes(&lookup_header), - ); - dir_table_offset += dir_header_size; - - assert(dll_names_offset == needed_size); - - try coff.pwriteAll(buffer.items, header.pointer_to_raw_data); - - coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IMPORT)] = .{ - .virtual_address = header.virtual_address + iat_size, - .size = dir_table_size, - }; - coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IAT)] = .{ - .virtual_address = header.virtual_address, - .size = iat_size, - }; - - coff.imports_count_dirty = false; -} - -fn writeStrtab(coff: *Coff) !void { - if (coff.strtab_offset == null) return; - - const comp = coff.base.comp; - const gpa = comp.gpa; - const diags = &comp.link_diags; - const allocated_size = coff.allocatedSize(coff.strtab_offset.?); - const needed_size: u32 = @intCast(coff.strtab.buffer.items.len); - - if (needed_size > allocated_size) { - coff.strtab_offset = null; - coff.strtab_offset = @intCast(coff.findFreeSpace(needed_size, @alignOf(u32))); - } - - log.debug("writing strtab from 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + needed_size }); - - var buffer = std.array_list.Managed(u8).init(gpa); - defer buffer.deinit(); - try buffer.ensureTotalCapacityPrecise(needed_size); - buffer.appendSliceAssumeCapacity(coff.strtab.buffer.items); - // Here, we do a trick in that we do not commit the size of the strtab to strtab buffer, instead - // we write the length of the strtab to a temporary buffer that goes to file. - mem.writeInt(u32, buffer.items[0..4], @as(u32, @intCast(coff.strtab.buffer.items.len)), .little); - - coff.pwriteAll(buffer.items, coff.strtab_offset.?) catch |err| { - return diags.fail("failed to write: {s}", .{@errorName(err)}); - }; -} - -fn writeSectionHeaders(coff: *Coff) !void { - const offset = coff.getSectionHeadersOffset(); - try coff.pwriteAll(@ptrCast(coff.sections.items(.header)), offset); -} - -fn writeDataDirectoriesHeaders(coff: *Coff) !void { - const offset = coff.getDataDirectoryHeadersOffset(); - try coff.pwriteAll(@ptrCast(&coff.data_directories), offset); -} - -fn writeHeader(coff: *Coff) !void { - const target = &coff.base.comp.root_mod.resolved_target.result; - const gpa = coff.base.comp.gpa; - var buffer: std.Io.Writer.Allocating = .init(gpa); - defer buffer.deinit(); - const writer = &buffer.writer; - - try buffer.ensureTotalCapacity(coff.getSizeOfHeaders()); - writer.writeAll(&msdos_stub) catch unreachable; - mem.writeInt(u32, buffer.writer.buffer[0x3c..][0..4], msdos_stub.len, .little); - - writer.writeAll("PE\x00\x00") catch unreachable; - var flags: coff_util.Header.Flags = .{ - .EXECUTABLE_IMAGE = true, - .DEBUG_STRIPPED = true, // TODO - }; - switch (coff.ptr_width) { - .p32 => flags.@"32BIT_MACHINE" = true, - .p64 => flags.LARGE_ADDRESS_AWARE = true, - } - if (coff.base.comp.config.output_mode == .Lib and coff.base.comp.config.link_mode == .dynamic) { - flags.DLL = true; - } - - const timestamp = if (coff.repro) 0 else std.time.timestamp(); - const size_of_optional_header = @as(u16, @intCast(coff.getOptionalHeaderSize() + coff.getDataDirectoryHeadersSize())); - var coff_header: coff_util.Header = .{ - .machine = target.toCoffMachine(), - .number_of_sections = @as(u16, @intCast(coff.sections.slice().len)), // TODO what if we prune a section - .time_date_stamp = @as(u32, @truncate(@as(u64, @bitCast(timestamp)))), - .pointer_to_symbol_table = coff.strtab_offset orelse 0, - .number_of_symbols = 0, - .size_of_optional_header = size_of_optional_header, - .flags = flags, - }; - - writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; - - const dll_flags: coff_util.DllFlags = .{ - .HIGH_ENTROPY_VA = true, // TODO do we want to permit non-PIE builds at all? - .DYNAMIC_BASE = true, - .TERMINAL_SERVER_AWARE = true, // We are not a legacy app - .NX_COMPAT = true, // We are compatible with Data Execution Prevention - }; - const subsystem: coff_util.Subsystem = .WINDOWS_CUI; - const size_of_image: u32 = coff.getSizeOfImage(); - const size_of_headers: u32 = mem.alignForward(u32, coff.getSizeOfHeaders(), default_file_alignment); - const base_of_code = coff.sections.get(coff.text_section_index.?).header.virtual_address; - const base_of_data = coff.sections.get(coff.data_section_index.?).header.virtual_address; - - var size_of_code: u32 = 0; - var size_of_initialized_data: u32 = 0; - var size_of_uninitialized_data: u32 = 0; - for (coff.sections.items(.header)) |header| { - if (header.flags.CNT_CODE) { - size_of_code += header.size_of_raw_data; - } - if (header.flags.CNT_INITIALIZED_DATA) { - size_of_initialized_data += header.size_of_raw_data; - } - if (header.flags.CNT_UNINITIALIZED_DATA) { - size_of_uninitialized_data += header.size_of_raw_data; - } - } - - switch (coff.ptr_width) { - .p32 => { - var opt_header = coff_util.OptionalHeaderPE32{ - .magic = .PE32, - .major_linker_version = 0, - .minor_linker_version = 0, - .size_of_code = size_of_code, - .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = size_of_uninitialized_data, - .address_of_entry_point = coff.entry_addr orelse 0, - .base_of_code = base_of_code, - .base_of_data = base_of_data, - .image_base = @intCast(coff.image_base), - .section_alignment = coff.page_size, - .file_alignment = default_file_alignment, - .major_operating_system_version = 6, - .minor_operating_system_version = 0, - .major_image_version = 0, - .minor_image_version = 0, - .major_subsystem_version = @intCast(coff.major_subsystem_version), - .minor_subsystem_version = @intCast(coff.minor_subsystem_version), - .win32_version_value = 0, - .size_of_image = size_of_image, - .size_of_headers = size_of_headers, - .checksum = 0, - .subsystem = subsystem, - .dll_flags = dll_flags, - .size_of_stack_reserve = default_size_of_stack_reserve, - .size_of_stack_commit = default_size_of_stack_commit, - .size_of_heap_reserve = default_size_of_heap_reserve, - .size_of_heap_commit = default_size_of_heap_commit, - .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(coff.data_directories.len), - }; - writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; - }, - .p64 => { - var opt_header = coff_util.OptionalHeaderPE64{ - .magic = .@"PE32+", - .major_linker_version = 0, - .minor_linker_version = 0, - .size_of_code = size_of_code, - .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = size_of_uninitialized_data, - .address_of_entry_point = coff.entry_addr orelse 0, - .base_of_code = base_of_code, - .image_base = coff.image_base, - .section_alignment = coff.page_size, - .file_alignment = default_file_alignment, - .major_operating_system_version = 6, - .minor_operating_system_version = 0, - .major_image_version = 0, - .minor_image_version = 0, - .major_subsystem_version = coff.major_subsystem_version, - .minor_subsystem_version = coff.minor_subsystem_version, - .win32_version_value = 0, - .size_of_image = size_of_image, - .size_of_headers = size_of_headers, - .checksum = 0, - .subsystem = subsystem, - .dll_flags = dll_flags, - .size_of_stack_reserve = default_size_of_stack_reserve, - .size_of_stack_commit = default_size_of_stack_commit, - .size_of_heap_reserve = default_size_of_heap_reserve, - .size_of_heap_commit = default_size_of_heap_commit, - .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(coff.data_directories.len), - }; - writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; - }, - } - - try coff.pwriteAll(buffer.written(), 0); -} - -pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { - return actual_size +| (actual_size / ideal_factor); -} - -fn detectAllocCollision(coff: *Coff, start: u32, size: u32) ?u32 { - const headers_size = @max(coff.getSizeOfHeaders(), coff.page_size); - if (start < headers_size) - return headers_size; - - const end = start + padToIdeal(size); - - if (coff.strtab_offset) |off| { - const tight_size = @as(u32, @intCast(coff.strtab.buffer.items.len)); - const increased_size = padToIdeal(tight_size); - const test_end = off + increased_size; - if (end > off and start < test_end) { - return test_end; - } - } - - for (coff.sections.items(.header)) |header| { - const tight_size = header.size_of_raw_data; - const increased_size = padToIdeal(tight_size); - const test_end = header.pointer_to_raw_data + increased_size; - if (end > header.pointer_to_raw_data and start < test_end) { - return test_end; - } - } - - return null; -} - -fn allocatedSize(coff: *Coff, start: u32) u32 { - if (start == 0) - return 0; - var min_pos: u32 = std.math.maxInt(u32); - if (coff.strtab_offset) |off| { - if (off > start and off < min_pos) min_pos = off; - } - for (coff.sections.items(.header)) |header| { - if (header.pointer_to_raw_data <= start) continue; - if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; - } - return min_pos - start; -} - -fn findFreeSpace(coff: *Coff, object_size: u32, min_alignment: u32) u32 { - var start: u32 = 0; - while (coff.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForward(u32, item_end, min_alignment); - } - return start; -} - -fn allocatedVirtualSize(coff: *Coff, start: u32) u32 { - if (start == 0) - return 0; - var min_pos: u32 = std.math.maxInt(u32); - for (coff.sections.items(.header)) |header| { - if (header.virtual_address <= start) continue; - if (header.virtual_address < min_pos) min_pos = header.virtual_address; - } - return min_pos - start; -} - -fn getSizeOfHeaders(coff: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 4; - return @as(u32, @intCast(msdos_hdr_size + @sizeOf(coff_util.Header) + coff.getOptionalHeaderSize() + - coff.getDataDirectoryHeadersSize() + coff.getSectionHeadersSize())); -} - -fn getOptionalHeaderSize(coff: Coff) u32 { - return switch (coff.ptr_width) { - .p32 => @as(u32, @intCast(@sizeOf(coff_util.OptionalHeaderPE32))), - .p64 => @as(u32, @intCast(@sizeOf(coff_util.OptionalHeaderPE64))), - }; -} - -fn getDataDirectoryHeadersSize(coff: Coff) u32 { - return @as(u32, @intCast(coff.data_directories.len * @sizeOf(coff_util.ImageDataDirectory))); -} - -fn getSectionHeadersSize(coff: Coff) u32 { - return @as(u32, @intCast(coff.sections.slice().len * @sizeOf(coff_util.SectionHeader))); -} - -fn getDataDirectoryHeadersOffset(coff: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 4; - return @as(u32, @intCast(msdos_hdr_size + @sizeOf(coff_util.Header) + coff.getOptionalHeaderSize())); -} - -fn getSectionHeadersOffset(coff: Coff) u32 { - return coff.getDataDirectoryHeadersOffset() + coff.getDataDirectoryHeadersSize(); -} - -fn getSizeOfImage(coff: Coff) u32 { - var image_size: u32 = mem.alignForward(u32, coff.getSizeOfHeaders(), coff.page_size); - for (coff.sections.items(.header)) |header| { - image_size += mem.alignForward(u32, header.virtual_size, coff.page_size); - } - return image_size; -} - -/// Returns symbol location corresponding to the set entrypoint (if any). -pub fn getEntryPoint(coff: Coff) ?SymbolWithLoc { - const comp = coff.base.comp; - - // TODO This is incomplete. - // The entry symbol name depends on the subsystem as well as the set of - // public symbol names from linked objects. - // See LinkerDriver::findDefaultEntry from the LLD project for the flow chart. - const entry_name = switch (coff.entry) { - .disabled => return null, - .default => switch (comp.config.output_mode) { - .Exe => "wWinMainCRTStartup", - .Obj, .Lib => return null, - }, - .enabled => "wWinMainCRTStartup", - .named => |name| name, - }; - const global_index = coff.resolver.get(entry_name) orelse return null; - return coff.globals.items[global_index]; -} - -/// Returns pointer-to-symbol described by `sym_loc` descriptor. -pub fn getSymbolPtr(coff: *Coff, sym_loc: SymbolWithLoc) *coff_util.Symbol { - assert(sym_loc.file == null); // TODO linking object files - return &coff.locals.items[sym_loc.sym_index]; -} - -/// Returns symbol described by `sym_loc` descriptor. -pub fn getSymbol(coff: *const Coff, sym_loc: SymbolWithLoc) *const coff_util.Symbol { - assert(sym_loc.file == null); // TODO linking object files - return &coff.locals.items[sym_loc.sym_index]; -} - -/// Returns name of the symbol described by `sym_loc` descriptor. -pub fn getSymbolName(coff: *const Coff, sym_loc: SymbolWithLoc) []const u8 { - assert(sym_loc.file == null); // TODO linking object files - const sym = coff.getSymbol(sym_loc); - const offset = sym.getNameOffset() orelse return sym.getName().?; - return coff.strtab.get(offset).?; -} - -/// Returns pointer to the global entry for `name` if one exists. -pub fn getGlobalPtr(coff: *Coff, name: []const u8) ?*SymbolWithLoc { - const global_index = coff.resolver.get(name) orelse return null; - return &coff.globals.items[global_index]; -} - -/// Returns the global entry for `name` if one exists. -pub fn getGlobal(coff: *const Coff, name: []const u8) ?SymbolWithLoc { - const global_index = coff.resolver.get(name) orelse return null; - return coff.globals.items[global_index]; -} - -/// Returns the index of the global entry for `name` if one exists. -pub fn getGlobalIndex(coff: *const Coff, name: []const u8) ?u32 { - return coff.resolver.get(name); -} - -/// Returns global entry at `index`. -pub fn getGlobalByIndex(coff: *const Coff, index: u32) SymbolWithLoc { - assert(index < coff.globals.items.len); - return coff.globals.items[index]; -} - -const GetOrPutGlobalPtrResult = struct { - found_existing: bool, - value_ptr: *SymbolWithLoc, -}; - -/// Return pointer to the global entry for `name` if one exists. -/// Puts a new global entry for `name` if one doesn't exist, and -/// returns a pointer to it. -pub fn getOrPutGlobalPtr(coff: *Coff, name: []const u8) !GetOrPutGlobalPtrResult { - if (coff.getGlobalPtr(name)) |ptr| { - return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr }; - } - const gpa = coff.base.comp.gpa; - const global_index = try coff.allocateGlobal(); - const global_name = try gpa.dupe(u8, name); - _ = try coff.resolver.put(gpa, global_name, global_index); - const ptr = &coff.globals.items[global_index]; - return GetOrPutGlobalPtrResult{ .found_existing = false, .value_ptr = ptr }; -} - -pub fn getAtom(coff: *const Coff, atom_index: Atom.Index) Atom { - assert(atom_index < coff.atoms.items.len); - return coff.atoms.items[atom_index]; -} - -pub fn getAtomPtr(coff: *Coff, atom_index: Atom.Index) *Atom { - assert(atom_index < coff.atoms.items.len); - return &coff.atoms.items[atom_index]; -} - -/// Returns atom if there is an atom referenced by the symbol described by `sym_loc` descriptor. -/// Returns null on failure. -pub fn getAtomIndexForSymbol(coff: *const Coff, sym_loc: SymbolWithLoc) ?Atom.Index { - assert(sym_loc.file == null); // TODO linking with object files - return coff.atom_by_index_table.get(sym_loc.sym_index); -} - -fn setSectionName(coff: *Coff, header: *coff_util.SectionHeader, name: []const u8) !void { - if (name.len <= 8) { - @memcpy(header.name[0..name.len], name); - @memset(header.name[name.len..], 0); - return; - } - const gpa = coff.base.comp.gpa; - const offset = try coff.strtab.insert(gpa, name); - const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; - @memset(header.name[name_offset.len..], 0); -} - -fn getSectionName(coff: *const Coff, header: *const coff_util.SectionHeader) []const u8 { - if (header.getName()) |name| { - return name; - } - const offset = header.getNameOffset().?; - return coff.strtab.get(offset).?; -} - -fn setSymbolName(coff: *Coff, symbol: *coff_util.Symbol, name: []const u8) !void { - if (name.len <= 8) { - @memcpy(symbol.name[0..name.len], name); - @memset(symbol.name[name.len..], 0); - return; - } - const gpa = coff.base.comp.gpa; - const offset = try coff.strtab.insert(gpa, name); - @memset(symbol.name[0..4], 0); - mem.writeInt(u32, symbol.name[4..8], offset, .little); -} - -fn logSymAttributes(sym: *const coff_util.Symbol, buf: *[4]u8) []const u8 { - @memset(buf[0..4], '_'); - switch (sym.section_number) { - .UNDEFINED => { - buf[3] = 'u'; - switch (sym.storage_class) { - .EXTERNAL => buf[1] = 'e', - .WEAK_EXTERNAL => buf[1] = 'w', - .NULL => {}, - else => unreachable, - } - }, - .ABSOLUTE => unreachable, // handle ABSOLUTE - .DEBUG => unreachable, - else => { - buf[0] = 's'; - switch (sym.storage_class) { - .EXTERNAL => buf[1] = 'e', - .WEAK_EXTERNAL => buf[1] = 'w', - .NULL => {}, - else => unreachable, - } - }, - } - return buf[0..]; -} - -fn logSymtab(coff: *Coff) void { - var buf: [4]u8 = undefined; - - log.debug("symtab:", .{}); - log.debug(" object(null)", .{}); - for (coff.locals.items, 0..) |*sym, sym_id| { - const where = if (sym.section_number == .UNDEFINED) "ord" else "sect"; - const def_index: u16 = switch (sym.section_number) { - .UNDEFINED => 0, // TODO - .ABSOLUTE => unreachable, // TODO - .DEBUG => unreachable, // TODO - else => @intFromEnum(sym.section_number), - }; - log.debug(" %{d}: {s} @{x} in {s}({d}), {s}", .{ - sym_id, - coff.getSymbolName(.{ .sym_index = @as(u32, @intCast(sym_id)), .file = null }), - sym.value, - where, - def_index, - logSymAttributes(sym, &buf), - }); - } - - log.debug("globals table:", .{}); - for (coff.globals.items) |sym_loc| { - const sym_name = coff.getSymbolName(sym_loc); - log.debug(" {s} => %{d} in object({?d})", .{ sym_name, sym_loc.sym_index, sym_loc.file }); - } - - log.debug("GOT entries:", .{}); - log.debug("{f}", .{coff.got_table}); -} - -fn logSections(coff: *Coff) void { - log.debug("sections:", .{}); - for (coff.sections.items(.header)) |*header| { - log.debug(" {s}: VM({x}, {x}) FILE({x}, {x})", .{ - coff.getSectionName(header), - header.virtual_address, - header.virtual_address + header.virtual_size, - header.pointer_to_raw_data, - header.pointer_to_raw_data + header.size_of_raw_data, - }); - } -} - -fn logImportTables(coff: *const Coff) void { - log.debug("import tables:", .{}); - for (coff.import_tables.keys(), 0..) |off, i| { - const itable = coff.import_tables.values()[i]; - log.debug("{f}", .{itable.fmtDebug(.{ - .coff = coff, - .index = i, - .name_off = off, - })}); - } -} - -pub const Atom = struct { - /// Each decl always gets a local symbol with the fully qualified name. - /// The vaddr and size are found here directly. - /// The file offset is found by computing the vaddr offset from the section vaddr - /// the symbol references, and adding that to the file offset of the section. - /// If this field is 0, it means the codegen size = 0 and there is no symbol or - /// offset table entry. - sym_index: u32, - - /// null means symbol defined by Zig source. - file: ?u32, - - /// Size of the atom - size: u32, - - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `Atom`. - prev_index: ?Index, - next_index: ?Index, - - const Index = u32; - - pub fn getSymbolIndex(atom: Atom) ?u32 { - if (atom.sym_index == 0) return null; - return atom.sym_index; - } - - /// Returns symbol referencing this atom. - fn getSymbol(atom: Atom, coff: *const Coff) *const coff_util.Symbol { - const sym_index = atom.getSymbolIndex().?; - return coff.getSymbol(.{ - .sym_index = sym_index, - .file = atom.file, - }); - } - - /// Returns pointer-to-symbol referencing this atom. - fn getSymbolPtr(atom: Atom, coff: *Coff) *coff_util.Symbol { - const sym_index = atom.getSymbolIndex().?; - return coff.getSymbolPtr(.{ - .sym_index = sym_index, - .file = atom.file, - }); - } - - fn getSymbolWithLoc(atom: Atom) SymbolWithLoc { - const sym_index = atom.getSymbolIndex().?; - return .{ .sym_index = sym_index, .file = atom.file }; - } - - /// Returns the name of this atom. - fn getName(atom: Atom, coff: *const Coff) []const u8 { - const sym_index = atom.getSymbolIndex().?; - return coff.getSymbolName(.{ - .sym_index = sym_index, - .file = atom.file, - }); - } - - /// Returns how much room there is to grow in virtual address space. - fn capacity(atom: Atom, coff: *const Coff) u32 { - const atom_sym = atom.getSymbol(coff); - if (atom.next_index) |next_index| { - const next = coff.getAtom(next_index); - const next_sym = next.getSymbol(coff); - return next_sym.value - atom_sym.value; - } else { - // We are the last atom. - // The capacity is limited only by virtual address space. - return std.math.maxInt(u32) - atom_sym.value; - } - } - - fn freeListEligible(atom: Atom, coff: *const Coff) bool { - // No need to keep a free list node for the last atom. - const next_index = atom.next_index orelse return false; - const next = coff.getAtom(next_index); - const atom_sym = atom.getSymbol(coff); - const next_sym = next.getSymbol(coff); - const cap = next_sym.value - atom_sym.value; - const ideal_cap = padToIdeal(atom.size); - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= min_text_capacity; - } -}; - -pub const Relocation = struct { - type: enum { - // x86, x86_64 - /// RIP-relative displacement to a GOT pointer - got, - /// RIP-relative displacement to an import pointer - import, - - // aarch64 - /// PC-relative distance to target page in GOT section - got_page, - /// Offset to a GOT pointer relative to the start of a page in GOT section - got_pageoff, - /// PC-relative distance to target page in a section (e.g., .rdata) - page, - /// Offset to a pointer relative to the start of a page in a section (e.g., .rdata) - pageoff, - /// PC-relative distance to target page in a import section - import_page, - /// Offset to a pointer relative to the start of a page in an import section (e.g., .rdata) - import_pageoff, - - // common - /// Absolute pointer value - direct, - }, - target: SymbolWithLoc, - offset: u32, - addend: u32, - pcrel: bool, - length: u2, - dirty: bool = true, - - /// Returns true if and only if the reloc can be resolved. - fn isResolvable(reloc: Relocation, coff: *Coff) bool { - _ = reloc.getTargetAddress(coff) orelse return false; - return true; - } - - fn isGotIndirection(reloc: Relocation) bool { - return switch (reloc.type) { - .got, .got_page, .got_pageoff => true, - else => false, - }; - } - - /// Returns address of the target if any. - fn getTargetAddress(reloc: Relocation, coff: *const Coff) ?u32 { - switch (reloc.type) { - .got, .got_page, .got_pageoff => { - const got_index = coff.got_table.lookup.get(reloc.target) orelse return null; - const header = coff.sections.items(.header)[coff.got_section_index.?]; - return header.virtual_address + got_index * coff.ptr_width.size(); - }, - .import, .import_page, .import_pageoff => { - const sym = coff.getSymbol(reloc.target); - const index = coff.import_tables.getIndex(sym.value) orelse return null; - const itab = coff.import_tables.values()[index]; - return itab.getImportAddress(reloc.target, .{ - .coff = coff, - .index = index, - .name_off = sym.value, - }); - }, - else => { - const target_atom_index = coff.getAtomIndexForSymbol(reloc.target) orelse return null; - const target_atom = coff.getAtom(target_atom_index); - return target_atom.getSymbol(coff).value; - }, - } - } - - fn resolve(reloc: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff: *Coff) void { - const atom = coff.getAtom(atom_index); - const source_sym = atom.getSymbol(coff); - const source_vaddr = source_sym.value + reloc.offset; - - const target_vaddr = reloc.getTargetAddress(coff).?; // Oops, you didn't check if the relocation can be resolved with isResolvable(). - const target_vaddr_with_addend = target_vaddr + reloc.addend; - - log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{ - source_vaddr, - target_vaddr_with_addend, - coff.getSymbolName(reloc.target), - @tagName(reloc.type), - }); - - const ctx: Context = .{ - .source_vaddr = source_vaddr, - .target_vaddr = target_vaddr_with_addend, - .image_base = image_base, - .code = code, - .ptr_width = coff.ptr_width, - }; - - const target = &coff.base.comp.root_mod.resolved_target.result; - switch (target.cpu.arch) { - .aarch64 => reloc.resolveAarch64(ctx), - .x86, .x86_64 => reloc.resolveX86(ctx), - else => unreachable, // unhandled target architecture - } - } - - const Context = struct { - source_vaddr: u32, - target_vaddr: u32, - image_base: u64, - code: []u8, - ptr_width: PtrWidth, - }; - - fn resolveAarch64(reloc: Relocation, ctx: Context) void { - const Instruction = aarch64_util.encoding.Instruction; - var buffer = ctx.code[reloc.offset..]; - switch (reloc.type) { - .got_page, .import_page, .page => { - const source_page = @as(i32, @intCast(ctx.source_vaddr >> 12)); - const target_page = @as(i32, @intCast(ctx.target_vaddr >> 12)); - const pages: i21 = @intCast(target_page - source_page); - var inst: Instruction = .read(buffer[0..Instruction.size]); - inst.data_processing_immediate.pc_relative_addressing.group.immhi = @intCast(pages >> 2); - inst.data_processing_immediate.pc_relative_addressing.group.immlo = @truncate(@as(u21, @bitCast(pages))); - inst.write(buffer[0..Instruction.size]); - }, - .got_pageoff, .import_pageoff, .pageoff => { - assert(!reloc.pcrel); - - const narrowed: u12 = @truncate(@as(u64, @intCast(ctx.target_vaddr))); - var inst: Instruction = .read(buffer[0..Instruction.size]); - switch (inst.decode()) { - else => unreachable, - .data_processing_immediate => inst.data_processing_immediate.add_subtract_immediate.group.imm12 = narrowed, - .load_store => |load_store| inst.load_store.register_unsigned_immediate.group.imm12 = - switch (load_store.register_unsigned_immediate.decode()) { - .integer => |integer| @shrExact(narrowed, @intFromEnum(integer.group.size)), - .vector => |vector| @shrExact(narrowed, @intFromEnum(vector.group.opc1.decode(vector.group.size))), - }, - } - inst.write(buffer[0..Instruction.size]); - }, - .direct => { - assert(!reloc.pcrel); - switch (reloc.length) { - 2 => mem.writeInt( - u32, - buffer[0..4], - @as(u32, @truncate(ctx.target_vaddr + ctx.image_base)), - .little, - ), - 3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little), - else => unreachable, - } - }, - - .got => unreachable, - .import => unreachable, - } - } - - fn resolveX86(reloc: Relocation, ctx: Context) void { - var buffer = ctx.code[reloc.offset..]; - switch (reloc.type) { - .got_page => unreachable, - .got_pageoff => unreachable, - .page => unreachable, - .pageoff => unreachable, - .import_page => unreachable, - .import_pageoff => unreachable, - - .got, .import => { - assert(reloc.pcrel); - const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4; - mem.writeInt(i32, buffer[0..4], disp, .little); - }, - .direct => { - if (reloc.pcrel) { - const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4; - mem.writeInt(i32, buffer[0..4], disp, .little); - } else switch (ctx.ptr_width) { - .p32 => mem.writeInt(u32, buffer[0..4], @as(u32, @intCast(ctx.target_vaddr + ctx.image_base)), .little), - .p64 => switch (reloc.length) { - 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(ctx.target_vaddr + ctx.image_base)), .little), - 3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little), - else => unreachable, - }, - } - }, - } - } -}; - -pub fn addRelocation(coff: *Coff, atom_index: Atom.Index, reloc: Relocation) !void { - const comp = coff.base.comp; - const gpa = comp.gpa; - log.debug(" (adding reloc of type {s} to target %{d})", .{ @tagName(reloc.type), reloc.target.sym_index }); - const gop = try coff.relocs.getOrPut(gpa, atom_index); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - try gop.value_ptr.append(gpa, reloc); -} - -fn addBaseRelocation(coff: *Coff, atom_index: Atom.Index, offset: u32) !void { - const comp = coff.base.comp; - const gpa = comp.gpa; - log.debug(" (adding base relocation at offset 0x{x} in %{d})", .{ - offset, - coff.getAtom(atom_index).getSymbolIndex().?, - }); - const gop = try coff.base_relocs.getOrPut(gpa, atom_index); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - try gop.value_ptr.append(gpa, offset); -} - -fn freeRelocations(coff: *Coff, atom_index: Atom.Index) void { - const comp = coff.base.comp; - const gpa = comp.gpa; - var removed_relocs = coff.relocs.fetchOrderedRemove(atom_index); - if (removed_relocs) |*relocs| relocs.value.deinit(gpa); - var removed_base_relocs = coff.base_relocs.fetchOrderedRemove(atom_index); - if (removed_base_relocs) |*base_relocs| base_relocs.value.deinit(gpa); -} - -/// Represents an import table in the .idata section where each contained pointer -/// is to a symbol from the same DLL. -/// -/// The layout of .idata section is as follows: -/// -/// --- ADDR1 : IAT (all import tables concatenated together) -/// ptr -/// ptr -/// 0 sentinel -/// ptr -/// 0 sentinel -/// --- ADDR2: headers -/// ImportDirectoryEntry header -/// ImportDirectoryEntry header -/// sentinel -/// --- ADDR2: lookup tables -/// Lookup table -/// 0 sentinel -/// Lookup table -/// 0 sentinel -/// --- ADDR3: name hint tables -/// hint-symname -/// hint-symname -/// --- ADDR4: DLL names -/// DLL#1 name -/// DLL#2 name -/// --- END -const ImportTable = struct { - entries: std.ArrayListUnmanaged(SymbolWithLoc) = .empty, - free_list: std.ArrayListUnmanaged(u32) = .empty, - lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .empty, - - fn deinit(itab: *ImportTable, allocator: Allocator) void { - itab.entries.deinit(allocator); - itab.free_list.deinit(allocator); - itab.lookup.deinit(allocator); - } - - /// Size of the import table does not include the sentinel. - fn size(itab: ImportTable) u32 { - return @as(u32, @intCast(itab.entries.items.len)) * @sizeOf(u64); - } - - fn addImport(itab: *ImportTable, allocator: Allocator, target: SymbolWithLoc) !ImportIndex { - try itab.entries.ensureUnusedCapacity(allocator, 1); - const index: u32 = blk: { - if (itab.free_list.pop()) |index| { - log.debug(" (reusing import entry index {d})", .{index}); - break :blk index; - } else { - log.debug(" (allocating import entry at index {d})", .{itab.entries.items.len}); - const index = @as(u32, @intCast(itab.entries.items.len)); - _ = itab.entries.addOneAssumeCapacity(); - break :blk index; - } - }; - itab.entries.items[index] = target; - try itab.lookup.putNoClobber(allocator, target, index); - return index; - } - - const Context = struct { - coff: *const Coff, - /// Index of this ImportTable in a global list of all tables. - /// This is required in order to calculate the base vaddr of this ImportTable. - index: usize, - /// Offset into the string interning table of the DLL this ImportTable corresponds to. - name_off: u32, - }; - - fn getBaseAddress(ctx: Context) u32 { - const header = ctx.coff.sections.items(.header)[ctx.coff.idata_section_index.?]; - var addr = header.virtual_address; - for (ctx.coff.import_tables.values(), 0..) |other_itab, i| { - if (ctx.index == i) break; - addr += @as(u32, @intCast(other_itab.entries.items.len * @sizeOf(u64))) + 8; - } - return addr; - } - - fn getImportAddress(itab: *const ImportTable, target: SymbolWithLoc, ctx: Context) ?u32 { - const index = itab.lookup.get(target) orelse return null; - const base_vaddr = getBaseAddress(ctx); - return base_vaddr + index * @sizeOf(u64); - } - - const Format = struct { - itab: ImportTable, - ctx: Context, - - fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void { - const lib_name = f.ctx.coff.temp_strtab.getAssumeExists(f.ctx.name_off); - const base_vaddr = getBaseAddress(f.ctx); - try writer.print("IAT({s}.dll) @{x}:", .{ lib_name, base_vaddr }); - for (f.itab.entries.items, 0..) |entry, i| { - try writer.print("\n {d}@{?x} => {s}", .{ - i, - f.itab.getImportAddress(entry, f.ctx), - f.ctx.coff.getSymbolName(entry), - }); - } - } - }; - - fn fmtDebug(itab: ImportTable, ctx: Context) fmt.Alt(Format, Format.default) { - return .{ .data = .{ .itab = itab, .ctx = ctx } }; - } - - const ImportIndex = u32; -}; - -fn pwriteAll(coff: *Coff, bytes: []const u8, offset: u64) error{LinkFailure}!void { - const comp = coff.base.comp; - const diags = &comp.link_diags; - coff.base.file.?.pwriteAll(bytes, offset) catch |err| { - return diags.fail("failed to write: {s}", .{@errorName(err)}); - }; -} - -/// This is the start of a Portable Executable (PE) file. -/// It starts with a MS-DOS header followed by a MS-DOS stub program. -/// This data does not change so we include it as follows in all binaries. -/// -/// In this context, -/// A "paragraph" is 16 bytes. -/// A "page" is 512 bytes. -/// A "long" is 4 bytes. -/// A "word" is 2 bytes. -pub const msdos_stub: [120]u8 = .{ - 'M', 'Z', // Magic number. Stands for Mark Zbikowski (designer of the MS-DOS executable format). - 0x78, 0x00, // Number of bytes in the last page. This matches the size of this entire MS-DOS stub. - 0x01, 0x00, // Number of pages. - 0x00, 0x00, // Number of entries in the relocation table. - 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). - 0x00, 0x00, // The number of paragraphs required by the program. - 0x00, 0x00, // The number of paragraphs requested by the program. - 0x00, 0x00, // Initial value for SS (relocatable segment address). - 0x00, 0x00, // Initial value for SP. - 0x00, 0x00, // Checksum. - 0x00, 0x00, // Initial value for IP. - 0x00, 0x00, // Initial value for CS (relocatable segment address). - 0x40, 0x00, // Absolute offset to relocation table. 64 matches the header size (all bytes before the MS-DOS stub program). - 0x00, 0x00, // Overlay number. Zero means this is the main executable. -} - // Reserved words. - ++ .{ 0x00, 0x00 } ** 4 - // OEM-related fields. - ++ .{ - 0x00, 0x00, // OEM identifier. - 0x00, 0x00, // OEM information. - } - // Reserved words. - ++ .{ 0x00, 0x00 } ** 10 - // 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. - ++ .{ 0x78, 0x00, 0x00, 0x00 } - // What follows is a 16-bit x86 MS-DOS program of 7 instructions that prints the bytes after these instructions and then exits. - ++ .{ - // Set the value of the data segment to the same value as the code segment. - 0x0e, // push cs - 0x1f, // pop ds - // Set the DX register to the address of the message. - // If you count all bytes of these 7 instructions you get 14, so that's the address of what's after these instructions. - 0xba, 14, 0x00, // mov dx, 14 - // Set AH to the system call code for printing a message. - 0xb4, 0x09, // mov ah, 0x09 - // Perform the system call to print the message. - 0xcd, 0x21, // int 0x21 - // Set AH to 0x4c which is the system call code for exiting, and set AL to 0x01 which is the exit code. - 0xb8, 0x01, 0x4c, // mov ax, 0x4c01 - // Peform the system call to exit the program with exit code 1. - 0xcd, 0x21, // int 0x21 - } - // Message to print. - ++ "This program cannot be run in DOS mode.".* - // Message terminators. - ++ .{ - '$', // We do not pass a length to the print system call; the string is terminated by this character. - 0x00, 0x00, // Terminating zero bytes. - }; diff --git a/src/link/Coff2.zig b/src/link/Coff2.zig index 7325566852..79d4b17505 100644 --- a/src/link/Coff2.zig +++ b/src/link/Coff2.zig @@ -28,6 +28,73 @@ relocs: std.ArrayList(Reloc), /// This is hiding actual bugs with global symbols! Reconsider once they are implemented correctly. entry_hack: Symbol.Index, +pub const default_file_alignment: u16 = 0x200; +pub const default_size_of_stack_reserve: u32 = 0x1000000; +pub const default_size_of_stack_commit: u32 = 0x1000; +pub const default_size_of_heap_reserve: u32 = 0x100000; +pub const default_size_of_heap_commit: u32 = 0x1000; + +/// This is the start of a Portable Executable (PE) file. +/// It starts with a MS-DOS header followed by a MS-DOS stub program. +/// This data does not change so we include it as follows in all binaries. +/// +/// In this context, +/// A "paragraph" is 16 bytes. +/// A "page" is 512 bytes. +/// A "long" is 4 bytes. +/// A "word" is 2 bytes. +pub const msdos_stub: [120]u8 = .{ + 'M', 'Z', // Magic number. Stands for Mark Zbikowski (designer of the MS-DOS executable format). + 0x78, 0x00, // Number of bytes in the last page. This matches the size of this entire MS-DOS stub. + 0x01, 0x00, // Number of pages. + 0x00, 0x00, // Number of entries in the relocation table. + 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). + 0x00, 0x00, // The number of paragraphs required by the program. + 0x00, 0x00, // The number of paragraphs requested by the program. + 0x00, 0x00, // Initial value for SS (relocatable segment address). + 0x00, 0x00, // Initial value for SP. + 0x00, 0x00, // Checksum. + 0x00, 0x00, // Initial value for IP. + 0x00, 0x00, // Initial value for CS (relocatable segment address). + 0x40, 0x00, // Absolute offset to relocation table. 64 matches the header size (all bytes before the MS-DOS stub program). + 0x00, 0x00, // Overlay number. Zero means this is the main executable. +} + // Reserved words. + ++ .{ 0x00, 0x00 } ** 4 + // OEM-related fields. + ++ .{ + 0x00, 0x00, // OEM identifier. + 0x00, 0x00, // OEM information. + } + // Reserved words. + ++ .{ 0x00, 0x00 } ** 10 + // 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. + ++ .{ 0x78, 0x00, 0x00, 0x00 } + // What follows is a 16-bit x86 MS-DOS program of 7 instructions that prints the bytes after these instructions and then exits. + ++ .{ + // Set the value of the data segment to the same value as the code segment. + 0x0e, // push cs + 0x1f, // pop ds + // Set the DX register to the address of the message. + // If you count all bytes of these 7 instructions you get 14, so that's the address of what's after these instructions. + 0xba, 14, 0x00, // mov dx, 14 + // Set AH to the system call code for printing a message. + 0xb4, 0x09, // mov ah, 0x09 + // Perform the system call to print the message. + 0xcd, 0x21, // int 0x21 + // Set AH to 0x4c which is the system call code for exiting, and set AL to 0x01 which is the exit code. + 0xb8, 0x01, 0x4c, // mov ax, 0x4c01 + // Peform the system call to exit the program with exit code 1. + 0xcd, 0x21, // int 0x21 + } + // Message to print. + ++ "This program cannot be run in DOS mode.".* + // Message terminators. + ++ .{ + '$', // We do not pass a length to the print system call; the string is terminated by this character. + 0x00, 0x00, // Terminating zero bytes. + }; + pub const Node = union(enum) { file, header, @@ -613,8 +680,7 @@ fn initHeaders( ) !void { const comp = coff.base.comp; const gpa = comp.gpa; - const file_align: std.mem.Alignment = - comptime .fromByteUnits(link.File.Coff.default_file_alignment); + const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment); const target_endian = coff.targetEndian(); const optional_header_size: u16 = if (is_image) switch (magic) { @@ -639,15 +705,14 @@ fn initHeaders( const signature_ni = Node.known.signature; assert(signature_ni == try coff.mf.addOnlyChildNode(gpa, header_ni, .{ - .size = (if (is_image) link.File.Coff.msdos_stub.len else 0) + "PE\x00\x00".len, + .size = (if (is_image) msdos_stub.len else 0) + "PE\x00\x00".len, .alignment = .@"4", .fixed = true, })); coff.nodes.appendAssumeCapacity(.signature); { const signature_slice = signature_ni.slice(&coff.mf); - if (is_image) - @memcpy(signature_slice[0..link.File.Coff.msdos_stub.len], &link.File.Coff.msdos_stub); + if (is_image) @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub); @memcpy(signature_slice[signature_slice.len - 4 ..], "PE\x00\x00"); } @@ -730,10 +795,10 @@ fn initHeaders( .TERMINAL_SERVER_AWARE = true, .NX_COMPAT = true, }, - .size_of_stack_reserve = link.File.Coff.default_size_of_stack_reserve, - .size_of_stack_commit = link.File.Coff.default_size_of_stack_commit, - .size_of_heap_reserve = link.File.Coff.default_size_of_heap_reserve, - .size_of_heap_commit = link.File.Coff.default_size_of_heap_commit, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = data_directories_len, }; @@ -781,10 +846,10 @@ fn initHeaders( .TERMINAL_SERVER_AWARE = true, .NX_COMPAT = true, }, - .size_of_stack_reserve = link.File.Coff.default_size_of_stack_reserve, - .size_of_stack_commit = link.File.Coff.default_size_of_stack_commit, - .size_of_heap_reserve = link.File.Coff.default_size_of_heap_reserve, - .size_of_heap_commit = link.File.Coff.default_size_of_heap_commit, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = data_directories_len, };