diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f5cf7cd6e..eef03325fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -583,6 +583,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/ImportTable.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bcc507b02f..6dcad604ac 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,36 +1,7 @@ -const Coff = @This(); - -const std = @import("std"); -const build_options = @import("build_options"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const coff = std.coff; -const fmt = std.fmt; -const log = std.log.scoped(.link); -const math = std.math; -const mem = std.mem; - -const Allocator = std.mem.Allocator; - -const codegen = @import("../codegen.zig"); -const link = @import("../link.zig"); -const lld = @import("Coff/lld.zig"); -const trace = @import("../tracy.zig").trace; - -const Air = @import("../Air.zig"); -pub const Atom = @import("Coff/Atom.zig"); -const Compilation = @import("../Compilation.zig"); -const Liveness = @import("../Liveness.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; -const Module = @import("../Module.zig"); -const Object = @import("Coff/Object.zig"); -const Relocation = @import("Coff/Relocation.zig"); -const StringTable = @import("strtab.zig").StringTable; -const TypedValue = @import("../TypedValue.zig"); - -pub const base_tag: link.File.Tag = .coff; - -const msdos_stub = @embedFile("msdos-stub.bin"); +//! The main driver of the COFF linker. +//! Currently uses our own implementation for the incremental linker, and falls back to +//! LLD for traditional linking (linking relocatable object files). +//! LLD is also the default linker for LLVM. /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?*LlvmObject = null, @@ -160,92 +131,6 @@ const Section = struct { free_list: std.ArrayListUnmanaged(Atom.Index) = .{}, }; -/// 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) = .{}, - free_list: std.ArrayListUnmanaged(u32) = .{}, - lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, - index: u8, - - const ITable = @This(); - - fn deinit(itab: *ITable, allocator: Allocator) void { - itab.entries.deinit(allocator); - itab.free_list.deinit(allocator); - itab.lookup.deinit(allocator); - } - - fn size(itab: ITable) u32 { - return @intCast(u32, itab.entries.items.len) * @sizeOf(u64); - } - - fn addImport(itab: *ITable, allocator: Allocator, target: SymbolWithLoc) !u32 { - try itab.entries.ensureUnusedCapacity(allocator, 1); - const index: u32 = blk: { - if (itab.free_list.popOrNull()) |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 = @intCast(u32, itab.entries.items.len); - _ = itab.entries.addOneAssumeCapacity(); - break :blk index; - } - }; - itab.entries.items[index] = target; - try itab.lookup.putNoClobber(allocator, target, index); - return index; - } - - fn getBaseAddress(itab: *const ITable, coff_file: *const Coff) u32 { - const header = coff_file.sections.items(.header)[coff_file.idata_section_index.?]; - var addr = header.virtual_address; - for (coff_file.import_tables.values(), 0..) |other_itab, i| { - if (itab.index == i) break; - addr += @intCast(u32, other_itab.entries.items.len * @sizeOf(u64)) + 8; - } - return addr; - } - - pub fn getImportAddress(itab: *const ITable, coff_file: *const Coff, target: SymbolWithLoc) ?u32 { - const index = itab.lookup.get(target) orelse return null; - const base_vaddr = itab.getBaseAddress(coff_file); - return base_vaddr + index * @sizeOf(u64); - } - - pub fn write(itab: ITable, writer: anytype) !void { - for (itab.entries.items) |_| { - try writer.writeIntLittle(u64, 0); - } - } -}; - const DeclMetadata = struct { atom: Atom.Index, section: u16, @@ -1527,7 +1412,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod const res = try self.import_tables.getOrPut(gpa, sym.value); const itable = res.value_ptr; if (!res.found_existing) { - itable.* = .{ .index = @intCast(u8, self.import_tables.values().len - 1) }; + itable.* = .{}; } if (itable.lookup.contains(global)) continue; // TODO: we could technically write the pointer placeholder for to-be-bound import here, @@ -2367,15 +2252,46 @@ fn logSections(self: *Coff) void { fn logImportTables(self: *const Coff) void { log.debug("import tables:", .{}); for (self.import_tables.keys(), 0..) |off, i| { - const lib_name = self.temp_strtab.getAssumeExists(off); const itable = self.import_tables.values()[i]; - log.debug("IAT({s}) @{x}:", .{ lib_name, itable.getBaseAddress(self) }); - for (itable.entries.items, 0..) |entry, j| { - log.debug(" {d}@{?x} => {s}", .{ - j, - itable.getImportAddress(self, entry), - self.getSymbolName(entry), - }); - } + log.debug("{}", .{itable.fmtDebug(.{ + .coff_file = self, + .index = i, + .name_off = off, + })}); } } + +const Coff = @This(); + +const std = @import("std"); +const build_options = @import("build_options"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const coff = std.coff; +const fmt = std.fmt; +const log = std.log.scoped(.link); +const math = std.math; +const mem = std.mem; + +const Allocator = std.mem.Allocator; + +const codegen = @import("../codegen.zig"); +const link = @import("../link.zig"); +const lld = @import("Coff/lld.zig"); +const trace = @import("../tracy.zig").trace; + +const Air = @import("../Air.zig"); +pub const Atom = @import("Coff/Atom.zig"); +const Compilation = @import("../Compilation.zig"); +const ImportTable = @import("Coff/ImportTable.zig"); +const Liveness = @import("../Liveness.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; +const Module = @import("../Module.zig"); +const Object = @import("Coff/Object.zig"); +const Relocation = @import("Coff/Relocation.zig"); +const StringTable = @import("strtab.zig").StringTable; +const TypedValue = @import("../TypedValue.zig"); + +pub const base_tag: link.File.Tag = .coff; + +const msdos_stub = @embedFile("msdos-stub.bin"); diff --git a/src/link/Coff/ImportTable.zig b/src/link/Coff/ImportTable.zig new file mode 100644 index 0000000000..ba58af2fe0 --- /dev/null +++ b/src/link/Coff/ImportTable.zig @@ -0,0 +1,133 @@ +//! 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 + +entries: std.ArrayListUnmanaged(SymbolWithLoc) = .{}, +free_list: std.ArrayListUnmanaged(u32) = .{}, +lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, + +pub 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. +pub fn size(itab: ImportTable) u32 { + return @intCast(u32, itab.entries.items.len) * @sizeOf(u64); +} + +pub fn addImport(itab: *ImportTable, allocator: Allocator, target: SymbolWithLoc) !ImportIndex { + try itab.entries.ensureUnusedCapacity(allocator, 1); + const index: u32 = blk: { + if (itab.free_list.popOrNull()) |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 = @intCast(u32, 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_file: *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_file.sections.items(.header)[ctx.coff_file.idata_section_index.?]; + var addr = header.virtual_address; + for (ctx.coff_file.import_tables.values(), 0..) |other_itab, i| { + if (ctx.index == i) break; + addr += @intCast(u32, other_itab.entries.items.len * @sizeOf(u64)) + 8; + } + return addr; +} + +pub 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 FormatContext = struct { + itab: ImportTable, + ctx: Context, +}; + +fn fmt( + fmt_ctx: FormatContext, + comptime unused_format_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + _ = options; + comptime assert(unused_format_string.len == 0); + const lib_name = fmt_ctx.ctx.coff_file.temp_strtab.getAssumeExists(fmt_ctx.ctx.name_off); + const base_vaddr = getBaseAddress(fmt_ctx.ctx); + try writer.print("IAT({s}.dll) @{x}:", .{ lib_name, base_vaddr }); + for (fmt_ctx.itab.entries.items, 0..) |entry, i| { + try writer.print("\n {d}@{?x} => {s}", .{ + i, + fmt_ctx.itab.getImportAddress(entry, fmt_ctx.ctx), + fmt_ctx.ctx.coff_file.getSymbolName(entry), + }); + } +} + +fn format(itab: ImportTable, comptime unused_format_string: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = itab; + _ = unused_format_string; + _ = options; + _ = writer; + @compileError("do not format ImportTable directly; use itab.fmtDebug()"); +} + +pub fn fmtDebug(itab: ImportTable, ctx: Context) std.fmt.Formatter(fmt) { + return .{ .data = .{ .itab = itab, .ctx = ctx } }; +} + +const ImportIndex = u32; +const ImportTable = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); + +const Allocator = std.mem.Allocator; +const Coff = @import("../Coff.zig"); +const SymbolWithLoc = Coff.SymbolWithLoc; diff --git a/src/link/Coff/Relocation.zig b/src/link/Coff/Relocation.zig index 5dcb0552fc..45f3a97052 100644 --- a/src/link/Coff/Relocation.zig +++ b/src/link/Coff/Relocation.zig @@ -61,8 +61,13 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 { .import, .import_page, .import_pageoff => { const sym = coff_file.getSymbol(self.target); - const itab = coff_file.import_tables.get(sym.value) orelse return null; - return itab.getImportAddress(coff_file, self.target); + const index = coff_file.import_tables.getIndex(sym.value) orelse return null; + const itab = coff_file.import_tables.values()[index]; + return itab.getImportAddress(self.target, .{ + .coff_file = coff_file, + .index = index, + .name_off = sym.value, + }); }, } }