zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit a2f4459da80937e8ca2a19d6298c90690f7ddfcd (tree)
parent 8bf109b0b34d1d97a71a447ab4d79a89125a2de2
Author: kcbanner <kcbanner@gmail.com>
Date:   Fri,  5 Jun 2026 01:55:35 -0400

Coff: entry point detection and IAT fixes

- Track which symbols reference IAT entries, and update them when the IAT moves
- Fix IAT flushMoved logic to account for ordinal entries
- Select which entry to use based on subsystem / image type
- Add errors for undefined / missing entry points
- When linking an image without a zcu, pick an entry point based on which main function was exported
- Fix up shifting differently aligned nodes when resizing a node

Diffstat:
Msrc/link/Coff.zig | 230++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/link/MappedFile.zig | 6+++---
2 files changed, 167 insertions(+), 69 deletions(-)

diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -74,12 +74,12 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct { alignment: InternPool.Alignment, }), relocs: std.ArrayList(Reloc), +entry: Node.GlobalMapIndex, const_prog_node: std.Progress.Node, synth_prog_node: std.Progress.Node, symbol_prog_node: std.Progress.Node, member_prog_node: std.Progress.Node, input_prog_node: std.Progress.Node, -subsystem: ?std.zig.Subsystem, dump_snapshot: bool, pub const default_file_alignment: u16 = 0x200; @@ -728,10 +728,37 @@ pub const ImportTable = struct { import_lookup_table_ni: MappedFile.Node.Index, import_address_table_si: Symbol.Index, import_hint_name_table_ni: MappedFile.Node.Index, + // All .iat_ptr globals that reference this table. + // This is separate from `iat_symbol_indices` because multiple symbols + // can reference to the same iat entry, after name demangling. + import_address_table_symbols: std.ArrayList(Symbol.Index), len: u32, hint_name_len: u32, }; + pub fn TableEntry(comptime magic: std.coff.OptionalHeader.Magic) type { + const Payload = packed union(u31) { + ordinal: packed struct(u31) { + ordinal: u16, + _: u15 = 0, + }, + hint_name_rva: u31, + }; + + return switch (magic) { + _ => comptime unreachable, + .PE32 => packed struct(u32) { + payload: Payload, + is_ordinal: bool, + }, + .@"PE32+" => packed struct(u64) { + payload: Payload, + _: u32 = 0, + is_ordinal: bool, + }, + }; + } + const Adapter = struct { coff: *Coff, @@ -864,7 +891,8 @@ pub const Symbol = struct { dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - _: u8 = 0, + is_entry: bool, + _: u7 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -1038,7 +1066,15 @@ pub const Symbol = struct { } pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff) void { - var ri = si.get(coff).target_relocs; + const sym = si.get(coff); + + // TODO: Would this be better modeled using an actual reloc? Would need a si for the header + if (sym.flags.is_entry) { + log.debug("updateEntryRVA({d}, 0x{x})", .{ si, sym.rva }); + coff.optionalHeaderStandardPtr().address_of_entry_point = sym.rva; + } + + var ri = sym.target_relocs; while (ri != .none) { const reloc = ri.get(coff); assert(reloc.target == si); @@ -1525,12 +1561,12 @@ fn create( }), .pending_uavs = .empty, .relocs = .empty, + .entry = .none, .const_prog_node = .none, .synth_prog_node = .none, .symbol_prog_node = .none, .member_prog_node = .none, .input_prog_node = .none, - .subsystem = options.subsystem, .dump_snapshot = options.enable_link_snapshots, }; errdefer coff.deinit(); @@ -1549,6 +1585,11 @@ fn create( major_subsystem_version, minor_subsystem_version, magic, + if (options.subsystem) |s| switch (s) { + .console => .WINDOWS_CUI, + .windows => .WINDOWS_GUI, + else => return error.UnsupportedCOFFSubsystem, + } else .WINDOWS_CUI, section_align, std.fs.path.basename(path.sub_path), ); @@ -1619,6 +1660,10 @@ fn isArchive(coff: *const Coff) bool { }; } +fn isExe(coff: *const Coff) bool { + return coff.base.comp.config.output_mode == .Exe; +} + fn isObj(coff: *const Coff) bool { return coff.base.comp.config.output_mode == .Obj; } @@ -1639,6 +1684,7 @@ fn initHeaders( major_subsystem_version: u16, minor_subsystem_version: u16, magic: std.coff.OptionalHeader.Magic, + subsystem: std.coff.Subsystem, section_align: std.mem.Alignment, file_name: []const u8, ) !void { @@ -1841,7 +1887,7 @@ fn initHeaders( .size_of_image = 0, .size_of_headers = 0, .checksum = 0, - .subsystem = .WINDOWS_CUI, + .subsystem = subsystem, .dll_flags = .{ .HIGH_ENTROPY_VA = true, .DYNAMIC_BASE = true, @@ -1890,7 +1936,7 @@ fn initHeaders( .size_of_image = 0, .size_of_headers = 0, .checksum = 0, - .subsystem = .WINDOWS_CUI, + .subsystem = subsystem, .dll_flags = .{ .HIGH_ENTROPY_VA = true, .DYNAMIC_BASE = true, @@ -2079,9 +2125,6 @@ pub fn initBuiltins(coff: *Coff) !void { const gpa = comp.gpa; const target = &comp.root_mod.resolved_target.result; if (coff.isImage()) { - try coff.symbols.ensureUnusedCapacity(gpa, 1); - try coff.globals.ensureUnusedCapacity(gpa, 1); - const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data }); const sym = si.get(coff); sym.ni = Node.known.header; @@ -2099,8 +2142,16 @@ pub fn initBuiltins(coff: *Coff) !void { for (lists) |list| { const addr_info = coff.targetAddrInfo(); - const start_osmi = try coff.objectSectionMapIndex(list.start, addr_info.alignment, .{ .read = true }); - const end_osmi = try coff.objectSectionMapIndex(list.end, addr_info.alignment, .{ .read = true }); + const start_osmi = try coff.objectSectionMapIndex( + list.start, + addr_info.alignment, + .{ .read = true }, + ); + const end_osmi = try coff.objectSectionMapIndex( + list.end, + addr_info.alignment, + .{ .read = true }, + ); const start_sym = start_osmi.symbol(coff).get(coff); try start_sym.ni.resize(&coff.mf, gpa, addr_info.size); @@ -2460,6 +2511,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .type = .unknown, .dll_storage_class = .default, .weak_external_strat = undefined, + .is_entry = false, }, .loc_relocs = .none, .target_relocs = .none, @@ -2482,14 +2534,14 @@ fn getOrPutString(coff: *Coff, string: []const u8) !String { fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional { return (try coff.getOrPutString(string orelse return .none)).toOptional(); } -fn getString(coff: *Coff, string: []const u8) ?String { +fn getString(coff: *Coff, string: []const u8) String.Optional { if (coff.strings.getKeyAdapted( string, std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes }, )) |key| - return @enumFromInt(key) + return @as(String, @enumFromInt(key)).toOptional() else - return null; + return .none; } /// If the name does not fit in the symbol header, adds it to the symbol table string table. @@ -4882,12 +4934,13 @@ fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { _ = prog_node; + const base = coff.base; + const comp = base.comp; + log.debug("prelink()", .{}); if (coff.pending_default_libs.items.len > 0) { // Libs provided by /DEFAULTLIB arguments in objects are searched after all other inputs - const base = coff.base; - const comp = base.comp; const gpa = comp.gpa; const arena = comp.arena; const target = &comp.root_mod.resolved_target.result; @@ -4949,6 +5002,34 @@ pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { } } + if (coff.isImage() and comp.config.link_libc) { + const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe()) + if (comp.zcu == null) switch (coff.optionalHeaderField(.subsystem)) { + .WINDOWS_CUI => &.{ + .{ "main", "mainCRTStartup" }, + .{ "wmain", "wmainCRTStartup" }, + }, + .WINDOWS_GUI => &.{ + .{ "WinMain", "WinMainCRTStartup" }, + .{ "wWinMain", "wWinMainCRTStartup" }, + }, + else => unreachable, + } else &.{} + else + &.{.{ null, "_DllMainCRTStartup" }}; + + for (entries) |entry| { + if (entry[0]) |required_name| { + const str = coff.getString(required_name).unwrap() orelse continue; + const si = coff.globals.get(.{ .name = str, .lib_name = .none }) orelse continue; + if (si.get(coff).ni == .none) continue; + } + + const si = try coff.globalSymbol(.{ .name = entry[1], .type = .code }); + coff.updateEntry(si.get(coff).gmi); + } + } + coff.inputs_complete = true; } @@ -5241,6 +5322,17 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { const gpa = comp.gpa; const max_notes = 4; + if (coff.isImage()) { + if (coff.entry == .none) + comp.link_diags.addError("no entry point defined", .{}) + else if (coff.entry.symbol(coff).get(coff).ni == .none) { + comp.link_diags.addError( + "no definition for entry point '{s}' found", + .{coff.entry.globalName(coff).name.toSlice(coff)}, + ); + } + } + var undef_indices: std.ArrayListUnmanaged(u32) = .empty; for (coff.relocs.items, 0..) |reloc, reloc_i| { const target_sym = reloc.target.get(coff); @@ -5988,6 +6080,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { .import_lookup_table_ni = import_lookup_table_ni, .import_address_table_si = import_address_table_si, .import_hint_name_table_ni = import_hint_name_table_ni, + .import_address_table_symbols = .empty, .len = 0, .hint_name_len = @intCast(import_hint_name_table_len), }; @@ -6034,20 +6127,20 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { gop.value_ptr.len = import_symbol_index + 1; const new_symbol_table_size = addr_info.size * (import_symbol_index + 2); + try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size); + const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff); + try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size); + const opt_name = import.name.toSlice(coff); const opt_import_hint_name_index = if (opt_name) |name| blk: { const import_hint_name_index = gop.value_ptr.hint_name_len; gop.value_ptr.hint_name_len = @intCast( import_hint_name_align.forward(import_hint_name_index + 2 + name.len + 1), ); + try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len); break :blk import_hint_name_index; } else null; - try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size); - const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff); - try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size); - try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len); - const import_hint_name_rva = if (opt_import_hint_name_index) |import_hint_name_index| blk: { const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf); const ordinal_hint: *u16 = @ptrCast(@alignCast(import_hint_name_slice[import_hint_name_index..][0..2])); @@ -6062,26 +6155,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { switch (addr_info.magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { - const Payload = packed union(u31) { - ordinal: packed struct(u31) { - ordinal: u16, - _: u15 = 0, - }, - hint_name_rva: u31, - }; - - const Entry = switch (ct_magic) { - _ => comptime unreachable, - .PE32 => packed struct(u32) { - payload: Payload, - is_ordinal: bool, - }, - .@"PE32+" => packed struct(u64) { - payload: Payload, - _: u32 = 0, - is_ordinal: bool, - }, - }; + const Entry = ImportTable.TableEntry(ct_magic); const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice)); const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice)); const import_hint_name_rvas: [2]Entry = .{ @@ -6112,6 +6186,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { sym.ni = iat_sym.ni; sym.setValue(.{ .node_offset = iat_offset }); si.flushMoved(coff); + (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si; }, .thunk => { sym.section_number = Symbol.Index.text.get(coff).section_number; @@ -6281,15 +6356,18 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.computeNodeRva(ni), ), .import_address_table => |import_index| { - const import_address_table_si = import_index.get(coff).import_address_table_si; + const entry = import_index.get(coff); + const import_address_table_si = entry.import_address_table_si; import_address_table_si.flushMoved(coff); coff.targetStore( &coff.importDirectoryEntryPtr(import_index).import_address_table_rva, import_address_table_si.get(coff).rva, ); + + for (entry.import_address_table_symbols.items) |iat_ptr_si| + iat_ptr_si.flushMoved(coff); }, .import_hint_name_table => |import_index| { - const target_endian = coff.targetEndian(); const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); const import_hint_name_rva = coff.computeNodeRva(ni); coff.targetStore( @@ -6302,32 +6380,36 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { import_entry.import_address_table_si.node(coff).slice(&coff.mf); const import_hint_name_slice = ni.slice(&coff.mf); const import_hint_name_align = ni.alignment(&coff.mf); + var import_hint_name_index: u32 = 0; for (0..import_entry.len) |import_symbol_index| { - import_hint_name_index = @intCast(import_hint_name_align.forward( - std.mem.indexOfScalarPos( - u8, - import_hint_name_slice, - import_hint_name_index, - 0, - ).? + 1, - )); switch (magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { - const Addr = TargetAddr(ct_magic); - const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice)); - const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice)); - const rva = std.mem.nativeTo( - Addr, - import_hint_name_rva + import_hint_name_index, - target_endian, - ); - import_lookup_table[import_symbol_index] = rva; - import_address_table[import_symbol_index] = rva; + const Entry = ImportTable.TableEntry(ct_magic); + const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice)); + const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice)); + + var entry = coff.targetLoad(&import_lookup_table[import_symbol_index]); + if (entry.is_ordinal) + continue; + + import_hint_name_index = @intCast(import_hint_name_align.forward( + std.mem.indexOfScalarPos( + u8, + import_hint_name_slice, + import_hint_name_index, + 0, + ).? + 1, + )); + + entry.payload.hint_name_rva = @intCast(import_hint_name_rva + import_hint_name_index); + import_hint_name_index += 2; + + coff.targetStore(&import_lookup_table[import_symbol_index], entry); + coff.targetStore(&import_address_table[import_symbol_index], entry); }, } - import_hint_name_index += 2; } }, .export_directory_table => { @@ -6679,14 +6761,16 @@ fn updateExportsInner( export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff); - if (isImage(coff)) { - if (@"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) { - coff.optionalHeaderStandardPtr().address_of_entry_point = exported_sym.rva; - } else if (@"export".opts.name.eqlSlice("_tls_used", ip)) { + if (coff.isImage()) { + if (@"export".opts.name.eqlSlice("_tls_used", ip)) { const tls_directory = coff.dataDirectoryPtr(.TLS); tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.value.size }; if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); + } else if ((coff.isExe() and @"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) or + (!coff.isExe() and @"export".opts.name.eqlSlice("_DllMainCRTStartup", ip))) + { + coff.updateEntry(export_sym.gmi); } } else continue; @@ -6780,6 +6864,20 @@ fn updateExportsInner( } } +/// Caller ensures that `applyTargetRelocs` will be called on `si` eventually +fn updateEntry(coff: *Coff, gmi: Node.GlobalMapIndex) void { + const si = gmi.symbol(coff); + log.debug("updateEntry({s}, {d})", .{ gmi.globalName(coff).name.toSlice(coff), si }); + + if (coff.entry != .none) + coff.entry.symbol(coff).get(coff).flags.is_entry = false; + + // TODO: Should we detect the subsystem like link.exe does (if not explicitly set) based on entry name? + + coff.entry = gmi; + si.get(coff).flags.is_entry = true; +} + pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void { _ = coff; _ = exported; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig @@ -756,7 +756,6 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested const node = ni.get(mf); const old_offset, const old_size = node.location().resolve(mf); const new_size = node.flags.alignment.forward(@intCast(requested_size)); - //if (new_size <= old_size) return; // Resize the entire file if (ni == Node.Index.root) { @@ -917,8 +916,9 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested if (new_last_fixed_offset + last_fixed_size <= old_first_floating_offset) break :make_space; assert(direction == .forward); + const shift_alignment = first_floating.flags.alignment.max(last_fixed.flags.alignment); if (first_floating.flags.fixed) { - shift = first_floating.flags.alignment.forward(@intCast( + shift = shift_alignment.forward(@intCast( @max(shift, first_floating_size), )); @@ -930,7 +930,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Move the found floating node to make space for preceding fixed nodes const last = parent.last.get(mf); const last_offset, const last_size = last.location().resolve(mf); - const new_first_floating_offset = first_floating.flags.alignment.forward( + const new_first_floating_offset = shift_alignment.forward( @intCast(@max(new_last_fixed_offset + last_fixed_size, last_offset + last_size)), ); const new_parent_size = new_first_floating_offset + first_floating_size;