From d2040b2763ad2684dcacce9acd8f8511bf9db397 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 27 Mar 2023 16:19:43 +0200 Subject: [PATCH 1/7] coff: grow .idata if required --- src/link/Coff.zig | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f210f2f2b3..2374bf9517 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1625,12 +1625,10 @@ fn writeBaseRelocations(self: *Coff) !void { const needed_size = @intCast(u32, buffer.items.len); if (needed_size > sect_capacity) { const new_offset = self.findFreeSpace(needed_size, default_file_alignment); - log.debug("writing {s} at 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ + log.debug("moving {s} from 0x{x} to 0x{x}", .{ self.getSectionName(header), header.pointer_to_raw_data, - header.pointer_to_raw_data + needed_size, new_offset, - new_offset + needed_size, }); header.pointer_to_raw_data = new_offset; @@ -1656,11 +1654,11 @@ fn writeImportTable(self: *Coff) !void { const gpa = self.base.allocator; - const section = self.sections.get(self.idata_section_index.?); - const last_atom_index = section.last_atom_index orelse return; + const last_atom_index = self.sections.items(.last_atom_index)[self.idata_section_index.?] orelse return; + const header = &self.sections.items(.header)[self.idata_section_index.?]; const last_atom = self.getAtom(last_atom_index); - const iat_rva = section.header.virtual_address; + const iat_rva = header.virtual_address; const iat_size = last_atom.getSymbol(self).value + last_atom.size * 2 - iat_rva; // account for sentinel zero pointer const dll_name = "KERNEL32.dll"; @@ -1696,9 +1694,18 @@ fn writeImportTable(self: *Coff) !void { try lookup_table.append(.{ .name_table_rva = 0 }); // the sentinel const dir_entry_size = @sizeOf(coff.ImportDirectoryEntry) + lookup_table.items.len * @sizeOf(coff.ImportLookupEntry64.ByName) + names_table.items.len + dll_name.len + 1; - const needed_size = iat_size + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry); - const sect_capacity = self.allocatedSize(section.header.pointer_to_raw_data); - assert(needed_size < sect_capacity); // TODO: implement expanding .idata section + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size = @intCast(u32, iat_size + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry)); + if (needed_size > sect_capacity) { + const new_offset = self.findFreeSpace(needed_size, default_file_alignment); + log.debug("moving .idata from 0x{x} to 0x{x}", .{ header.pointer_to_raw_data, new_offset }); + header.pointer_to_raw_data = new_offset; + + const sect_vm_capacity = self.allocatedVirtualSize(header.virtual_address); + if (needed_size > sect_vm_capacity) { + try self.growSectionVM(self.idata_section_index.?, needed_size); + } + } // Fixup offsets const base_rva = iat_rva + iat_size; @@ -1719,10 +1726,10 @@ fn writeImportTable(self: *Coff) !void { buffer.appendSliceAssumeCapacity(dll_name); buffer.appendAssumeCapacity(0); - try self.base.file.?.pwriteAll(buffer.items, section.header.pointer_to_raw_data + iat_size); + try self.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data + iat_size); // Override the IAT atoms // TODO: we should rewrite only dirtied atoms, but that's for way later - try self.base.file.?.pwriteAll(mem.sliceAsBytes(lookup_table.items), section.header.pointer_to_raw_data); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(lookup_table.items), header.pointer_to_raw_data); self.data_directories[@enumToInt(coff.DirectoryEntry.IMPORT)] = .{ .virtual_address = iat_rva + iat_size, From 0dc210f950ff926fb8e57288a8ff257abc942b6d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 27 Mar 2023 18:23:25 +0200 Subject: [PATCH 2/7] link: pass expected lib name as hint in getGlobalSymbol() --- src/arch/aarch64/CodeGen.zig | 14 ++++---------- src/arch/wasm/CodeGen.zig | 2 +- src/arch/x86_64/CodeGen.zig | 14 ++++---------- src/link.zig | 12 +++++++----- src/link/Coff.zig | 3 ++- src/link/MachO.zig | 3 ++- src/link/Wasm.zig | 3 ++- 7 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index e2e2ce9ead..ee23696950 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -4318,16 +4318,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier }); } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; - const decl_name = mod.declPtr(extern_fn.owner_decl).name; - if (extern_fn.lib_name) |lib_name| { - log.debug("TODO enforce that '{s}' is expected in '{s}' library", .{ - decl_name, - lib_name, - }); - } - + const decl_name = mem.sliceTo(mod.declPtr(extern_fn.owner_decl).name, 0); + const lib_name = mem.sliceTo(extern_fn.lib_name, 0); if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); + const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; _ = try self.addInst(.{ @@ -4340,7 +4334,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier }, }); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const sym_index = try coff_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); + const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name); try self.genSetReg(Type.initTag(.u64), .x30, .{ .linker_load = .{ .type = .import, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 9af66eb40c..199ddada65 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -6121,7 +6121,7 @@ fn callIntrinsic( args: []const WValue, ) InnerError!WValue { assert(param_types.len == args.len); - const symbol_index = func.bin_file.base.getGlobalSymbol(name) catch |err| { + const symbol_index = func.bin_file.base.getGlobalSymbol(name, null) catch |err| { return func.fail("Could not find or create global symbol '{s}'", .{@errorName(err)}); }; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index b071d14447..5ddc9c77ca 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -5317,16 +5317,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; - const decl_name = mod.declPtr(extern_fn.owner_decl).name; - if (extern_fn.lib_name) |lib_name| { - log.debug("TODO enforce that '{s}' is expected in '{s}' library", .{ - decl_name, - lib_name, - }); - } - + const decl_name = mem.sliceTo(mod.declPtr(extern_fn.owner_decl).name, 0); + const lib_name = mem.sliceTo(extern_fn.lib_name, 0); if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const sym_index = try coff_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); + const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name); try self.genSetReg(Type.initTag(.usize), .rax, .{ .linker_load = .{ .type = .import, @@ -5335,7 +5329,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier }); try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); + const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; _ = try self.addInst(.{ diff --git a/src/link.zig b/src/link.zig index ae6c02c08b..a7c3ac51bc 100644 --- a/src/link.zig +++ b/src/link.zig @@ -504,18 +504,20 @@ pub const File = struct { /// Called from within CodeGen to retrieve the symbol index of a global symbol. /// If no symbol exists yet with this name, a new undefined global symbol will /// be created. This symbol may get resolved once all relocatables are (re-)linked. - pub fn getGlobalSymbol(base: *File, name: []const u8) UpdateDeclError!u32 { + /// Optionally, it is possible to specify where to expect the symbol defined if it + /// is an import. + pub fn getGlobalSymbol(base: *File, name: []const u8, lib_name: ?[]const u8) UpdateDeclError!u32 { if (build_options.only_c) @compileError("unreachable"); - log.debug("getGlobalSymbol '{s}'", .{name}); + log.debug("getGlobalSymbol '{s}' (expected in '{?s}')", .{ name, lib_name }); switch (base.tag) { // zig fmt: off - .coff => return @fieldParentPtr(Coff, "base", base).getGlobalSymbol(name), + .coff => return @fieldParentPtr(Coff, "base", base).getGlobalSymbol(name, lib_name), .elf => unreachable, - .macho => return @fieldParentPtr(MachO, "base", base).getGlobalSymbol(name), + .macho => return @fieldParentPtr(MachO, "base", base).getGlobalSymbol(name, lib_name), .plan9 => unreachable, .spirv => unreachable, .c => unreachable, - .wasm => return @fieldParentPtr(Wasm, "base", base).getGlobalSymbol(name), + .wasm => return @fieldParentPtr(Wasm, "base", base).getGlobalSymbol(name, lib_name), .nvptx => unreachable, // zig fmt: on } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2374bf9517..8d2a4ad16e 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1526,7 +1526,8 @@ pub fn getDeclVAddr(self: *Coff, decl_index: Module.Decl.Index, reloc_info: link return 0; } -pub fn getGlobalSymbol(self: *Coff, name: []const u8) !u32 { +pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name: ?[]const u8) !u32 { + _ = lib_name; const gop = try self.getOrPutGlobalPtr(name); const global_index = self.getGlobalIndex(name).?; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2f594d1fda..885afd37c3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3202,7 +3202,8 @@ fn insertSection(self: *MachO, segment_index: u8, header: macho.section_64) !u8 return insertion_index; } -pub fn getGlobalSymbol(self: *MachO, name: []const u8) !u32 { +pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 { + _ = lib_name; const gpa = self.base.allocator; const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name}); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 3561c86ce8..e59b83176c 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1573,7 +1573,8 @@ pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: Module.Decl.In /// such as an exported or imported symbol. /// If the symbol does not yet exist, creates a new one symbol instead /// and then returns the index to it. -pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8) !u32 { +pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !u32 { + _ = lib_name; const name_index = try wasm.string_table.put(wasm.base.allocator, name); const gop = try wasm.globals.getOrPut(wasm.base.allocator, name_index); if (gop.found_existing) { From 2a5c4ea8f08d212b10d4dc8748fbaf5beddecbb6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 27 Mar 2023 20:45:32 +0200 Subject: [PATCH 3/7] coff: repurpose value field of import Symbol for lib_name offset --- src/link/Coff.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 8d2a4ad16e..a87996001e 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -64,6 +64,8 @@ globals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, strtab_offset: ?u32 = null, +temp_strtab: StringTable(.temp_strtab) = .{}, + got_entries: std.ArrayListUnmanaged(Entry) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, got_entries_table: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, @@ -309,6 +311,7 @@ pub fn deinit(self: *Coff) void { self.locals_free_list.deinit(gpa); self.globals_free_list.deinit(gpa); self.strtab.deinit(gpa); + self.temp_strtab.deinit(gpa); self.got_entries.deinit(gpa); self.got_entries_free_list.deinit(gpa); self.got_entries_table.deinit(gpa); @@ -358,6 +361,8 @@ fn populateMissingMetadata(self: *Coff) !void { try self.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32)); self.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32)); + try self.temp_strtab.buffer.append(gpa, 0); + // Index 0 is always a null symbol. try self.locals.append(gpa, .{ .name = [_]u8{0} ** 8, @@ -1526,8 +1531,7 @@ pub fn getDeclVAddr(self: *Coff, decl_index: Module.Decl.Index, reloc_info: link return 0; } -pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name: ?[]const u8) !u32 { - _ = lib_name; +pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name_name: ?[]const u8) !u32 { const gop = try self.getOrPutGlobalPtr(name); const global_index = self.getGlobalIndex(name).?; @@ -1544,6 +1548,12 @@ pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name: ?[]const u8) !u3 try self.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 self.temp_strtab.insert(gpa, lib_name); + } + try self.unresolved.putNoClobber(gpa, global_index, true); return global_index; From fb3345e346d26cfac01e45e3662385c587ec3462 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Mar 2023 10:40:19 +0200 Subject: [PATCH 4/7] coff: do not use atoms for synthetic import address table Instead, introduce a custom ImportTable structure which will act as a thunk in the MachO linker, and we will use that to calculate the address of a pointer on-the-fly. Additionally, fix logic in writeImportTables to allow for multiple DLLs. --- src/link/Coff.zig | 368 +++++++++++++++++++++++------------ src/link/Coff/Relocation.zig | 36 ++-- 2 files changed, 258 insertions(+), 146 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a87996001e..bcc507b02f 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -70,9 +70,10 @@ got_entries: std.ArrayListUnmanaged(Entry) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, got_entries_table: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, -imports: std.ArrayListUnmanaged(Entry) = .{}, -imports_free_list: std.ArrayListUnmanaged(u32) = .{}, -imports_table: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, +/// 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) = .{}, +imports_count_dirty: bool = true, /// Virtual address of the entry point procedure relative to image base. entry_addr: ?u32 = null, @@ -159,6 +160,92 @@ 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, @@ -315,9 +402,11 @@ pub fn deinit(self: *Coff) void { self.got_entries.deinit(gpa); self.got_entries_free_list.deinit(gpa); self.got_entries_table.deinit(gpa); - self.imports.deinit(gpa); - self.imports_free_list.deinit(gpa); - self.imports_table.deinit(gpa); + + for (self.import_tables.values()) |*itab| { + itab.deinit(gpa); + } + self.import_tables.deinit(gpa); { var it = self.decls.iterator(); @@ -730,28 +819,6 @@ pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { return index; } -pub fn allocateImportEntry(self: *Coff, target: SymbolWithLoc) !u32 { - const gpa = self.base.allocator; - try self.imports.ensureUnusedCapacity(gpa, 1); - - const index: u32 = blk: { - if (self.imports_free_list.popOrNull()) |index| { - log.debug(" (reusing import entry index {d})", .{index}); - break :blk index; - } else { - log.debug(" (allocating import entry at index {d})", .{self.imports.items.len}); - const index = @intCast(u32, self.imports.items.len); - _ = self.imports.addOneAssumeCapacity(); - break :blk index; - } - }; - - self.imports.items[index] = .{ .target = target, .sym_index = 0 }; - try self.imports_table.putNoClobber(gpa, target, index); - - return index; -} - pub fn createAtom(self: *Coff) !Atom.Index { const gpa = self.base.allocator; const atom_index = @intCast(Atom.Index, self.atoms.items.len); @@ -802,21 +869,6 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !Atom.Index { return atom_index; } -fn createImportAtom(self: *Coff) !Atom.Index { - const atom_index = try self.createAtom(); - const atom = self.getAtomPtr(atom_index); - atom.size = @sizeOf(u64); - atom.alignment = @alignOf(u64); - - const sym = atom.getSymbolPtr(self); - sym.section_number = @intToEnum(coff.SectionNumber, self.idata_section_index.? + 1); - sym.value = try self.allocateAtom(atom_index, atom.size, atom.alignment); - - log.debug("allocated import atom at 0x{x}", .{sym.value}); - - return atom_index; -} - fn growAtom(self: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignment: u32) !u32 { const atom = self.getAtom(atom_index); const sym = atom.getSymbol(self); @@ -876,10 +928,8 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void { var it = self.relocs.valueIterator(); while (it.next()) |relocs| { for (relocs.items) |*reloc| { - const target_atom_index = reloc.getTargetAtomIndex(self) orelse continue; - const target_atom = self.getAtom(target_atom_index); - const target_sym = target_atom.getSymbol(self); - if (target_sym.value < addr) continue; + const target_vaddr = reloc.getTargetAddress(self) orelse continue; + if (target_vaddr < addr) continue; reloc.dirty = true; } } @@ -1468,35 +1518,42 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + const gpa = self.base.allocator; + while (self.unresolved.popOrNull()) |entry| { assert(entry.value); // We only expect imports generated by the incremental linker for now. const global = self.globals.items[entry.key]; - if (self.imports_table.contains(global)) continue; - - const import_index = try self.allocateImportEntry(global); - const import_atom_index = try self.createImportAtom(); - const import_atom = self.getAtom(import_atom_index); - self.imports.items[import_index].sym_index = import_atom.getSymbolIndex().?; - try self.writePtrWidthAtom(import_atom_index); - } - - if (build_options.enable_logging) { - self.logSymtab(); + const sym = self.getSymbol(global); + 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) }; + } + 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); + self.imports_count_dirty = true; } + try self.writeImportTables(); { var it = self.relocs.keyIterator(); while (it.next()) |atom| { try self.resolveRelocs(atom.*); } } - try self.writeImportTable(); try self.writeBaseRelocations(); if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; } + if (build_options.enable_logging) { + self.logSymtab(); + self.logImportTables(); + } + try self.writeStrtab(); try self.writeDataDirectoriesHeaders(); try self.writeSectionHeaders(); @@ -1660,53 +1717,36 @@ fn writeBaseRelocations(self: *Coff) !void { }; } -fn writeImportTable(self: *Coff) !void { +fn writeImportTables(self: *Coff) !void { if (self.idata_section_index == null) return; + if (!self.imports_count_dirty) return; const gpa = self.base.allocator; - const last_atom_index = self.sections.items(.last_atom_index)[self.idata_section_index.?] orelse return; + const ext = ".dll"; const header = &self.sections.items(.header)[self.idata_section_index.?]; - const last_atom = self.getAtom(last_atom_index); - const iat_rva = header.virtual_address; - const iat_size = last_atom.getSymbol(self).value + last_atom.size * 2 - iat_rva; // account for sentinel zero pointer - - const dll_name = "KERNEL32.dll"; - - var import_dir_entry = coff.ImportDirectoryEntry{ - .import_lookup_table_rva = @sizeOf(coff.ImportDirectoryEntry) * 2, - .time_date_stamp = 0, - .forwarder_chain = 0, - .name_rva = 0, - .import_address_table_rva = iat_rva, - }; - - // TODO: we currently assume there's only one (implicit) DLL - ntdll - var lookup_table = std.ArrayList(coff.ImportLookupEntry64.ByName).init(gpa); - defer lookup_table.deinit(); - - var names_table = std.ArrayList(u8).init(gpa); - defer names_table.deinit(); - - // TODO: check if import is still valid - for (self.imports.items) |entry| { - const target_name = self.getSymbolName(entry.target); - const start = names_table.items.len; - mem.writeIntLittle(u16, try names_table.addManyAsArray(2), 0); // TODO: currently, hint is set to 0 as we haven't yet parsed any DLL - try names_table.appendSlice(target_name); - try names_table.append(0); - const end = names_table.items.len; - if (!mem.isAlignedGeneric(usize, end - start, @sizeOf(u16))) { - try names_table.append(0); + // Calculate needed size + var iat_size: u32 = 0; + var dir_table_size: u32 = @sizeOf(coff.ImportDirectoryEntry); // sentinel + var lookup_table_size: u32 = 0; + var names_table_size: u32 = 0; + var dll_names_size: u32 = 0; + for (self.import_tables.keys(), 0..) |off, i| { + const lib_name = self.temp_strtab.getAssumeExists(off); + const itable = self.import_tables.values()[i]; + iat_size += itable.size() + 8; + dir_table_size += @sizeOf(coff.ImportDirectoryEntry); + lookup_table_size += @intCast(u32, itable.entries.items.len + 1) * @sizeOf(coff.ImportLookupEntry64.ByName); + for (itable.entries.items) |entry| { + const sym_name = self.getSymbolName(entry); + names_table_size += 2 + mem.alignForwardGeneric(u32, @intCast(u32, sym_name.len + 1), 2); } - try lookup_table.append(.{ .name_table_rva = @intCast(u31, start) }); + dll_names_size += @intCast(u32, lib_name.len + ext.len + 1); } - try lookup_table.append(.{ .name_table_rva = 0 }); // the sentinel - const dir_entry_size = @sizeOf(coff.ImportDirectoryEntry) + lookup_table.items.len * @sizeOf(coff.ImportLookupEntry64.ByName) + names_table.items.len + dll_name.len + 1; + const needed_size = iat_size + dir_table_size + lookup_table_size + names_table_size + dll_names_size; const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); - const needed_size = @intCast(u32, iat_size + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry)); if (needed_size > sect_capacity) { const new_offset = self.findFreeSpace(needed_size, default_file_alignment); log.debug("moving .idata from 0x{x} to 0x{x}", .{ header.pointer_to_raw_data, new_offset }); @@ -1716,41 +1756,105 @@ fn writeImportTable(self: *Coff) !void { if (needed_size > sect_vm_capacity) { try self.growSectionVM(self.idata_section_index.?, needed_size); } + + header.virtual_size = @max(header.virtual_size, needed_size); + header.size_of_raw_data = needed_size; } - // Fixup offsets - const base_rva = iat_rva + iat_size; - import_dir_entry.import_lookup_table_rva += base_rva; - import_dir_entry.name_rva = @intCast(u32, base_rva + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry) - dll_name.len - 1); - - for (lookup_table.items[0 .. lookup_table.items.len - 1]) |*lk| { - lk.name_table_rva += @intCast(u31, base_rva + @sizeOf(coff.ImportDirectoryEntry) * 2 + lookup_table.items.len * @sizeOf(coff.ImportLookupEntry64.ByName)); - } - + // Do the actual writes var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); - try buffer.ensureTotalCapacity(dir_entry_size + @sizeOf(coff.ImportDirectoryEntry)); - buffer.appendSliceAssumeCapacity(mem.asBytes(&import_dir_entry)); - buffer.appendNTimesAssumeCapacity(0, @sizeOf(coff.ImportDirectoryEntry)); // the sentinel; TODO: I think doing all of the above on bytes directly might be cleaner - buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(lookup_table.items)); - buffer.appendSliceAssumeCapacity(names_table.items); - buffer.appendSliceAssumeCapacity(dll_name); - buffer.appendAssumeCapacity(0); + try buffer.ensureTotalCapacityPrecise(needed_size); + buffer.resize(needed_size) catch unreachable; - try self.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data + iat_size); - // Override the IAT atoms - // TODO: we should rewrite only dirtied atoms, but that's for way later - try self.base.file.?.pwriteAll(mem.sliceAsBytes(lookup_table.items), header.pointer_to_raw_data); + const dir_header_size = @sizeOf(coff.ImportDirectoryEntry); + const lookup_entry_size = @sizeOf(coff.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 (self.import_tables.keys(), 0..) |off, i| { + const lib_name = self.temp_strtab.getAssumeExists(off); + const itable = self.import_tables.values()[i]; + + // Lookup table header + const lookup_header = coff.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, + }; + mem.copy(u8, buffer.items[dir_table_offset..], mem.asBytes(&lookup_header)); + dir_table_offset += dir_header_size; + + for (itable.entries.items) |entry| { + const import_name = self.getSymbolName(entry); + + // IAT and lookup table entry + const lookup = coff.ImportLookupEntry64.ByName{ .name_table_rva = @intCast(u31, header.virtual_address + names_table_offset) }; + mem.copy(u8, buffer.items[iat_offset..], mem.asBytes(&lookup)); + iat_offset += lookup_entry_size; + mem.copy(u8, buffer.items[lookup_table_offset..], mem.asBytes(&lookup)); + lookup_table_offset += lookup_entry_size; + + // Names table entry + mem.writeIntLittle(u16, buffer.items[names_table_offset..][0..2], 0); // Hint set to 0 until we learn how to parse DLLs + names_table_offset += 2; + mem.copy(u8, buffer.items[names_table_offset..], import_name); + names_table_offset += @intCast(u32, 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.writeIntLittle(u64, buffer.items[iat_offset..][0..lookup_entry_size], 0); + iat_offset += 8; + + // Lookup table sentinel + mem.copy(u8, buffer.items[lookup_table_offset..], mem.asBytes(&coff.ImportLookupEntry64.ByName{ .name_table_rva = 0 })); + lookup_table_offset += lookup_entry_size; + + // DLL name + mem.copy(u8, buffer.items[dll_names_offset..], lib_name); + dll_names_offset += @intCast(u32, lib_name.len); + mem.copy(u8, buffer.items[dll_names_offset..], ext); + dll_names_offset += @intCast(u32, ext.len); + buffer.items[dll_names_offset] = 0; + dll_names_offset += 1; + } + + // Sentinel + const lookup_header = coff.ImportDirectoryEntry{ + .import_lookup_table_rva = 0, + .time_date_stamp = 0, + .forwarder_chain = 0, + .name_rva = 0, + .import_address_table_rva = 0, + }; + mem.copy(u8, buffer.items[dir_table_offset..], mem.asBytes(&lookup_header)); + dir_table_offset += dir_header_size; + + assert(dll_names_offset == needed_size); + + try self.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data); self.data_directories[@enumToInt(coff.DirectoryEntry.IMPORT)] = .{ - .virtual_address = iat_rva + iat_size, - .size = @intCast(u32, @sizeOf(coff.ImportDirectoryEntry) * 2), + .virtual_address = header.virtual_address + iat_size, + .size = dir_table_size, }; - self.data_directories[@enumToInt(coff.DirectoryEntry.IAT)] = .{ - .virtual_address = iat_rva, + .virtual_address = header.virtual_address, .size = iat_size, }; + + self.imports_count_dirty = false; } fn writeStrtab(self: *Coff) !void { @@ -2139,14 +2243,6 @@ pub fn getGotAtomIndexForSymbol(self: *const Coff, sym_loc: SymbolWithLoc) ?Atom return self.getAtomIndexForSymbol(.{ .sym_index = got_entry.sym_index, .file = null }); } -/// Returns import atom that references `sym_loc` if one exists. -/// Returns null otherwise. -pub fn getImportAtomIndexForSymbol(self: *const Coff, sym_loc: SymbolWithLoc) ?Atom.Index { - const imports_index = self.imports_table.get(sym_loc) orelse return null; - const imports_entry = self.imports.items[imports_index]; - return self.getAtomIndexForSymbol(.{ .sym_index = imports_entry.sym_index, .file = null }); -} - fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { if (name.len <= 8) { mem.copy(u8, &header.name, name); @@ -2267,3 +2363,19 @@ 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), + }); + } + } +} diff --git a/src/link/Coff/Relocation.zig b/src/link/Coff/Relocation.zig index 1ba1d7a1c1..5dcb0552fc 100644 --- a/src/link/Coff/Relocation.zig +++ b/src/link/Coff/Relocation.zig @@ -45,23 +45,25 @@ pcrel: bool, length: u2, dirty: bool = true, -/// Returns an Atom which is the target node of this relocation edge (if any). -pub fn getTargetAtomIndex(self: Relocation, coff_file: *const Coff) ?Atom.Index { +/// Returns address of the target if any. +pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 { switch (self.type) { - .got, - .got_page, - .got_pageoff, - => return coff_file.getGotAtomIndexForSymbol(self.target), + .got, .got_page, .got_pageoff, .direct, .page, .pageoff => { + const maybe_target_atom_index = switch (self.type) { + .got, .got_page, .got_pageoff => coff_file.getGotAtomIndexForSymbol(self.target), + .direct, .page, .pageoff => coff_file.getAtomIndexForSymbol(self.target), + else => unreachable, + }; + const target_atom_index = maybe_target_atom_index orelse return null; + const target_atom = coff_file.getAtom(target_atom_index); + return target_atom.getSymbol(coff_file).value; + }, - .direct, - .page, - .pageoff, - => return coff_file.getAtomIndexForSymbol(self.target), - - .import, - .import_page, - .import_pageoff, - => return coff_file.getImportAtomIndexForSymbol(self.target), + .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); + }, } } @@ -73,9 +75,7 @@ pub fn resolve(self: *Relocation, atom_index: Atom.Index, coff_file: *Coff) !voi const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address; - const target_atom_index = self.getTargetAtomIndex(coff_file) orelse return; - const target_atom = coff_file.getAtom(target_atom_index); - const target_vaddr = target_atom.getSymbol(coff_file).value; + const target_vaddr = self.getTargetAddress(coff_file) orelse return; const target_vaddr_with_addend = target_vaddr + self.addend; log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) (in file at 0x{x})", .{ From db877df8eb1831891fc1f3cb6dab1503868b5732 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Mar 2023 14:13:39 +0200 Subject: [PATCH 5/7] coff: move import table definition into a separate ImportTable.zig module --- CMakeLists.txt | 1 + src/link/Coff.zig | 174 +++++++++------------------------- src/link/Coff/ImportTable.zig | 133 ++++++++++++++++++++++++++ src/link/Coff/Relocation.zig | 9 +- 4 files changed, 186 insertions(+), 131 deletions(-) create mode 100644 src/link/Coff/ImportTable.zig 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, + }); }, } } From 70838d34aa581c9723d39592329827cf671cf0e9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Mar 2023 14:17:12 +0200 Subject: [PATCH 6/7] coff: assert the imports table is not dirty --- src/link/Coff.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 6dcad604ac..df525bba90 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1451,6 +1451,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod self.error_flags.no_entry_point_found = false; try self.writeHeader(); } + + assert(!self.imports_count_dirty); } pub fn getDeclVAddr(self: *Coff, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 { From 1c5f2557894539d7620933324dad48a43a16d0ef Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Mar 2023 15:49:26 +0200 Subject: [PATCH 7/7] tests: enable multi-threaded x86_64-windows tests with self-hosted --- test/tests.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/test/tests.zig b/test/tests.zig index 517d789b18..26e684cd0d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -112,7 +112,6 @@ const test_targets = blk: { .os_tag = .windows, .abi = .gnu, }, - .single_threaded = true, // https://github.com/ziglang/zig/issues/15075 .backend = .stage2_x86_64, },