zig

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

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

Coff: fixes to special symbols, more debug output

- Change flushSpecialSymbol into a state machine, since referencing entry points may pull in tls from the crt
- Output symbol / section table with --debug-link-snapshot

Diffstat:
Msrc/link/Coff.zig | 288++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 184 insertions(+), 104 deletions(-)

diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -49,7 +49,7 @@ input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, inputs_complete: bool, exports_complete: bool, -special_symbols_complete: bool, +pending_special_symbol: SpecialSymbol, strings: std.HashMapUnmanaged( u32, void, @@ -790,6 +790,7 @@ pub const ImportTable = struct { }; pub const String = enum(u32) { + // TODO: Re-order @".data" = 0, @".idata" = 6, @".rdata" = 13, @@ -802,6 +803,7 @@ pub const String = enum(u32) { @".dtors$ZZZ" = 64, @".bss" = 75, @".fptable" = 80, + @".tls" = 89, _, pub const Optional = enum(u32) { @@ -817,6 +819,7 @@ pub const String = enum(u32) { @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"), @".bss" = @intFromEnum(String.@".bss"), @".fptable" = @intFromEnum(String.@".fptable"), + @".tls" = @intFromEnum(String.@".tls"), none = std.math.maxInt(u32), _, @@ -892,6 +895,12 @@ pub const WeakExternalStrat = enum(u2) { } }; +const SpecialSymbol = enum { + entry, + tls, + none, +}; + pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, @@ -1538,7 +1547,7 @@ fn create( .input_section_pending_index = 0, .inputs_complete = false, .exports_complete = false, - .special_symbols_complete = false, + .pending_special_symbol = .entry, .strings = .empty, .string_bytes = .empty, .section_table = .empty, @@ -1718,7 +1727,7 @@ fn initHeaders( // TLS section if (comp.config.any_non_single_threaded) { if (!is_image) expected_nodes_len += 1; - expected_nodes_len += 2; + expected_nodes_len += 1; } } defer assert(coff.nodes.len == expected_nodes_len); @@ -2130,10 +2139,11 @@ fn initHeaders( }); // While tls variables allocated at runtime are writable, the template itself is not. - // In images, this call triggers the creation of a .tls pseudo section in .rdata. - // In objects / archives, this section is part of the above .tls$ section. - _ = try coff.objectSectionMapIndex( - .@".tls$", + // In images, the template is in a .tls pseudo section in .rdata. + // In objects / archives, this section is part of the above .tls$ section. The suffix + // is maintained so merging can occur with other input tls symbols when linked later. + _ = try coff.pseudoSectionMapIndex( + if (is_image) .@".tls" else .@".tls$", coff.mf.flags.block_size, .{ .read = true, .write = !is_image, .initialized = true }, ); @@ -5622,15 +5632,15 @@ 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}, - ), - }; + if (coff.exports_complete and coff.pending_special_symbol != .none) { + coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) 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(); @@ -5773,7 +5783,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { 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; + if (coff.exports_complete and coff.pending_special_symbol != .none) 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; @@ -6318,103 +6328,116 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } -fn flushSpecialSymbols(coff: *Coff) !void { +fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol { 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" }}; + if (!coff.isImage()) return .none; + return next: switch (pending) { + .entry => { + // 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; + 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; + 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 }, - ); + 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.symbols.ensureUnusedCapacity(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); - 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 }, - }, - ); - } - } + const optional_hdr = coff.optionalHeaderStandardPtr(); + optional_hdr.address_of_entry_point = std.mem.nativeTo( + u32, + entry_si.get(coff).rva, + coff.targetEndian(), + ); - 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 }, - }, - ); - } + 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 }, + }, + ); + } + + // Referencing the startup functions may trigger loading the object containing them, + // we need to wait until that is done before looking for further symbols. + break :next .tls; + }, + .tls => { + if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| { + log.debug("tlsDir({d})", .{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.ensureUnusedCapacity(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 }, + }, + ); + } + + break :next .none; + }, + .none => unreachable, + }; } fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { @@ -7137,11 +7160,68 @@ fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void { 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); + try w.writeAll("Section table:\n"); + for (coff.section_table.keys(), coff.section_table.values()) |name, sec| + try coff.printSection(w, name, sec.si); + try w.writeAll("Symbol table:\n"); + for (1..coff.symbols.items.len) |si| + try coff.printSymbol(w, @enumFromInt(si)); + return .enabled; } return .disabled; } +fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !void { + const sym = si.get(coff); + try w.print("{d:0>6}@{d:0>2} {x:08} n{d:0>8} | {s}\n", .{ + si, + sym.section_number, + if (sym.flags.value_tag == .size) sym.value.size else 0, + sym.ni, + name.toSlice(coff), + }); +} + +fn printSymbol(coff: *Coff, w: *Io.Writer, si: Symbol.Index) !void { + const sym = si.get(coff); + const node = coff.getNode(sym.ni); + try w.print("{d:0>6}@{d:0>2} {x:08} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} | {f}\n", .{ + si, + sym.section_number, + if (sym.flags.value_tag == .size) + @as(u64, sym.value.size) + else if (sym.ni != .none) + sym.ni.location(&coff.mf).resolve(&coff.mf)[1] + else + 0, + switch (sym.flags.type) { + .unknown => "u", + .code => "c", + .data => "d", + }, + sym.ni, + if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0, + node, + sym.rva, + fmtGlobalName(coff, sym.gmi), + }); +} + +const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex }; + +fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalName, globalNameEscape) { + return .{ .data = .{ .coff = coff, .gmi = gmi } }; +} + +fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void { + if (data.gmi == .none) return; + const gn = data.gmi.globalName(data.coff); + try w.writeAll(gn.name.toSlice(data.coff)); + if (gn.lib_name.unwrap()) |lib_name| + try w.print("({s})", .{lib_name.toSlice(data.coff)}); +} + pub fn printNode( coff: *Coff, tid: Zcu.PerThread.Id,