authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:35-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:26:55-04:00
loga2f4459da80937e8ca2a19d6298c90690f7ddfcd
tree35cd3e5b732295255e286609c72794ff6184ecc9
parent8bf109b0b34d1d97a71a447ab4d79a89125a2de2

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

2 files changed, 167 insertions(+), 69 deletions(-)

src/link/Coff.zig+164-66
......@@ -74,12 +74,12 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct {
7474 alignment: InternPool.Alignment,
7575}),
7676relocs: std.ArrayList(Reloc),
77entry: Node.GlobalMapIndex,
7778const_prog_node: std.Progress.Node,
7879synth_prog_node: std.Progress.Node,
7980symbol_prog_node: std.Progress.Node,
8081member_prog_node: std.Progress.Node,
8182input_prog_node: std.Progress.Node,
82subsystem: ?std.zig.Subsystem,
8383dump_snapshot: bool,
8484
8585pub const default_file_alignment: u16 = 0x200;
......@@ -728,10 +728,37 @@ pub const ImportTable = struct {
728728 import_lookup_table_ni: MappedFile.Node.Index,
729729 import_address_table_si: Symbol.Index,
730730 import_hint_name_table_ni: MappedFile.Node.Index,
731 // All .iat_ptr globals that reference this table.
732 // This is separate from `iat_symbol_indices` because multiple symbols
733 // can reference to the same iat entry, after name demangling.
734 import_address_table_symbols: std.ArrayList(Symbol.Index),
731735 len: u32,
732736 hint_name_len: u32,
733737 };
734738
739 pub fn TableEntry(comptime magic: std.coff.OptionalHeader.Magic) type {
740 const Payload = packed union(u31) {
741 ordinal: packed struct(u31) {
742 ordinal: u16,
743 _: u15 = 0,
744 },
745 hint_name_rva: u31,
746 };
747
748 return switch (magic) {
749 _ => comptime unreachable,
750 .PE32 => packed struct(u32) {
751 payload: Payload,
752 is_ordinal: bool,
753 },
754 .@"PE32+" => packed struct(u64) {
755 payload: Payload,
756 _: u32 = 0,
757 is_ordinal: bool,
758 },
759 };
760 }
761
735762 const Adapter = struct {
736763 coff: *Coff,
737764
......@@ -864,7 +891,8 @@ pub const Symbol = struct {
864891 dll_storage_class: DllStorageClass,
865892 // Only defined for .alias_si and .alias_name
866893 weak_external_strat: WeakExternalStrat,
867 _: u8 = 0,
894 is_entry: bool,
895 _: u7 = 0,
868896 },
869897 /// Relocations contained within this symbol
870898 loc_relocs: Reloc.Index,
......@@ -1038,7 +1066,15 @@ pub const Symbol = struct {
10381066 }
10391067
10401068 pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff) void {
1041 var ri = si.get(coff).target_relocs;
1069 const sym = si.get(coff);
1070
1071 // TODO: Would this be better modeled using an actual reloc? Would need a si for the header
1072 if (sym.flags.is_entry) {
1073 log.debug("updateEntryRVA({d}, 0x{x})", .{ si, sym.rva });
1074 coff.optionalHeaderStandardPtr().address_of_entry_point = sym.rva;
1075 }
1076
1077 var ri = sym.target_relocs;
10421078 while (ri != .none) {
10431079 const reloc = ri.get(coff);
10441080 assert(reloc.target == si);
......@@ -1525,12 +1561,12 @@ fn create(
15251561 }),
15261562 .pending_uavs = .empty,
15271563 .relocs = .empty,
1564 .entry = .none,
15281565 .const_prog_node = .none,
15291566 .synth_prog_node = .none,
15301567 .symbol_prog_node = .none,
15311568 .member_prog_node = .none,
15321569 .input_prog_node = .none,
1533 .subsystem = options.subsystem,
15341570 .dump_snapshot = options.enable_link_snapshots,
15351571 };
15361572 errdefer coff.deinit();
......@@ -1549,6 +1585,11 @@ fn create(
15491585 major_subsystem_version,
15501586 minor_subsystem_version,
15511587 magic,
1588 if (options.subsystem) |s| switch (s) {
1589 .console => .WINDOWS_CUI,
1590 .windows => .WINDOWS_GUI,
1591 else => return error.UnsupportedCOFFSubsystem,
1592 } else .WINDOWS_CUI,
15521593 section_align,
15531594 std.fs.path.basename(path.sub_path),
15541595 );
......@@ -1619,6 +1660,10 @@ fn isArchive(coff: *const Coff) bool {
16191660 };
16201661}
16211662
1663fn isExe(coff: *const Coff) bool {
1664 return coff.base.comp.config.output_mode == .Exe;
1665}
1666
16221667fn isObj(coff: *const Coff) bool {
16231668 return coff.base.comp.config.output_mode == .Obj;
16241669}
......@@ -1639,6 +1684,7 @@ fn initHeaders(
16391684 major_subsystem_version: u16,
16401685 minor_subsystem_version: u16,
16411686 magic: std.coff.OptionalHeader.Magic,
1687 subsystem: std.coff.Subsystem,
16421688 section_align: std.mem.Alignment,
16431689 file_name: []const u8,
16441690) !void {
......@@ -1841,7 +1887,7 @@ fn initHeaders(
18411887 .size_of_image = 0,
18421888 .size_of_headers = 0,
18431889 .checksum = 0,
1844 .subsystem = .WINDOWS_CUI,
1890 .subsystem = subsystem,
18451891 .dll_flags = .{
18461892 .HIGH_ENTROPY_VA = true,
18471893 .DYNAMIC_BASE = true,
......@@ -1890,7 +1936,7 @@ fn initHeaders(
18901936 .size_of_image = 0,
18911937 .size_of_headers = 0,
18921938 .checksum = 0,
1893 .subsystem = .WINDOWS_CUI,
1939 .subsystem = subsystem,
18941940 .dll_flags = .{
18951941 .HIGH_ENTROPY_VA = true,
18961942 .DYNAMIC_BASE = true,
......@@ -2079,9 +2125,6 @@ pub fn initBuiltins(coff: *Coff) !void {
20792125 const gpa = comp.gpa;
20802126 const target = &comp.root_mod.resolved_target.result;
20812127 if (coff.isImage()) {
2082 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2083 try coff.globals.ensureUnusedCapacity(gpa, 1);
2084
20852128 const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data });
20862129 const sym = si.get(coff);
20872130 sym.ni = Node.known.header;
......@@ -2099,8 +2142,16 @@ pub fn initBuiltins(coff: *Coff) !void {
20992142
21002143 for (lists) |list| {
21012144 const addr_info = coff.targetAddrInfo();
2102 const start_osmi = try coff.objectSectionMapIndex(list.start, addr_info.alignment, .{ .read = true });
2103 const end_osmi = try coff.objectSectionMapIndex(list.end, addr_info.alignment, .{ .read = true });
2145 const start_osmi = try coff.objectSectionMapIndex(
2146 list.start,
2147 addr_info.alignment,
2148 .{ .read = true },
2149 );
2150 const end_osmi = try coff.objectSectionMapIndex(
2151 list.end,
2152 addr_info.alignment,
2153 .{ .read = true },
2154 );
21042155
21052156 const start_sym = start_osmi.symbol(coff).get(coff);
21062157 try start_sym.ni.resize(&coff.mf, gpa, addr_info.size);
......@@ -2460,6 +2511,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
24602511 .type = .unknown,
24612512 .dll_storage_class = .default,
24622513 .weak_external_strat = undefined,
2514 .is_entry = false,
24632515 },
24642516 .loc_relocs = .none,
24652517 .target_relocs = .none,
......@@ -2482,14 +2534,14 @@ fn getOrPutString(coff: *Coff, string: []const u8) !String {
24822534fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
24832535 return (try coff.getOrPutString(string orelse return .none)).toOptional();
24842536}
2485fn getString(coff: *Coff, string: []const u8) ?String {
2537fn getString(coff: *Coff, string: []const u8) String.Optional {
24862538 if (coff.strings.getKeyAdapted(
24872539 string,
24882540 std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes },
24892541 )) |key|
2490 return @enumFromInt(key)
2542 return @as(String, @enumFromInt(key)).toOptional()
24912543 else
2492 return null;
2544 return .none;
24932545}
24942546
24952547/// 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 {
48824934
48834935pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
48844936 _ = prog_node;
4937 const base = coff.base;
4938 const comp = base.comp;
4939
48854940 log.debug("prelink()", .{});
48864941
48874942 if (coff.pending_default_libs.items.len > 0) {
48884943 // Libs provided by /DEFAULTLIB arguments in objects are searched after all other inputs
4889 const base = coff.base;
4890 const comp = base.comp;
48914944 const gpa = comp.gpa;
48924945 const arena = comp.arena;
48934946 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 {
49495002 }
49505003 }
49515004
5005 if (coff.isImage() and comp.config.link_libc) {
5006 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
5007 if (comp.zcu == null) switch (coff.optionalHeaderField(.subsystem)) {
5008 .WINDOWS_CUI => &.{
5009 .{ "main", "mainCRTStartup" },
5010 .{ "wmain", "wmainCRTStartup" },
5011 },
5012 .WINDOWS_GUI => &.{
5013 .{ "WinMain", "WinMainCRTStartup" },
5014 .{ "wWinMain", "wWinMainCRTStartup" },
5015 },
5016 else => unreachable,
5017 } else &.{}
5018 else
5019 &.{.{ null, "_DllMainCRTStartup" }};
5020
5021 for (entries) |entry| {
5022 if (entry[0]) |required_name| {
5023 const str = coff.getString(required_name).unwrap() orelse continue;
5024 const si = coff.globals.get(.{ .name = str, .lib_name = .none }) orelse continue;
5025 if (si.get(coff).ni == .none) continue;
5026 }
5027
5028 const si = try coff.globalSymbol(.{ .name = entry[1], .type = .code });
5029 coff.updateEntry(si.get(coff).gmi);
5030 }
5031 }
5032
49525033 coff.inputs_complete = true;
49535034}
49545035
......@@ -5241,6 +5322,17 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
52415322 const gpa = comp.gpa;
52425323 const max_notes = 4;
52435324
5325 if (coff.isImage()) {
5326 if (coff.entry == .none)
5327 comp.link_diags.addError("no entry point defined", .{})
5328 else if (coff.entry.symbol(coff).get(coff).ni == .none) {
5329 comp.link_diags.addError(
5330 "no definition for entry point '{s}' found",
5331 .{coff.entry.globalName(coff).name.toSlice(coff)},
5332 );
5333 }
5334 }
5335
52445336 var undef_indices: std.ArrayListUnmanaged(u32) = .empty;
52455337 for (coff.relocs.items, 0..) |reloc, reloc_i| {
52465338 const target_sym = reloc.target.get(coff);
......@@ -5988,6 +6080,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
59886080 .import_lookup_table_ni = import_lookup_table_ni,
59896081 .import_address_table_si = import_address_table_si,
59906082 .import_hint_name_table_ni = import_hint_name_table_ni,
6083 .import_address_table_symbols = .empty,
59916084 .len = 0,
59926085 .hint_name_len = @intCast(import_hint_name_table_len),
59936086 };
......@@ -6034,20 +6127,20 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
60346127 gop.value_ptr.len = import_symbol_index + 1;
60356128 const new_symbol_table_size = addr_info.size * (import_symbol_index + 2);
60366129
6130 try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6131 const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
6132 try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6133
60376134 const opt_name = import.name.toSlice(coff);
60386135 const opt_import_hint_name_index = if (opt_name) |name| blk: {
60396136 const import_hint_name_index = gop.value_ptr.hint_name_len;
60406137 gop.value_ptr.hint_name_len = @intCast(
60416138 import_hint_name_align.forward(import_hint_name_index + 2 + name.len + 1),
60426139 );
6140 try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
60436141 break :blk import_hint_name_index;
60446142 } else null;
60456143
6046 try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6047 const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
6048 try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6049 try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
6050
60516144 const import_hint_name_rva = if (opt_import_hint_name_index) |import_hint_name_index| blk: {
60526145 const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
60536146 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 {
60626155 switch (addr_info.magic) {
60636156 _ => unreachable,
60646157 inline .PE32, .@"PE32+" => |ct_magic| {
6065 const Payload = packed union(u31) {
6066 ordinal: packed struct(u31) {
6067 ordinal: u16,
6068 _: u15 = 0,
6069 },
6070 hint_name_rva: u31,
6071 };
6072
6073 const Entry = switch (ct_magic) {
6074 _ => comptime unreachable,
6075 .PE32 => packed struct(u32) {
6076 payload: Payload,
6077 is_ordinal: bool,
6078 },
6079 .@"PE32+" => packed struct(u64) {
6080 payload: Payload,
6081 _: u32 = 0,
6082 is_ordinal: bool,
6083 },
6084 };
6158 const Entry = ImportTable.TableEntry(ct_magic);
60856159 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
60866160 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
60876161 const import_hint_name_rvas: [2]Entry = .{
......@@ -6112,6 +6186,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
61126186 sym.ni = iat_sym.ni;
61136187 sym.setValue(.{ .node_offset = iat_offset });
61146188 si.flushMoved(coff);
6189 (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si;
61156190 },
61166191 .thunk => {
61176192 sym.section_number = Symbol.Index.text.get(coff).section_number;
......@@ -6281,15 +6356,18 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
62816356 coff.computeNodeRva(ni),
62826357 ),
62836358 .import_address_table => |import_index| {
6284 const import_address_table_si = import_index.get(coff).import_address_table_si;
6359 const entry = import_index.get(coff);
6360 const import_address_table_si = entry.import_address_table_si;
62856361 import_address_table_si.flushMoved(coff);
62866362 coff.targetStore(
62876363 &coff.importDirectoryEntryPtr(import_index).import_address_table_rva,
62886364 import_address_table_si.get(coff).rva,
62896365 );
6366
6367 for (entry.import_address_table_symbols.items) |iat_ptr_si|
6368 iat_ptr_si.flushMoved(coff);
62906369 },
62916370 .import_hint_name_table => |import_index| {
6292 const target_endian = coff.targetEndian();
62936371 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
62946372 const import_hint_name_rva = coff.computeNodeRva(ni);
62956373 coff.targetStore(
......@@ -6302,32 +6380,36 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
63026380 import_entry.import_address_table_si.node(coff).slice(&coff.mf);
63036381 const import_hint_name_slice = ni.slice(&coff.mf);
63046382 const import_hint_name_align = ni.alignment(&coff.mf);
6383
63056384 var import_hint_name_index: u32 = 0;
63066385 for (0..import_entry.len) |import_symbol_index| {
6307 import_hint_name_index = @intCast(import_hint_name_align.forward(
6308 std.mem.indexOfScalarPos(
6309 u8,
6310 import_hint_name_slice,
6311 import_hint_name_index,
6312 0,
6313 ).? + 1,
6314 ));
63156386 switch (magic) {
63166387 _ => unreachable,
63176388 inline .PE32, .@"PE32+" => |ct_magic| {
6318 const Addr = TargetAddr(ct_magic);
6319 const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice));
6320 const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice));
6321 const rva = std.mem.nativeTo(
6322 Addr,
6323 import_hint_name_rva + import_hint_name_index,
6324 target_endian,
6325 );
6326 import_lookup_table[import_symbol_index] = rva;
6327 import_address_table[import_symbol_index] = rva;
6389 const Entry = ImportTable.TableEntry(ct_magic);
6390 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
6391 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
6392
6393 var entry = coff.targetLoad(&import_lookup_table[import_symbol_index]);
6394 if (entry.is_ordinal)
6395 continue;
6396
6397 import_hint_name_index = @intCast(import_hint_name_align.forward(
6398 std.mem.indexOfScalarPos(
6399 u8,
6400 import_hint_name_slice,
6401 import_hint_name_index,
6402 0,
6403 ).? + 1,
6404 ));
6405
6406 entry.payload.hint_name_rva = @intCast(import_hint_name_rva + import_hint_name_index);
6407 import_hint_name_index += 2;
6408
6409 coff.targetStore(&import_lookup_table[import_symbol_index], entry);
6410 coff.targetStore(&import_address_table[import_symbol_index], entry);
63286411 },
63296412 }
6330 import_hint_name_index += 2;
63316413 }
63326414 },
63336415 .export_directory_table => {
......@@ -6679,14 +6761,16 @@ fn updateExportsInner(
66796761 export_sym.section_number = exported_sym.section_number;
66806762 defer export_si.applyTargetRelocs(coff);
66816763
6682 if (isImage(coff)) {
6683 if (@"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) {
6684 coff.optionalHeaderStandardPtr().address_of_entry_point = exported_sym.rva;
6685 } else if (@"export".opts.name.eqlSlice("_tls_used", ip)) {
6764 if (coff.isImage()) {
6765 if (@"export".opts.name.eqlSlice("_tls_used", ip)) {
66866766 const tls_directory = coff.dataDirectoryPtr(.TLS);
66876767 tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.value.size };
66886768 if (coff.targetEndian() != native_endian)
66896769 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6770 } else if ((coff.isExe() and @"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) or
6771 (!coff.isExe() and @"export".opts.name.eqlSlice("_DllMainCRTStartup", ip)))
6772 {
6773 coff.updateEntry(export_sym.gmi);
66906774 }
66916775 } else continue;
66926776
......@@ -6780,6 +6864,20 @@ fn updateExportsInner(
67806864 }
67816865}
67826866
6867/// Caller ensures that `applyTargetRelocs` will be called on `si` eventually
6868fn updateEntry(coff: *Coff, gmi: Node.GlobalMapIndex) void {
6869 const si = gmi.symbol(coff);
6870 log.debug("updateEntry({s}, {d})", .{ gmi.globalName(coff).name.toSlice(coff), si });
6871
6872 if (coff.entry != .none)
6873 coff.entry.symbol(coff).get(coff).flags.is_entry = false;
6874
6875 // TODO: Should we detect the subsystem like link.exe does (if not explicitly set) based on entry name?
6876
6877 coff.entry = gmi;
6878 si.get(coff).flags.is_entry = true;
6879}
6880
67836881pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void {
67846882 _ = coff;
67856883 _ = exported;
src/link/MappedFile.zig+3-3
......@@ -756,7 +756,6 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
756756 const node = ni.get(mf);
757757 const old_offset, const old_size = node.location().resolve(mf);
758758 const new_size = node.flags.alignment.forward(@intCast(requested_size));
759 //if (new_size <= old_size) return;
760759
761760 // Resize the entire file
762761 if (ni == Node.Index.root) {
......@@ -917,8 +916,9 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
917916 if (new_last_fixed_offset + last_fixed_size <= old_first_floating_offset)
918917 break :make_space;
919918 assert(direction == .forward);
919 const shift_alignment = first_floating.flags.alignment.max(last_fixed.flags.alignment);
920920 if (first_floating.flags.fixed) {
921 shift = first_floating.flags.alignment.forward(@intCast(
921 shift = shift_alignment.forward(@intCast(
922922 @max(shift, first_floating_size),
923923 ));
924924
......@@ -930,7 +930,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
930930 // Move the found floating node to make space for preceding fixed nodes
931931 const last = parent.last.get(mf);
932932 const last_offset, const last_size = last.location().resolve(mf);
933 const new_first_floating_offset = first_floating.flags.alignment.forward(
933 const new_first_floating_offset = shift_alignment.forward(
934934 @intCast(@max(new_last_fixed_offset + last_fixed_size, last_offset + last_size)),
935935 );
936936 const new_parent_size = new_first_floating_offset + first_floating_size;