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:56-04:00
log769b5eaf9415f25cd68d6cd525614373f162ea90
tree0c02d44837b5515a94e29abcb4f7d585c32c32d4
parent303521cd14e17099be7aade312e83ea539c67b42

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

1 files changed, 184 insertions(+), 104 deletions(-)

src/link/Coff.zig+184-104
......@@ -49,7 +49,7 @@ input_sections: std.ArrayList(Node.InputSection),
4949input_section_pending_index: u32,
5050inputs_complete: bool,
5151exports_complete: bool,
52special_symbols_complete: bool,
52pending_special_symbol: SpecialSymbol,
5353strings: std.HashMapUnmanaged(
5454 u32,
5555 void,
......@@ -790,6 +790,7 @@ pub const ImportTable = struct {
790790};
791791
792792pub const String = enum(u32) {
793 // TODO: Re-order
793794 @".data" = 0,
794795 @".idata" = 6,
795796 @".rdata" = 13,
......@@ -802,6 +803,7 @@ pub const String = enum(u32) {
802803 @".dtors$ZZZ" = 64,
803804 @".bss" = 75,
804805 @".fptable" = 80,
806 @".tls" = 89,
805807 _,
806808
807809 pub const Optional = enum(u32) {
......@@ -817,6 +819,7 @@ pub const String = enum(u32) {
817819 @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"),
818820 @".bss" = @intFromEnum(String.@".bss"),
819821 @".fptable" = @intFromEnum(String.@".fptable"),
822 @".tls" = @intFromEnum(String.@".tls"),
820823 none = std.math.maxInt(u32),
821824 _,
822825
......@@ -892,6 +895,12 @@ pub const WeakExternalStrat = enum(u2) {
892895 }
893896};
894897
898const SpecialSymbol = enum {
899 entry,
900 tls,
901 none,
902};
903
895904pub const Symbol = struct {
896905 ni: MappedFile.Node.Index,
897906 rva: u32,
......@@ -1538,7 +1547,7 @@ fn create(
15381547 .input_section_pending_index = 0,
15391548 .inputs_complete = false,
15401549 .exports_complete = false,
1541 .special_symbols_complete = false,
1550 .pending_special_symbol = .entry,
15421551 .strings = .empty,
15431552 .string_bytes = .empty,
15441553 .section_table = .empty,
......@@ -1718,7 +1727,7 @@ fn initHeaders(
17181727 // TLS section
17191728 if (comp.config.any_non_single_threaded) {
17201729 if (!is_image) expected_nodes_len += 1;
1721 expected_nodes_len += 2;
1730 expected_nodes_len += 1;
17221731 }
17231732 }
17241733 defer assert(coff.nodes.len == expected_nodes_len);
......@@ -2130,10 +2139,11 @@ fn initHeaders(
21302139 });
21312140
21322141 // While tls variables allocated at runtime are writable, the template itself is not.
2133 // In images, this call triggers the creation of a .tls pseudo section in .rdata.
2134 // In objects / archives, this section is part of the above .tls$ section.
2135 _ = try coff.objectSectionMapIndex(
2136 .@".tls$",
2142 // In images, the template is in a .tls pseudo section in .rdata.
2143 // In objects / archives, this section is part of the above .tls$ section. The suffix
2144 // is maintained so merging can occur with other input tls symbols when linked later.
2145 _ = try coff.pseudoSectionMapIndex(
2146 if (is_image) .@".tls" else .@".tls$",
21372147 coff.mf.flags.block_size,
21382148 .{ .read = true, .write = !is_image, .initialized = true },
21392149 );
......@@ -5622,15 +5632,15 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
56225632 }) coff.late_globals_pending_index += 1;
56235633 break :task;
56245634 }
5625 if (coff.exports_complete and !coff.special_symbols_complete) {
5626 coff.special_symbols_complete = true;
5627 coff.flushSpecialSymbols() catch |err| switch (err) {
5628 error.OutOfMemory => |e| return e,
5629 else => |e| return comp.link_diags.fail(
5630 "linker failed to flush special symbols: {t}",
5631 .{e},
5632 ),
5633 };
5635 if (coff.exports_complete and coff.pending_special_symbol != .none) {
5636 coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) catch |err|
5637 switch (err) {
5638 error.OutOfMemory => |e| return e,
5639 else => |e| return comp.link_diags.fail(
5640 "linker failed to flush special symbols: {t}",
5641 .{e},
5642 ),
5643 };
56345644 break :task;
56355645 }
56365646 var lazy_it = coff.lazy.iterator();
......@@ -5773,7 +5783,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
57735783 if (coff.inputs_complete and coff.globals.count() > coff.global_pending_index) return true;
57745784 assert(!coff.exports_complete or coff.inputs_complete);
57755785 if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) return true;
5776 if (coff.exports_complete and !coff.special_symbols_complete) return true;
5786 if (coff.exports_complete and coff.pending_special_symbol != .none) return true;
57775787 for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
57785788 if (coff.symbol_table.pending.count() > 0) return true;
57795789 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 {
63186328 return true;
63196329}
63206330
6321fn flushSpecialSymbols(coff: *Coff) !void {
6331fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol {
63226332 const comp = coff.base.comp;
63236333 const gpa = comp.gpa;
63246334 const machine = coff.targetLoad(&coff.headerPtr().machine);
63256335
6326 if (coff.isImage()) {
6327 // TODO: Use explicitly specified entry if set, add err if not found
6328 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
6329 if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
6330 .WINDOWS_CUI => &.{
6331 .{ "main", "mainCRTStartup" },
6332 .{ "wmain", "wmainCRTStartup" },
6333 },
6334 .WINDOWS_GUI => &.{
6335 .{ "WinMain", "WinMainCRTStartup" },
6336 .{ "wWinMain", "wWinMainCRTStartup" },
6337 },
6338 else => unreachable,
6339 } else &.{
6340 .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
6341 }
6342 else
6343 &.{.{ null, "_DllMainCRTStartup" }};
6336 if (!coff.isImage()) return .none;
6337 return next: switch (pending) {
6338 .entry => {
6339 // TODO: Use explicitly specified entry if set, add err if not found
6340 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
6341 if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
6342 .WINDOWS_CUI => &.{
6343 .{ "main", "mainCRTStartup" },
6344 .{ "wmain", "wmainCRTStartup" },
6345 },
6346 .WINDOWS_GUI => &.{
6347 .{ "WinMain", "WinMainCRTStartup" },
6348 .{ "wWinMain", "wWinMainCRTStartup" },
6349 },
6350 else => unreachable,
6351 } else &.{
6352 .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
6353 }
6354 else
6355 &.{.{ null, "_DllMainCRTStartup" }};
63446356
6345 const entry_si = for (entries) |entry| {
6346 if (entry[0]) |required_name|
6347 if (coff.getDefinedGlobal(required_name) == .null) continue;
6357 const entry_si = for (entries) |entry| {
6358 if (entry[0]) |required_name|
6359 if (coff.getDefinedGlobal(required_name) == .null) continue;
63486360
6349 break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
6350 } else .null;
6361 break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
6362 } else .null;
63516363
6352 if (entry_si != .null) {
6353 log.debug(
6354 "entry({s}, {d})",
6355 .{ entry_si.get(coff).gmi.globalName(coff).name.toSlice(coff), entry_si },
6356 );
6364 if (entry_si != .null) {
6365 log.debug(
6366 "entry({s}, {d})",
6367 .{ entry_si.get(coff).gmi.globalName(coff).name.toSlice(coff), entry_si },
6368 );
63576369
6358 try coff.symbols.ensureTotalCapacity(gpa, 1);
6359 const optional_hdr_si = coff.addSymbolAssumeCapacity();
6360 const optional_hdr_sym = optional_hdr_si.get(coff);
6361 optional_hdr_sym.ni = Node.known.optional_header;
6362 assert(optional_hdr_sym.loc_relocs == .none);
6363 optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6364
6365 const optional_hdr = coff.optionalHeaderStandardPtr();
6366 optional_hdr.address_of_entry_point = std.mem.nativeTo(
6367 u32,
6368 entry_si.get(coff).rva,
6369 coff.targetEndian(),
6370 );
6370 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6371 const optional_hdr_si = coff.addSymbolAssumeCapacity();
6372 const optional_hdr_sym = optional_hdr_si.get(coff);
6373 optional_hdr_sym.ni = Node.known.optional_header;
6374 assert(optional_hdr_sym.loc_relocs == .none);
6375 optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
63716376
6372 try coff.addReloc(
6373 optional_hdr_si,
6374 @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
6375 entry_si,
6376 .{ .known = 0 },
6377 switch (machine) {
6378 else => |tag| @panic(@tagName(tag)),
6379 .AMD64 => .{ .AMD64 = .ADDR32NB },
6380 .I386 => .{ .I386 = .DIR32NB },
6381 },
6382 );
6383 }
6384 }
6377 const optional_hdr = coff.optionalHeaderStandardPtr();
6378 optional_hdr.address_of_entry_point = std.mem.nativeTo(
6379 u32,
6380 entry_si.get(coff).rva,
6381 coff.targetEndian(),
6382 );
63856383
6386 if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
6387 const tls_directory = coff.dataDirectoryPtr(.TLS);
6388 tls_directory.* = .{
6389 .virtual_address = tls_used_si.get(coff).rva,
6390 .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
6391 _ => unreachable,
6392 .PE32 => 24,
6393 .@"PE32+" => 40,
6394 },
6395 };
6396 if (coff.targetEndian() != native_endian)
6397 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6398
6399 try coff.symbols.ensureTotalCapacity(gpa, 1);
6400 const data_dir_si = coff.addSymbolAssumeCapacity();
6401 const data_dir_sym = data_dir_si.get(coff);
6402 data_dir_sym.ni = Node.known.data_directories;
6403 assert(data_dir_sym.loc_relocs == .none);
6404 data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6405
6406 try coff.addReloc(
6407 data_dir_si,
6408 @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
6409 tls_used_si,
6410 .{ .known = 0 },
6411 switch (machine) {
6412 else => |tag| @panic(@tagName(tag)),
6413 .AMD64 => .{ .AMD64 = .ADDR32NB },
6414 .I386 => .{ .I386 = .DIR32NB },
6415 },
6416 );
6417 }
6384 try coff.addReloc(
6385 optional_hdr_si,
6386 @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
6387 entry_si,
6388 .{ .known = 0 },
6389 switch (machine) {
6390 else => |tag| @panic(@tagName(tag)),
6391 .AMD64 => .{ .AMD64 = .ADDR32NB },
6392 .I386 => .{ .I386 = .DIR32NB },
6393 },
6394 );
6395 }
6396
6397 // Referencing the startup functions may trigger loading the object containing them,
6398 // we need to wait until that is done before looking for further symbols.
6399 break :next .tls;
6400 },
6401 .tls => {
6402 if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
6403 log.debug("tlsDir({d})", .{tls_used_si});
6404
6405 const tls_directory = coff.dataDirectoryPtr(.TLS);
6406 tls_directory.* = .{
6407 .virtual_address = tls_used_si.get(coff).rva,
6408 .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
6409 _ => unreachable,
6410 .PE32 => 24,
6411 .@"PE32+" => 40,
6412 },
6413 };
6414 if (coff.targetEndian() != native_endian)
6415 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6416
6417 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6418 const data_dir_si = coff.addSymbolAssumeCapacity();
6419 const data_dir_sym = data_dir_si.get(coff);
6420 data_dir_sym.ni = Node.known.data_directories;
6421 assert(data_dir_sym.loc_relocs == .none);
6422 data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6423
6424 try coff.addReloc(
6425 data_dir_si,
6426 @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
6427 tls_used_si,
6428 .{ .known = 0 },
6429 switch (machine) {
6430 else => |tag| @panic(@tagName(tag)),
6431 .AMD64 => .{ .AMD64 = .ADDR32NB },
6432 .I386 => .{ .I386 = .DIR32NB },
6433 },
6434 );
6435 }
6436
6437 break :next .none;
6438 },
6439 .none => unreachable,
6440 };
64186441}
64196442
64206443fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
......@@ -7137,11 +7160,68 @@ fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void {
71377160pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
71387161 if (coff.dump_snapshot) {
71397162 try coff.printNode(tid, w, .root, 0);
7163 try w.writeAll("Section table:\n");
7164 for (coff.section_table.keys(), coff.section_table.values()) |name, sec|
7165 try coff.printSection(w, name, sec.si);
7166 try w.writeAll("Symbol table:\n");
7167 for (1..coff.symbols.items.len) |si|
7168 try coff.printSymbol(w, @enumFromInt(si));
7169
71407170 return .enabled;
71417171 }
71427172 return .disabled;
71437173}
71447174
7175fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !void {
7176 const sym = si.get(coff);
7177 try w.print("{d:0>6}@{d:0>2} {x:08} n{d:0>8} | {s}\n", .{
7178 si,
7179 sym.section_number,
7180 if (sym.flags.value_tag == .size) sym.value.size else 0,
7181 sym.ni,
7182 name.toSlice(coff),
7183 });
7184}
7185
7186fn printSymbol(coff: *Coff, w: *Io.Writer, si: Symbol.Index) !void {
7187 const sym = si.get(coff);
7188 const node = coff.getNode(sym.ni);
7189 try w.print("{d:0>6}@{d:0>2} {x:08} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} | {f}\n", .{
7190 si,
7191 sym.section_number,
7192 if (sym.flags.value_tag == .size)
7193 @as(u64, sym.value.size)
7194 else if (sym.ni != .none)
7195 sym.ni.location(&coff.mf).resolve(&coff.mf)[1]
7196 else
7197 0,
7198 switch (sym.flags.type) {
7199 .unknown => "u",
7200 .code => "c",
7201 .data => "d",
7202 },
7203 sym.ni,
7204 if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0,
7205 node,
7206 sym.rva,
7207 fmtGlobalName(coff, sym.gmi),
7208 });
7209}
7210
7211const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex };
7212
7213fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalName, globalNameEscape) {
7214 return .{ .data = .{ .coff = coff, .gmi = gmi } };
7215}
7216
7217fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void {
7218 if (data.gmi == .none) return;
7219 const gn = data.gmi.globalName(data.coff);
7220 try w.writeAll(gn.name.toSlice(data.coff));
7221 if (gn.lib_name.unwrap()) |lib_name|
7222 try w.print("({s})", .{lib_name.toSlice(data.coff)});
7223}
7224
71457225pub fn printNode(
71467226 coff: *Coff,
71477227 tid: Zcu.PerThread.Id,