zig

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

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

Coff: special symbols

- Add a step after exports are known that looks for specific required symbols (entry points, _tls_used) and set up relocs as they may move after this point
- Remove the special case handling of entry point symbols
- Change the linker crash reporter to require --dump-link-snapshot to dump the snapshot

Diffstat:
Msrc/crash_report.zig | 9++++++---
Msrc/link.zig | 10++++++++--
Msrc/link/Coff.zig | 247++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/link/Elf2.zig | 10++++++++--
4 files changed, 180 insertions(+), 96 deletions(-)

diff --git a/src/crash_report.zig b/src/crash_report.zig @@ -133,10 +133,13 @@ fn dumpCrashContext() Io.Writer.Error!void { } else if (LinkerOp.current) |linker_op| { try w.writeAll("Linker snapshot:\n\n"); if (build_options.enable_link_snapshots) { - try linker_op.lf.dump(w, linker_op.tid); - try w.writeAll("\n\n"); + switch (try linker_op.lf.dump(w, linker_op.tid)) { + .unsupported => try w.writeAll("(backend does not support link snapshots))"), + .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"), + .enabled => try w.writeAll("\n\n"), + } } else { - try w.print("(build with -Dlink-snapshot to dump linker state)", .{}); + try w.writeAll("(build with -Dlink-snapshot to dump linker state)"); } } else { try w.writeAll("(no context)\n\n"); diff --git a/src/link.zig b/src/link.zig @@ -1090,7 +1090,13 @@ pub const File = struct { } } - pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !void { + pub const DumpResult = enum { + unsupported, + disabled, + enabled, + }; + + pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult { if (!build_options.enable_link_snapshots) unreachable; switch (base.tag) { .elf, @@ -1100,7 +1106,7 @@ pub const File = struct { .spirv, .plan9, .lld, - => {}, + => return .unsupported, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid); diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -49,6 +49,7 @@ input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, inputs_complete: bool, exports_complete: bool, +special_symbols_complete: bool, strings: std.HashMapUnmanaged( u32, void, @@ -74,7 +75,6 @@ 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, @@ -896,8 +896,7 @@ pub const Symbol = struct { dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - is_entry: bool, - _: u7 = 0, + _: u8 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -1017,6 +1016,11 @@ pub const Symbol = struct { return &coff.symbols.items[@intFromEnum(si)]; } + pub fn unwrap(si: Symbol.Index) ?Symbol.Index { + if (si == .null) return null; + return si; + } + pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index { const ni = si.get(coff).ni; assert(ni != .none); @@ -1074,12 +1078,6 @@ pub const Symbol = struct { pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) void { 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 != end) { const reloc = ri.get(coff); @@ -1549,6 +1547,7 @@ fn create( .input_section_pending_index = 0, .inputs_complete = false, .exports_complete = false, + .special_symbols_complete = false, .strings = .empty, .string_bytes = .empty, .section_table = .empty, @@ -1567,7 +1566,6 @@ fn create( }), .pending_uavs = .empty, .relocs = .empty, - .entry = .none, .const_prog_node = .none, .synth_prog_node = .none, .symbol_prog_node = .none, @@ -2525,7 +2523,6 @@ 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, @@ -2642,6 +2639,14 @@ fn getOrPutGlobalSymbol( return sym_gop; } +fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index { + if (coff.globals.get(.{ + .name = coff.getString(name).unwrap() orelse return .null, + .lib_name = .none, + })) |si| if (si.get(coff).ni != .none) return si; + return .null; +} + pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { const gop = try coff.getOrPutGlobalSymbol(opts); if (gop.found_existing) { @@ -3423,8 +3428,6 @@ pub fn addReloc( ) !void { const gpa = coff.base.comp.gpa; const target = target_si.get(coff); - // TODO: Could duplicate the uninit flag on Symbol.flags? - assert(!coff.targetLoad(loc_si.get(coff).section_number.header(coff).flags).CNT_UNINITIALIZED_DATA); const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len); log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{ @@ -4386,11 +4389,10 @@ fn loadObject( // Resolve this once we see alias alias.weak_external_psi = .wrap(@intCast(i)); } else { - sym.setValue(if (alias.si == .null) .{ - // See .external branch above - .alias_name = alias.name, + sym.setValue(if (alias.si.unwrap()) |alias_si| .{ + .alias_si = alias_si, } else .{ - .alias_si = alias.si, + .alias_name = alias.name, }); sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux; } @@ -5027,35 +5029,9 @@ 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; + if (comp.zcu == null) + coff.exports_complete = true; } pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -5347,17 +5323,6 @@ 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); @@ -5406,12 +5371,20 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { for (undef_indices.items[start_i .. i + 1]) |reference_i| { if (err.note_slot == num_full_notes) break; - const loc_si = coff.relocs.items[reference_i].loc; + const reloc = &coff.relocs.items[reference_i]; + const loc_si = reloc.loc; if (loc_si == prev_loc_si) continue; defer prev_loc_si = loc_si; const loc_sym = loc_si.get(coff); switch (coff.getNode(loc_sym.ni)) { + .data_directories => { + const dir_align = std.mem.Alignment.of(std.coff.ImageDataDirectory); + const dir: std.coff.IMAGE.DIRECTORY_ENTRY = + @enumFromInt(dir_align.backward(reloc.offset) / @sizeOf(std.coff.IMAGE.DIRECTORY_ENTRY)); + err.addNote("referenced by data directory entry: {t}", .{dir}); + }, + .optional_header => err.addNote("referenced by optional header field", .{}), .input_section => |isi| { const other_ioi = isi.input(coff); if (loc_sym.gmi == .none) { @@ -5577,6 +5550,17 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }) coff.late_globals_pending_index += 1; break :task; } + if (coff.exports_complete and !coff.special_symbols_complete) { + coff.special_symbols_complete = true; + coff.flushSpecialSymbols() catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| return comp.link_diags.fail( + "linker failed to flush special symbols: {t}", + .{e}, + ), + }; + break :task; + } var lazy_it = coff.lazy.iterator(); while (lazy_it.next()) |lazy| if (lazy.value.pending_index < lazy.value.map.count()) { const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; @@ -5713,7 +5697,9 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.pending_uavs.count() > 0) return true; if (coff.pending_input != null) return true; if (coff.inputs_complete and coff.globals.count() > coff.global_pending_index) return true; + assert(!coff.exports_complete or coff.inputs_complete); if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) return true; + if (coff.exports_complete and !coff.special_symbols_complete) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.symbol_table.pending.count() > 0) return true; if (coff.input_sections.items.len > coff.input_section_pending_index) return true; @@ -6258,6 +6244,105 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } +fn flushSpecialSymbols(coff: *Coff) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const machine = coff.targetLoad(&coff.headerPtr().machine); + + if (coff.isImage()) { + // TODO: Use explicitly specified entry if set, add err if not found + const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe()) + if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) { + .WINDOWS_CUI => &.{ + .{ "main", "mainCRTStartup" }, + .{ "wmain", "wmainCRTStartup" }, + }, + .WINDOWS_GUI => &.{ + .{ "WinMain", "WinMainCRTStartup" }, + .{ "wWinMain", "wWinMainCRTStartup" }, + }, + else => unreachable, + } else &.{ + .{ "wWinMainCRTStartup", "wWinMainCRTStartup" }, + } + else + &.{.{ null, "_DllMainCRTStartup" }}; + + const entry_si = for (entries) |entry| { + if (entry[0]) |required_name| + if (coff.getDefinedGlobal(required_name) == .null) continue; + + break try coff.globalSymbol(.{ .name = entry[1], .type = .code }); + } else .null; + + if (entry_si != .null) { + log.debug( + "entry({s}, {d})", + .{ entry_si.get(coff).gmi.globalName(coff).name.toSlice(coff), entry_si }, + ); + + try coff.symbols.ensureTotalCapacity(gpa, 1); + const optional_hdr_si = coff.addSymbolAssumeCapacity(); + const optional_hdr_sym = optional_hdr_si.get(coff); + optional_hdr_sym.ni = Node.known.optional_header; + assert(optional_hdr_sym.loc_relocs == .none); + optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + + const optional_hdr = coff.optionalHeaderStandardPtr(); + optional_hdr.address_of_entry_point = std.mem.nativeTo( + u32, + entry_si.get(coff).rva, + coff.targetEndian(), + ); + + try coff.addReloc( + optional_hdr_si, + @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr), + entry_si, + .{ .known = 0 }, + switch (machine) { + else => |tag| @panic(@tagName(tag)), + .AMD64 => .{ .AMD64 = .ADDR32NB }, + .I386 => .{ .I386 = .DIR32NB }, + }, + ); + } + } + + if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| { + const tls_directory = coff.dataDirectoryPtr(.TLS); + tls_directory.* = .{ + .virtual_address = tls_used_si.get(coff).rva, + .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) { + _ => unreachable, + .PE32 => 24, + .@"PE32+" => 40, + }, + }; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); + + try coff.symbols.ensureTotalCapacity(gpa, 1); + const data_dir_si = coff.addSymbolAssumeCapacity(); + const data_dir_sym = data_dir_si.get(coff); + data_dir_sym.ni = Node.known.data_directories; + assert(data_dir_sym.loc_relocs == .none); + data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + + try coff.addReloc( + data_dir_si, + @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr), + tls_used_si, + .{ .known = 0 }, + switch (machine) { + else => |tag| @panic(@tagName(tag)), + .AMD64 => .{ .AMD64 = .ADDR32NB }, + .I386 => .{ .I386 = .DIR32NB }, + }, + ); + } +} + fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -6312,7 +6397,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { } fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { - log.debug("flushMoved({s})", .{@tagName(coff.getNode(ni))}); + log.debug("flushMoved({s}, n{d})", .{ @tagName(coff.getNode(ni)), ni }); switch (coff.getNode(ni)) { .file, .header, @@ -6500,7 +6585,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { const offset, const size = ni.location(&coff.mf).resolve(&coff.mf); - log.debug("flushResized({s}, 0x{x})", .{ @tagName(coff.getNode(ni)), size }); + log.debug("flushResized({s}, n{d}, 0x{x})", .{ @tagName(coff.getNode(ni)), ni, size }); switch (coff.getNode(ni)) { .file => { @@ -6779,6 +6864,7 @@ fn updateExportsInner( }; while (try coff.idle(pt.tid)) {} + const machine = coff.targetLoad(&coff.headerPtr().machine); const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); for (export_indices) |export_index| { @@ -6795,18 +6881,7 @@ fn updateExportsInner( export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff, .none); - 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; + if (!coff.isImage()) continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; const gop = try coff.export_table.entries.getOrPutAdapted( @@ -6888,7 +6963,11 @@ fn updateExportsInner( @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index), export_si, .{ .known = 0 }, - .{ .AMD64 = .ADDR32NB }, + switch (machine) { + else => |tag| @panic(@tagName(tag)), + .AMD64 => .{ .AMD64 = .ADDR32NB }, + .I386 => .{ .I386 = .DIR32NB }, + }, ); } else { gop.value_ptr.si = export_si; @@ -6898,20 +6977,6 @@ 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; @@ -6928,11 +6993,15 @@ fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void { const stderr = try io.lockStderr(&buffer, null); defer io.unlockStderr(); const w = &stderr.file_writer.interface; - try coff.dump(w, tid); + _ = try coff.dump(w, tid); } -pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !void { - try coff.printNode(tid, w, .root, 0); +pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult { + if (coff.dump_snapshot) { + try coff.printNode(tid, w, .root, 0); + return .enabled; + } + return .disabled; } pub fn printNode( diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig @@ -161,6 +161,7 @@ textrel_count: u32, const_prog_node: std.Progress.Node, synth_prog_node: std.Progress.Node, input_prog_node: std.Progress.Node, +dump_snapshot: bool, const Error = link.Error || error{MappedFileIo}; @@ -2624,6 +2625,7 @@ fn create( .synth_prog_node = .none, .input_prog_node = .none, .textrel_count = 0, + .dump_snapshot = options.enable_link_snapshots, }; errdefer elf.deinit(); @@ -6711,8 +6713,12 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm _ = name; } -pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !void { - return elf.printNode(tid, w, .root, 0); +pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult { + if (elf.dump_snapshot) { + try elf.printNode(tid, w, .root, 0); + return .enabled; + } + return .disabled; } pub fn printNode(